diff --git a/.gitignore b/.gitignore index 9daa824..7948b66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store node_modules +/app/assets/builds/* diff --git a/lib/generators/rbui/base_generator.rb b/lib/generators/rbui/base_generator.rb new file mode 100644 index 0000000..1d2a63b --- /dev/null +++ b/lib/generators/rbui/base_generator.rb @@ -0,0 +1,15 @@ +require "rails/generators" + +module RBUI + module Generators + class BaseGenerator < defined?(Rails::Generators::Base) ? Rails::Generators::Base : Object + namespace "rbui:base" + + source_root File.join(__dir__, "templates") + + def copy_templates + template "base_store_initializer.rb", "config/initializers/rbui.rb" + end + end + end +end diff --git a/lib/generators/rbui/component_generator.rb b/lib/generators/rbui/component_generator.rb new file mode 100644 index 0000000..d20eeec --- /dev/null +++ b/lib/generators/rbui/component_generator.rb @@ -0,0 +1,43 @@ +# rails g phlex:component alert +module RBUI + module Generators + class BaseGenerator < defined?(Rails::Generators::Base) ? Rails::Generators::Base : Object + namespace "rbui:component" + + if defined?(Rails::Generators::Base) + source_root File.expand_path("../../..", __dir__) + argument :component_name, type: :string, required: true + + def copy_component_files + component = component_name.downcase + source_path = "lib/rbui" + destination_path = "app/components/phlex_ui" + + if File.directory?(File.join(self.class.source_root, source_path, component)) + # If it's a directory, copy the entire directory + directory File.join(source_path, component), File.join(destination_path, component) + puts "Copied directory #{component} to #{destination_path}" + elsif File.exist?(File.join(self.class.source_root, source_path, "#{component}.rb")) + # If it's a single file, copy just the file + copy_file File.join(source_path, "base.rb"), File.join(destination_path, "base.rb") + # copy_file File.join(source_path, "attribute_merger.rb"), File.join(destination_path, "attribute_merger.rb") + copy_file File.join(source_path, "#{component}.rb"), File.join(destination_path, "#{component}.rb") + puts "Copied file #{component}.rb to #{destination_path}" + else + puts "Component '#{component}' not found in phlex_ui gem" + end + end + + private + + def source_path(component) + "lib/phlex_ui/#{component}" + end + else + def copy_component_files + puts "This generator can only be run in a Rails environment." + end + end + end + end +end diff --git a/lib/generators/rbui/install/install_generator.rb b/lib/generators/rbui/install/install_generator.rb new file mode 100644 index 0000000..c5db2a0 --- /dev/null +++ b/lib/generators/rbui/install/install_generator.rb @@ -0,0 +1,128 @@ +module RBUI + module Generators + class InstallGenerator < defined?(Rails::Generators::Base) ? Rails::Generators::Base : Object + namespace "rbui:install" + + if defined?(Rails::Generators::Base) + source_root File.expand_path("templates", __dir__) + + def add_phlex_rails + say "Checking for Phlex Rails" + if gem_installed?("phlex-rails") + say "Phlex Rails is already installed", :green + else + say "Adding Phlex Rails" + run "bundle add phlex-rails" + end + + if yes?("Do you want to run the Phlex installer? (y/n)") + say "Run Phlex install" + run "bin/rails generate phlex:install" + end + end + + def install_stuff + if yes?("Do you want to set up the dev test data? (y/n)") + say "Add index controller" + run "bin/rails generate controller static index --no-helper --no-assets --no-test-framework --no-jbuilder" + + say "Add index view" + run "bin/rails g phlex:view Static::Index" + + append_to_file "app/controllers/static_controller.rb", after: " def index" do + "\n render Static::IndexView" + end + + template "index_view.rb", "app/views/static/index_view.rb", force: true + + say "Add index route" + append_to_file "config/routes.rb", after: "Rails.application.routes.draw do" do + "\n root to: \"static#index\"\n" + end + end + + say "Checking for Tailwind CSS" + if gem_installed?("tailwindcss-rails") + say "Tailwind CSS is already installed", :green + + if yes?("Do you want to run the Tailwind installer? (y/n)") + say "Run Tailwind install" + run "./bin/rails tailwindcss:install" + end + elsif yes?("Do you want us to install Tailwind CSS? (y/n)") + say "Adding Tailwind CSS" + run "./bin/bundle add tailwindcss-rails" + + say "Run Tailwind install" + run "./bin/rails tailwindcss:install" + end + + say "Add tailwind animate" + run "yarn add tailwindcss-animate" + + say "update tailwind.config.js" + template "tailwind.config.js", "config/tailwind.config.js", force: true + + say "Add CSS variables" + template "application.tailwind.css", "app/assets/stylesheets/application.tailwind.css", force: true + end + + def pin_rbui_js + importmap_binstub = Rails.root.join("bin/importmap") + importmap_config_path = Rails.root.join("config/importmap.rb") + stimulus_path = Rails.root.join("app/javascript/application.js") + + if importmap_binstub.exist? + say "Pin rbui-js" + append_to_file importmap_config_path do + %(pin "rbui-js", to: "rbui-js.js"\n) + end + else + say "Add rbui-js package" + # run "yarn add rbui-js" + run "yarn add ../phlex_ui" + run "yarn add phlex_ui" + end + + if stimulus_path.exist? + say "Add RBUI Stimulus controllers" + append_to_file stimulus_path do + "\nimport \"rbui-js\";\nimport \"phlex_ui\";\n" + end + run "yarn build" + else + say "Default Stimulus location is missing: app/javascript/controllers/index.js", :red + say " Add to your Stimulus index.js:" + say " import \"rbui-js\"" + end + end + + def include_rbui + say "Add RBUI to your global component layout" + insert_into_file "app/views/application_view.rb", after: "class ApplicationView < ApplicationComponent\n" do + " include RBUI\n include PhlexUI\n" + end + end + + else + def self.source_root + File.expand_path("templates", __dir__) + end + + def add_stylesheet_link + puts "This generator can only be run in a Rails environment." + end + + def revoke + puts "This generator can only be run in a Rails environment." + end + end + + private + + def gem_installed?(name) + Gem::Specification.find_all_by_name(name).any? + end + end + end +end diff --git a/lib/generators/rbui/install/templates/.keep b/lib/generators/rbui/install/templates/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/generators/rbui/install/templates/application.tailwind.css.tt b/lib/generators/rbui/install/templates/application.tailwind.css.tt new file mode 100644 index 0000000..f9bcc35 --- /dev/null +++ b/lib/generators/rbui/install/templates/application.tailwind.css.tt @@ -0,0 +1,60 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 350 89% 60%; + --destructive-foreground: 0 0% 100%; + --warning: 38 92% 50%; + --warning-foreground: 0 0% 100%; + --success: 87 100% 37%; + --success-foreground: 0 0% 100%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --radius: 0.5rem; + } + + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 350 89% 60%; + --destructive-foreground: 0 0% 100%; + --warning: 38 92% 50%; + --warning-foreground: 0 0% 100%; + --success: 84 81% 44%; + --success-foreground: 0 0% 100%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + } +} diff --git a/lib/generators/rbui/install/templates/index_view.rb.tt b/lib/generators/rbui/install/templates/index_view.rb.tt new file mode 100644 index 0000000..a5a8cd7 --- /dev/null +++ b/lib/generators/rbui/install/templates/index_view.rb.tt @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Static::IndexView < ApplicationView + def template + render PhlexUI::Button.new { "click me" } + br {} + + # UI.Button { "click me" } + + AlertDialog do + AlertDialogTrigger do + Button { "Show dialog" } + end + AlertDialogContent do + AlertDialogHeader do + AlertDialogTitle { "Are you absolutely sure?" } + AlertDialogDescription { "This action cannot be undone. This will permanently delete your account and remove your data from our servers." } + end + AlertDialogFooter do + AlertDialogCancel { "Cancel" } + AlertDialogAction { "Continue" } # Will probably be a link to a controller action (e.g. delete account) + end + end + end + h1 { "Static::Index" } + p { "Find me in app/views/static/index_view.rb" } + end +end + diff --git a/lib/generators/rbui/install/templates/tailwind.config.js.tt b/lib/generators/rbui/install/templates/tailwind.config.js.tt new file mode 100644 index 0000000..43549ae --- /dev/null +++ b/lib/generators/rbui/install/templates/tailwind.config.js.tt @@ -0,0 +1,77 @@ +// For importing tailwind styles from phlex_ui/phlex_ui_pro gem +const execSync = require('child_process').execSync; + +// Import phlex_ui gem path (To make sure Tailwind loads classes used by phlex_ui gem) +const outputPhlexUI = execSync('bundle show phlex_ui', { encoding: 'utf-8' }); +const phlex_ui_path = outputPhlexUI.trim() + '/**/*.rb'; + +const defaultTheme = require('tailwindcss/defaultTheme') + +module.exports = { + darkMode: ["class"], + content: [ + './app/views/**/*.{erb,haml,html,slim,rb}', + './app/helpers/**/*.rb', + './app/assets/stylesheets/**/*.css', + './app/javascript/**/*.js', + phlex_ui_path + ], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + warning: { + DEFAULT: "hsl(var(--warning))", + foreground: "hsl(var(--warning-foreground))", + }, + success: { + DEFAULT: "hsl(var(--success))", + foreground: "hsl(var(--success-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: defaultTheme.fontFamily.sans, + }, + }, + }, + plugins: [ + require("tailwindcss-animate"), + ], +} + diff --git a/lib/generators/rbui/templates/base_store_initializer.rb.tt b/lib/generators/rbui/templates/base_store_initializer.rb.tt new file mode 100644 index 0000000..e88f39a --- /dev/null +++ b/lib/generators/rbui/templates/base_store_initializer.rb.tt @@ -0,0 +1,9 @@ +PhlexUI.setup do |config| + # Setting a namespace allows you to access PhlexUI components through this namespace. + # For example, with namespace set to "UI", you can use: + # UI::Button.new instead of PhlexUI::Button.new + # UI::Card.new instead of PhlexUI::Card.new + # This can help avoid naming conflicts and allows for cleaner, more concise code. + # If you prefer to use PhlexUI components directly, you can leave this unset. + config.namespace = "UI" +end diff --git a/lib/phlex_ui.rb b/lib/phlex_ui.rb index 4719c3e..caeae51 100644 --- a/lib/phlex_ui.rb +++ b/lib/phlex_ui.rb @@ -1,6 +1,14 @@ require "json" require "phlex" +if defined?(ActiveSupport::Inflector) + require "active_support/inflector" + ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym "PhlexUI" + inflect.acronym "UI" + end +end + module RBUI extend Phlex::Kit end @@ -46,3 +54,9 @@ def self.create_namespace_module # Manually require all the files Dir.glob(File.join(__dir__, "phlex_ui", "**", "*.rb")).sort.each { |file| require file } Dir.glob(File.join(__dir__, "rbui", "**", "*.rb")).sort.each { |file| require file } + +# If you need to require generators (assuming they're needed) +if defined?(Rails::Generators) + require_relative "generators/rbui/install/install_generator" + require_relative "generators/rbui/component_generator" +end diff --git a/lib/rbui/railtie.rb b/lib/rbui/railtie.rb new file mode 100644 index 0000000..f6eefaa --- /dev/null +++ b/lib/rbui/railtie.rb @@ -0,0 +1,41 @@ +module RBUI + if defined?(Rails) + class Railtie < ::Rails::Railtie + generators do + require_relative "../generators/rbui/install/install_generator" + + config.app_generators do |g| + g.templates.unshift File.expand_path("../templates", __FILE__) + end + + initializer "rbui.set_generator_namespace" do + Rails::Generators.namespace(RBUI::Generators, as: "rbui") + end + end + + # Add Zeitwerk configuration + initializer "rbui.configure_zeitwerk" do + Rails.autoloaders.main.inflector.inflect( + "phlex_ui" => "PhlexUI", + "phlexui" => "PhlexUI" + ) + end + + # Add component loading + config.to_prepare do + phlex_ui_components_path = Rails.root.join("app/components/phlex_ui") + + if Dir.exist?(phlex_ui_components_path) + Dir[phlex_ui_components_path.join("*.rb")].each do |file| + component_name = File.basename(file, ".rb").camelize + full_component_name = "PhlexUI::#{component_name}" + + if defined?(full_component_name.constantize) + load file + end + end + end + end + end + end +end diff --git a/phlex_ui.gemspec b/phlex_ui.gemspec index d7c4bb1..06de048 100644 --- a/phlex_ui.gemspec +++ b/phlex_ui.gemspec @@ -8,6 +8,7 @@ Gem::Specification.new do |s| s.authors = ["George Kettle"] s.email = "george.kettle@icloud.com" s.files = Dir["lib/**/*.rb", "tasks/**/*.rake"] + s.require_path = "lib" s.homepage = "https://rubygems.org/gems/phlex_ui" s.license = "MIT" diff --git a/test/lib/generators/phlex_ui_generator_test.rb b/test/lib/generators/phlex_ui_generator_test.rb new file mode 100644 index 0000000..8310d24 --- /dev/null +++ b/test/lib/generators/phlex_ui_generator_test.rb @@ -0,0 +1,14 @@ +require "test_helper" +require "generators/phlex_ui/phlex_ui_generator" + +class PhlexUiGeneratorTest < Rails::Generators::TestCase + tests PhlexUiGenerator + destination Rails.root.join("tmp/generators") + setup :prepare_destination + + # test "generator runs without errors" do + # assert_nothing_raised do + # run_generator ["arguments"] + # end + # end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index c4e7c96..c6b10a4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -17,3 +17,8 @@ def view_template(&) def phlex_context(&) render TestContext.new, & end +# Add this near the top of your test_helper.rb +trace = TracePoint.new(:class) do |tp| + puts "Loaded: #{tp.path}" if tp.path.include?("ahoy") +end +trace.enable