diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0ad438d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.rubocop.yml b/.rubocop.yml index 019df56..a96266d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -54,9 +54,6 @@ Naming/PredicateName: Naming/VariableName: Enabled: true -Lint/DeprecatedClassMethods: - Enabled: true - Layout/AlignParameters: Enabled: true @@ -117,3 +114,6 @@ Layout/SpaceInsideParens: Layout/TrailingWhitespace: Enabled: true + +Lint/DeprecatedClassMethods: + Enabled: true diff --git a/CHANGELOG.md b/CHANGELOG.md index ef8a6d7..103e485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ **Enhancements:** - Komponent now reports component stats when you run `bin/rails stats` +- Komponent now includes a styleguide engine that you can mount to your project + to document your components, and 2 new generators: + - `rails g komponent:styleguide` to set it up + - `rails g komponent:examples` to generate an `examples` file for each existing component **Bug fixes:** - Removed redundant `class` attribute in HAML templates diff --git a/README.md b/README.md index ed8968f..303fbf4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ This gem has been inspired by our Rails development practices at [Ouvrages](http - [Stimulus integration](#stimulus-integration) - [Internationalization](#internationalization) - [Available locales configuration](#available-locales-configuration) + - [Styleguide](#styleguide) - [Configuration](#configuration) - [Change default root path](#change-default-root-path) - [Default options for the generators](#default-options-for-the-generators) @@ -313,6 +314,34 @@ I18n.available_locales = [:en, :fr] > If you have the `rails-i18n` gem in your `Gemfile`, you should whitelist locales to prevent creating a lot of > locale files when you generate a new component. +### Styleguide + +Komponent includes a basic styleguide engine that you can use in your project to document your components. + +![Komponent styleguide UI](https://user-images.githubusercontent.com/38524/41193700-45909330-6c10-11e8-87b7-59e628529200.png) + +To set it up, you can use the generator: + +```sh +rails generate komponent:styleguide +``` + +This command will: + +* copy the styleguide components (`komponent/container`, `komponent/footer`, `komponent/header` and `komponent/sidebar`) to your components folder, so you can customize them +* add a new `komponent.js` pack to your packs folder +* mount the engine in your routes + +Then, for each component, you can describe it and render examples for each state in the `_example.html.slim` file from the component folder. The engine will then render it on the component page. + +If you have existing components, you can generate all their example files at once with: + +```sh +rails generate komponent:examples +``` + +Finally, visit `http://localhost:3000/styleguide` to access your styleguide. + ### Configuration #### Change default root path diff --git a/app/controllers/komponent/styleguide_controller.rb b/app/controllers/komponent/styleguide_controller.rb new file mode 100644 index 0000000..5c8e8cd --- /dev/null +++ b/app/controllers/komponent/styleguide_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Komponent + class StyleguideController < ::ApplicationController + layout 'komponent' + rescue_from ActionView::MissingTemplate, with: :missing_template + + def index; end + + def show + @component = Komponent::Component.find(params[:id]) + end + + private + + def missing_template + render 'komponent/styleguide/missing_template', status: :not_found + end + end +end diff --git a/app/views/komponent/styleguide/index.html.erb b/app/views/komponent/styleguide/index.html.erb new file mode 100644 index 0000000..9c51264 --- /dev/null +++ b/app/views/komponent/styleguide/index.html.erb @@ -0,0 +1,9 @@ +

Styleguide

+ +<% if components.any? %> +

Select one of the components from the side to view its examples and documentation.

+<% else %> +

<Hint: You haven't created any component yet +

You can generate your first component by running:

+
rails generate component component-name
+<% end %> diff --git a/app/views/komponent/styleguide/missing_template.html.erb b/app/views/komponent/styleguide/missing_template.html.erb new file mode 100644 index 0000000..43e9315 --- /dev/null +++ b/app/views/komponent/styleguide/missing_template.html.erb @@ -0,0 +1,3 @@ +

Examples missing

+ +

Please create <%= @component.path %>/_examples.html.<%= Rails.application.config.app_generators.rails[:template_engine] || :erb %> file.

diff --git a/app/views/komponent/styleguide/show.html.erb b/app/views/komponent/styleguide/show.html.erb new file mode 100644 index 0000000..716aad5 --- /dev/null +++ b/app/views/komponent/styleguide/show.html.erb @@ -0,0 +1 @@ +<%= render partial: @component.examples_view %> diff --git a/app/views/layouts/komponent.html.erb b/app/views/layouts/komponent.html.erb new file mode 100644 index 0000000..972c1c8 --- /dev/null +++ b/app/views/layouts/komponent.html.erb @@ -0,0 +1,17 @@ + + + + Styleguide + <%= javascript_pack_tag 'komponent' %> + <%= stylesheet_pack_tag 'komponent', media: 'all' %> + <%= csrf_meta_tags %> + + + <%= c 'komponent/header' %> + <%= c 'komponent/sidebar' %> + <%= c 'komponent/container' do %> + <%= yield %> + <% end %> + <%= c 'komponent/footer' %> + + diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..7293246 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Komponent::Engine.routes.draw do + resources :styleguide, only: %i[index show] +end diff --git a/features/component_generator.feature b/features/component_generator.feature index cc59f3f..47c0013 100644 --- a/features/component_generator.feature +++ b/features/component_generator.feature @@ -11,6 +11,7 @@ Feature: Component generator | awesome_button/awesome_button.css | | awesome_button/awesome_button.js | | awesome_button/awesome_button_component.rb | + | awesome_button/_examples.html.erb | And the file named "index.js" should contain: """ import "components/awesome_button/awesome_button"; @@ -30,6 +31,7 @@ Feature: Component generator | admin/sub_admin/awesome_button/admin_sub_admin_awesome_button.css | | admin/sub_admin/awesome_button/admin_sub_admin_awesome_button.js | | admin/sub_admin/awesome_button/admin_sub_admin_awesome_button_component.rb | + | admin/sub_admin/awesome_button/_examples.html.erb | And the file named "index.js" should contain: """ import "components/admin"; @@ -117,6 +119,7 @@ Feature: Component generator When I run `rails generate component AwesomeButton` And I cd to "frontend/components/awesome_button" Then a file named "_awesome_button.html.erb" should exist + And a file named "_examples.html.erb" should exist Scenario: Generate component with custom template engine defined to `haml` Given a file named "config/initializers/custom_configuration.rb" with: @@ -126,6 +129,7 @@ Feature: Component generator When I run `rails generate component AwesomeButton` And I cd to "frontend/components/awesome_button" Then a file named "_awesome_button.html.haml" should exist + And a file named "_examples.html.haml" should exist Scenario: Generate component with custom template engine defined to `slim` Given a file named "config/initializers/custom_configuration.rb" with: @@ -135,6 +139,7 @@ Feature: Component generator When I run `rails generate component AwesomeButton` And I cd to "frontend/components/awesome_button" Then a file named "_awesome_button.html.slim" should exist + And a file named "_examples.html.slim" should exist Scenario: Generate component with custom stylesheet engine defined to `scss` Given a file named "config/initializers/custom_configuration.rb" with: diff --git a/lib/generators/component/component_generator.rb b/lib/generators/component/component_generator.rb index b2aaaca..c28db85 100644 --- a/lib/generators/component/component_generator.rb +++ b/lib/generators/component/component_generator.rb @@ -34,6 +34,10 @@ def create_locale_files end end + def create_examples_view_file + template "examples.html.#{template_engine}.erb", component_path + "_examples.html.#{template_engine}" + end + def import_to_packs root_path = default_path base_path = root_path + "components" diff --git a/lib/generators/component/templates/examples.html.erb.erb b/lib/generators/component/templates/examples.html.erb.erb new file mode 100644 index 0000000..54c8aa3 --- /dev/null +++ b/lib/generators/component/templates/examples.html.erb.erb @@ -0,0 +1,3 @@ +

<%= @component_name %>

+ +<%%= cdoc "<%= component_name %>" %> diff --git a/lib/generators/component/templates/examples.html.haml.erb b/lib/generators/component/templates/examples.html.haml.erb new file mode 100644 index 0000000..851eec6 --- /dev/null +++ b/lib/generators/component/templates/examples.html.haml.erb @@ -0,0 +1,3 @@ +%h1 <%= @component_name %> + += cdoc "<%= component_name %>" diff --git a/lib/generators/component/templates/examples.html.slim.erb b/lib/generators/component/templates/examples.html.slim.erb new file mode 100644 index 0000000..050dc7b --- /dev/null +++ b/lib/generators/component/templates/examples.html.slim.erb @@ -0,0 +1,3 @@ +h1 <%= @component_name %> + += cdoc "<%= component_name %>" diff --git a/lib/generators/komponent/examples_generator.rb b/lib/generators/komponent/examples_generator.rb new file mode 100644 index 0000000..bef5c02 --- /dev/null +++ b/lib/generators/komponent/examples_generator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'komponent/component' +require File.expand_path('../utils', __FILE__) + +module Komponent + module Generators + class ExamplesGenerator < Rails::Generators::Base + include Utils + + source_root File.expand_path('../../component/templates', __FILE__) + + def create_examples_files + Komponent::Component.all.each do |name, component| + create_examples_view_file(name) + end + end + + protected + + def split_name(name) + name.split(/[:,::,\/]/).reject(&:blank?).map(&:underscore) + end + + def component_path(component_name) + path_parts = [default_path, 'components', *split_name(component_name)] + + Pathname.new(path_parts.join('/')) + end + + private + + def create_examples_view_file(component_name) + @component_name = split_name(component_name).last.underscore + + template "examples.html.#{template_engine}.erb", component_path(component_name) + "_examples.html.#{template_engine}" + end + end + end +end diff --git a/lib/generators/komponent/install_generator.rb b/lib/generators/komponent/install_generator.rb index 4e21b5c..9dd5646 100644 --- a/lib/generators/komponent/install_generator.rb +++ b/lib/generators/komponent/install_generator.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true +require File.expand_path('../utils', __FILE__) module Komponent module Generators class InstallGenerator < Rails::Generators::Base + include Utils + class_option :stimulus, type: :boolean, default: false def check_webpacker_dependency @@ -80,14 +83,6 @@ def application_pack_path komponent_root_directory.join("packs", "application.js") end - def komponent_root_directory - default_path - end - - def components_directory - Rails.root.join(komponent_root_directory, "components") - end - def webpacker_configuration_file Rails.root.join("config", "webpacker.yml") end @@ -96,10 +91,6 @@ def webpacker_default_structure Rails.root.join("app", "javascript") end - def komponent_already_installed? - File.directory?(relative_path_from_rails) - end - def dependencies_not_met_error_message "Seems you don't have webpacker installed in your project. Please install webpacker, and follow instructions at https://github.com/rails/webpacker" end @@ -108,31 +99,6 @@ def stimulus? return options[:stimulus] if options[:stimulus] komponent_configuration[:stimulus] end - - def default_path - rails_configuration.komponent.root - end - - def relative_path_from_rails - default_path.relative_path_from(Rails.root) - end - - private - - def komponent_configuration - { - stimulus: nil, - locale: nil, - }.merge(app_generators.komponent) - end - - def rails_configuration - Rails.application.config - end - - def app_generators - rails_configuration.app_generators - end end end end diff --git a/lib/generators/komponent/styleguide_generator.rb b/lib/generators/komponent/styleguide_generator.rb new file mode 100644 index 0000000..4e050c0 --- /dev/null +++ b/lib/generators/komponent/styleguide_generator.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require File.expand_path('../utils', __FILE__) + +module Komponent + module Generators + class StyleguideGenerator < Rails::Generators::Base + include Utils + + source_root File.expand_path('../templates/styleguide', __FILE__) + + def check_komponent_dependency + unless komponent_already_installed? + raise Thor::Error, dependencies_not_met_error_message + end + end + + def copy_styleguide_components + directory 'components', components_directory.join('komponent') + end + + def create_komponent_pack + template 'packs/komponent.js', komponent_pack_path + end + + def append_to_application_routes + route 'mount Komponent::Engine => \'/\' if Rails.env.development?' + end + + protected + + def komponent_pack_path + komponent_root_directory.join('packs', 'komponent.js') + end + + def dependencies_not_met_error_message + 'Seems you don\'t have komponent installed in your project. Please install komponent, and follow instructions at https://github.com/komposable/komponent' + end + end + end +end diff --git a/lib/generators/komponent/templates/styleguide/components/container/_komponent_container.html.erb b/lib/generators/komponent/templates/styleguide/components/container/_komponent_container.html.erb new file mode 100644 index 0000000..31dc85d --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/container/_komponent_container.html.erb @@ -0,0 +1,3 @@ +
+ <%= yield %> +
diff --git a/lib/generators/komponent/templates/styleguide/components/container/komponent_container.css b/lib/generators/komponent/templates/styleguide/components/container/komponent_container.css new file mode 100644 index 0000000..a41d825 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/container/komponent_container.css @@ -0,0 +1,17 @@ +/* stylelint-disable value-list-comma-newline-after */ + +.komponent-container { + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + margin: 40px 60px 0 300px; + + .komponent-code { + background-color: #333; + color: #fff; + margin: 10px 0; + padding: 10px; + } +} + +/* stylelint-enable */ diff --git a/lib/generators/komponent/templates/styleguide/components/container/komponent_container.js b/lib/generators/komponent/templates/styleguide/components/container/komponent_container.js new file mode 100644 index 0000000..70b1a82 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/container/komponent_container.js @@ -0,0 +1 @@ +import "./komponent_container.css"; diff --git a/lib/generators/komponent/templates/styleguide/components/container/komponent_container_component.rb b/lib/generators/komponent/templates/styleguide/components/container/komponent_container_component.rb new file mode 100644 index 0000000..3a2362d --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/container/komponent_container_component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module KomponentContainerComponent + extend ComponentHelper +end diff --git a/lib/generators/komponent/templates/styleguide/components/footer/_komponent_footer.html.erb b/lib/generators/komponent/templates/styleguide/components/footer/_komponent_footer.html.erb new file mode 100644 index 0000000..c3a1fae --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/footer/_komponent_footer.html.erb @@ -0,0 +1,3 @@ + diff --git a/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer.css b/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer.css new file mode 100644 index 0000000..95a03e4 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer.css @@ -0,0 +1,27 @@ +/* stylelint-disable value-list-comma-newline-after */ + +.komponent-footer { + bottom: 30px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 14px; + position: fixed; + right: 30px; + text-align: center; + + &, + a { + color: #999; + } + + a { + text-decoration: none; + + &:hover, + &:focus { + color: #0038ea; + } + } +} + +/* stylelint-enable */ diff --git a/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer.js b/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer.js new file mode 100644 index 0000000..c1d61e6 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer.js @@ -0,0 +1 @@ +import "./komponent_footer.css"; diff --git a/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer_component.rb b/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer_component.rb new file mode 100644 index 0000000..9f64ef8 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/footer/komponent_footer_component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module KomponentFooterComponent + extend ComponentHelper +end diff --git a/lib/generators/komponent/templates/styleguide/components/header/_komponent_header.html.erb b/lib/generators/komponent/templates/styleguide/components/header/_komponent_header.html.erb new file mode 100644 index 0000000..e4feb12 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/header/_komponent_header.html.erb @@ -0,0 +1,3 @@ +
+ +
diff --git a/lib/generators/komponent/templates/styleguide/components/header/komponent_header.css b/lib/generators/komponent/templates/styleguide/components/header/komponent_header.css new file mode 100644 index 0000000..ac9f194 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/header/komponent_header.css @@ -0,0 +1,15 @@ +/* stylelint-disable value-list-comma-newline-after */ + +.komponent-header { + align-items: center; + background-color: #0038ea; + color: white; + display: flex; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + height: 60px; + padding: 0 20px; +} + +/* stylelint-enable */ diff --git a/lib/generators/komponent/templates/styleguide/components/header/komponent_header.js b/lib/generators/komponent/templates/styleguide/components/header/komponent_header.js new file mode 100644 index 0000000..b013393 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/header/komponent_header.js @@ -0,0 +1 @@ +import "./komponent_header.css"; diff --git a/lib/generators/komponent/templates/styleguide/components/header/komponent_header_component.rb b/lib/generators/komponent/templates/styleguide/components/header/komponent_header_component.rb new file mode 100644 index 0000000..7d3d6b6 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/header/komponent_header_component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module KomponentHeaderComponent + extend ComponentHelper +end diff --git a/lib/generators/komponent/templates/styleguide/components/index.js b/lib/generators/komponent/templates/styleguide/components/index.js new file mode 100644 index 0000000..878e312 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/index.js @@ -0,0 +1,4 @@ +import "components/komponent/container/komponent_container"; +import "components/komponent/footer/komponent_footer"; +import "components/komponent/header/komponent_header"; +import "components/komponent/sidebar/komponent_sidebar"; diff --git a/lib/generators/komponent/templates/styleguide/components/sidebar/_komponent_sidebar.html.erb b/lib/generators/komponent/templates/styleguide/components/sidebar/_komponent_sidebar.html.erb new file mode 100644 index 0000000..195ade3 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/sidebar/_komponent_sidebar.html.erb @@ -0,0 +1,10 @@ +
+
Components
+ +
diff --git a/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar.css b/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar.css new file mode 100644 index 0000000..3ac9e59 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar.css @@ -0,0 +1,43 @@ +/* stylelint-disable value-list-comma-newline-after */ + +.komponent-sidebar { + background-color: #dbe1f3; + bottom: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + left: 0; + overflow: auto; + padding: 20px; + position: absolute; + top: 60px; + width: 240px; + + &-title { + color: #0038ea; + font-size: 14px; + font-weight: bold; + letter-spacing: 1px; + margin: 0 0 20px; + text-transform: uppercase; + } + + &-items { + font-size: 14px; + letter-spacing: 0; + line-height: 1.25; + list-style: none; + margin: 0 0 30px; + padding: 0; + } + + &-item { + margin: 0 0 5px; + } + + a { + color: #333; + text-decoration: none; + } +} + +/* stylelint-enable */ diff --git a/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar.js b/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar.js new file mode 100644 index 0000000..2b3af41 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar.js @@ -0,0 +1 @@ +import "./komponent_sidebar.css"; diff --git a/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar_component.rb b/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar_component.rb new file mode 100644 index 0000000..3395063 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/components/sidebar/komponent_sidebar_component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module KomponentSidebarComponent + extend ComponentHelper +end diff --git a/lib/generators/komponent/templates/styleguide/packs/komponent.js b/lib/generators/komponent/templates/styleguide/packs/komponent.js new file mode 100644 index 0000000..c31f602 --- /dev/null +++ b/lib/generators/komponent/templates/styleguide/packs/komponent.js @@ -0,0 +1,2 @@ +import 'components'; +import 'components/komponent'; diff --git a/lib/generators/komponent/utils.rb b/lib/generators/komponent/utils.rb new file mode 100644 index 0000000..f70c267 --- /dev/null +++ b/lib/generators/komponent/utils.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Komponent + module Generators + module Utils + protected + + def rails_configuration + Rails.application.config + end + + def app_generators + rails_configuration.app_generators + end + + def template_engine + app_generators.rails[:template_engine] || :erb + end + + def default_path + rails_configuration.komponent.root + end + + def relative_path_from_rails + default_path.relative_path_from(Rails.root) + end + + def komponent_root_directory + default_path + end + + def komponent_configuration + { + stimulus: nil, + locale: nil, + examples: nil, + }.merge(app_generators.komponent) + end + + def components_directory + Rails.root.join(komponent_root_directory, 'components') + end + + def komponent_already_installed? + File.directory?(relative_path_from_rails) + end + end + end +end diff --git a/lib/komponent.rb b/lib/komponent.rb index 3d737e6..965007c 100644 --- a/lib/komponent.rb +++ b/lib/komponent.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require "komponent/version" +require 'komponent/version' module Komponent - require 'komponent/railtie' if defined?(Rails) + require 'komponent/engine' if defined?(Rails) end diff --git a/lib/komponent/component.rb b/lib/komponent/component.rb new file mode 100644 index 0000000..8cd991e --- /dev/null +++ b/lib/komponent/component.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Komponent + class Component + class << self + def all + component_dirs = components_root.join('*/**/') + + components = {} + + # TODO: list only components directories + # use ComponentPathResolver? + Dir.glob(component_dirs).sort.each do |component_dir| + component_path = Pathname.new(component_dir).relative_path_from(components_root).to_s + + next unless File.exist?(components_root.join(component_path) + .join("#{File.basename(component_path)}_component.rb")) + + components[component_path] = Component.new(component_path) + end + + components + end + + def find(id) + components = all + + raise Exception, "Component with ID=#{id} not found" unless components.keys.include? id + components.fetch(id) + end + + def components_root + @components_root ||= Rails.application.config.komponent.root.join('components') + end + end + + attr_reader :id + + def initialize(id) + @id = id + end + + def title + id.titleize + end + + def path + Komponent::ComponentPathResolver.new.resolve(@id) + end + + def examples_view + "components/#{id}/examples" + end + end +end diff --git a/lib/komponent/component_path_resolver.rb b/lib/komponent/component_path_resolver.rb index 2ae69d9..4a1010a 100644 --- a/lib/komponent/component_path_resolver.rb +++ b/lib/komponent/component_path_resolver.rb @@ -17,6 +17,12 @@ def resolve(component_name) root_path.join(*component_name) end + def component_paths + komponent_configuration.component_paths.map do |path| + Pathname.new(path) + end + end + protected def path_has_component?(path, component_name) @@ -27,12 +33,6 @@ def path_has_component?(path, component_name) File.exist?(file_name) end - def component_paths - komponent_configuration.component_paths.map do |path| - Pathname.new(path) - end - end - def komponent_configuration app_configuration.komponent end diff --git a/lib/komponent/railtie.rb b/lib/komponent/engine.rb similarity index 65% rename from lib/komponent/railtie.rb rename to lib/komponent/engine.rb index 2e77e43..a965e25 100644 --- a/lib/komponent/railtie.rb +++ b/lib/komponent/engine.rb @@ -4,10 +4,13 @@ require 'komponent/component_helper' require 'komponent/component_path_resolver' require 'komponent/component_renderer' +require 'komponent/component' require 'komponent/translation' module Komponent - class Railtie < Rails::Railtie + class Engine < Rails::Engine + isolate_namespace Komponent + rake_tasks do load 'komponent/rails/tasks/komponent.rake' end @@ -26,6 +29,9 @@ class Railtie < Rails::Railtie app.config.komponent.component_paths.prepend( app.config.komponent.root.join("components") ) + app.config.komponent.component_paths.append( + Komponent::Engine.root.join('frontend/components') + ) ActiveSupport.on_load :action_view do require 'komponent/komponent_helper' @@ -38,5 +44,15 @@ class Railtie < Rails::Railtie ) end end + + initializer "my_engine.action_dispatch" do |app| + ActiveSupport.on_load :action_controller do + ActionController::Base.prepend_view_path Komponent::Engine.root.join("frontend") + end + end + + initializer 'komponent.autoload', before: :set_autoload_paths do |app| + app.config.autoload_paths << Komponent::Engine.root.join('frontend') + end end end diff --git a/lib/komponent/komponent_helper.rb b/lib/komponent/komponent_helper.rb index 1b9d7df..bc8a564 100644 --- a/lib/komponent/komponent_helper.rb +++ b/lib/komponent/komponent_helper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'komponent/component' + module KomponentHelper def component(component_name, locals = {}, options = {}, &block) captured_block = proc { |args| capture(args, &block) } if block_given? @@ -14,4 +16,30 @@ def component(component_name, locals = {}, options = {}, &block) ) end alias :c :component + + def components + Komponent::Component.all + end + + def component_with_doc(component_name, locals = {}, options = {}, &block) + captured_output = component(component_name, locals, options, &block) + + captured_doc = capture do + content_tag :pre, class: "komponent-code" do + content_tag :code do + "= component \"#{component_name}\"" + (locals.present? ? ", #{pretty_locals(locals)}" : "") + end + end + end + + captured_output + captured_doc + end + alias :cdoc :component_with_doc + + private + + def pretty_locals(locals) + return nil if locals.blank? + JSON.pretty_generate(locals).gsub(/^(\s+)"(\w+)":/, "\\1\\2:") + end end diff --git a/test/komponent/component_test.rb b/test/komponent/component_test.rb new file mode 100644 index 0000000..93efc4a --- /dev/null +++ b/test/komponent/component_test.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ComponentTest < ActionView::TestCase + def test_all_returns_components + all = Komponent::Component.all + + assert all.is_a?(Hash) + assert_equal all.count, 9 + assert all["foo"].is_a?(Komponent::Component) + end + + def test_find_raises_exception_when_missing + assert_raise Exception do + Komponent::Component.find("missing") + end + end + + def test_find_returns_component + assert \ + Komponent::Component.find("foo").is_a?(Komponent::Component) + assert_equal \ + "foo", + Komponent::Component.find("foo").id + end + + def test_returns_title + assert_equal \ + "Foo Bar", + Komponent::Component.new("foo_bar").title + end + + def test_returns_examples_view + assert_equal \ + "components/foo/examples", + Komponent::Component.new("foo").examples_view + end + + def test_returns_path + path = Komponent::Component.new("foo").path + + assert path.is_a?(Pathname) + assert path.to_s.include?("frontend/components/foo") + end +end diff --git a/test/komponent/komponent_helper_test.rb b/test/komponent/komponent_helper_test.rb index b67dc3f..f9ac73e 100644 --- a/test/komponent/komponent_helper_test.rb +++ b/test/komponent/komponent_helper_test.rb @@ -77,4 +77,31 @@ def test_helper_supports_content_for_across_components %(
Greetings from Ping
), component('pong').chomp end + + def test_helper_lists_components + assert_equal( + [ + 'all', + 'bar', + 'foo', + 'foo_bar', + 'hello', + 'ping', + 'pong', + 'required', + 'world', + ], + components.keys + ) + end + + def test_helper_renders_with_doc + assert_equal \ + %(
🌎 😎
+
= component "all", {
+  world: "🌎",
+  sunglasses: "😎"
+}
), + component_with_doc('all', world: "🌎", sunglasses: "😎").chomp + end end