phlex 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of phlex might be problematic. Click here for more details.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.rubocop.yml +21 -5
  4. data/Gemfile +26 -12
  5. data/Procfile.dev +3 -0
  6. data/README.md +1 -1
  7. data/Rakefile +3 -5
  8. data/SECURITY.md +1 -1
  9. data/bench.rb +7 -0
  10. data/config/sus.rb +15 -0
  11. data/docs/assets/application.css +6 -0
  12. data/docs/build.rb +17 -10
  13. data/docs/components/callout.rb +1 -1
  14. data/docs/components/code_block.rb +2 -2
  15. data/docs/components/code_span.rb +9 -0
  16. data/docs/components/example.rb +5 -5
  17. data/docs/components/heading.rb +2 -2
  18. data/docs/components/layout.rb +62 -17
  19. data/docs/components/markdown.rb +14 -15
  20. data/docs/components/nav/item.rb +33 -0
  21. data/docs/components/nav.rb +6 -0
  22. data/docs/components/tabs/tab.rb +4 -2
  23. data/docs/components/tabs.rb +1 -1
  24. data/docs/components/title.rb +2 -2
  25. data/docs/page_builder.rb +3 -0
  26. data/docs/pages/application_page.rb +1 -1
  27. data/docs/pages/helpers.rb +97 -0
  28. data/docs/pages/index.rb +6 -17
  29. data/docs/pages/library/collections.rb +83 -0
  30. data/docs/pages/rails/getting_started.rb +53 -0
  31. data/docs/pages/rails/helpers.rb +55 -0
  32. data/docs/pages/rails/layouts.rb +61 -0
  33. data/docs/pages/rails/migrating.rb +37 -0
  34. data/docs/pages/rails/rendering_views.rb +35 -0
  35. data/docs/pages/templates.rb +53 -151
  36. data/docs/pages/testing/capybara.rb +48 -0
  37. data/docs/pages/testing/getting_started.rb +44 -0
  38. data/docs/pages/testing/nokogiri.rb +83 -0
  39. data/docs/pages/testing/rails.rb +17 -0
  40. data/docs/pages/translations.rb +81 -0
  41. data/docs/pages/views.rb +87 -78
  42. data/fixtures/compiler_test_helpers.rb +19 -0
  43. data/fixtures/content.rb +60 -0
  44. data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
  45. data/fixtures/dummy/app/components/comment_component.rb +8 -0
  46. data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
  47. data/fixtures/dummy/app/components/reaction_component.rb +7 -0
  48. data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
  49. data/fixtures/dummy/app/views/application_view.rb +8 -0
  50. data/fixtures/dummy/app/views/articles/form.rb +3 -1
  51. data/fixtures/dummy/app/views/card.rb +4 -2
  52. data/fixtures/dummy/app/views/comments/comment.rb +25 -0
  53. data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
  54. data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
  55. data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
  56. data/fixtures/dummy/app/views/heading.rb +1 -1
  57. data/fixtures/layout.rb +5 -5
  58. data/fixtures/page.rb +18 -24
  59. data/fixtures/{test_helper.rb → rails_helper.rb} +3 -7
  60. data/fixtures/standard_element.rb +87 -0
  61. data/fixtures/view_helper.rb +1 -1
  62. data/fixtures/void_element.rb +31 -0
  63. data/lib/generators/phlex/collection/USAGE +8 -0
  64. data/lib/generators/phlex/collection/collection_generator.rb +13 -0
  65. data/lib/generators/phlex/collection/templates/collection.rb.erb +16 -0
  66. data/lib/generators/phlex/controller/USAGE +10 -0
  67. data/lib/generators/phlex/controller/controller_generator.rb +54 -0
  68. data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
  69. data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
  70. data/lib/generators/phlex/layout/USAGE +8 -0
  71. data/lib/generators/phlex/layout/layout_generator.rb +13 -0
  72. data/lib/generators/phlex/layout/templates/layout.rb.erb +31 -0
  73. data/lib/generators/phlex/page/USAGE +8 -0
  74. data/lib/generators/phlex/page/page_generator.rb +13 -0
  75. data/lib/generators/phlex/page/templates/page.rb.erb +13 -0
  76. data/lib/generators/phlex/table/USAGE +8 -0
  77. data/lib/generators/phlex/table/table_generator.rb +14 -0
  78. data/lib/generators/phlex/table/templates/table.rb.erb +11 -0
  79. data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
  80. data/lib/generators/phlex/view/view_generator.rb +9 -1
  81. data/lib/install/phlex.rb +10 -1
  82. data/lib/phlex/block.rb +2 -4
  83. data/lib/phlex/buffered.rb +6 -8
  84. data/lib/phlex/callable.rb +9 -0
  85. data/lib/phlex/collection.rb +33 -0
  86. data/lib/phlex/compiler/elements.rb +49 -0
  87. data/lib/phlex/compiler/generators/content.rb +103 -0
  88. data/lib/phlex/compiler/generators/element.rb +61 -0
  89. data/lib/phlex/compiler/nodes/base.rb +19 -0
  90. data/lib/phlex/compiler/nodes/call.rb +9 -0
  91. data/lib/phlex/compiler/nodes/command.rb +13 -0
  92. data/lib/phlex/compiler/nodes/fcall.rb +18 -0
  93. data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
  94. data/lib/phlex/compiler/nodes/vcall.rb +9 -0
  95. data/lib/phlex/compiler/optimizer.rb +66 -0
  96. data/lib/phlex/compiler/visitors/base.rb +15 -0
  97. data/lib/phlex/compiler/visitors/file.rb +23 -11
  98. data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
  99. data/lib/phlex/compiler/visitors/statements.rb +36 -0
  100. data/lib/phlex/compiler/visitors/view.rb +19 -0
  101. data/lib/phlex/compiler/visitors/view_method.rb +59 -0
  102. data/lib/phlex/compiler.rb +23 -3
  103. data/lib/phlex/elements.rb +57 -0
  104. data/lib/phlex/engine.rb +0 -3
  105. data/lib/phlex/helpers.rb +59 -0
  106. data/lib/phlex/html/callbacks.rb +11 -0
  107. data/lib/phlex/html.rb +209 -54
  108. data/lib/phlex/markdown.rb +76 -0
  109. data/lib/phlex/rails/form.rb +67 -0
  110. data/lib/phlex/rails/helpers.rb +118 -0
  111. data/lib/phlex/rails/layout.rb +15 -0
  112. data/lib/phlex/rails.rb +10 -0
  113. data/lib/phlex/renderable.rb +9 -3
  114. data/lib/phlex/table.rb +104 -0
  115. data/lib/phlex/testing/capybara.rb +25 -0
  116. data/lib/phlex/testing/nokogiri.rb +24 -0
  117. data/lib/phlex/testing/rails.rb +19 -0
  118. data/lib/phlex/testing/view_helper.rb +15 -0
  119. data/lib/phlex/translation.rb +23 -0
  120. data/lib/phlex/turbo/frame.rb +21 -0
  121. data/lib/phlex/turbo/stream.rb +18 -0
  122. data/lib/phlex/version.rb +1 -1
  123. data/lib/phlex.rb +22 -24
  124. metadata +112 -15
  125. data/.rspec +0 -1
  126. data/fixtures/compilation/vcall.rb +0 -38
  127. data/lib/phlex/compiler/generators/standard_element.rb +0 -30
  128. data/lib/phlex/compiler/generators/void_element.rb +0 -29
  129. data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
  130. data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
  131. data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
  132. data/lib/phlex/compiler/visitors/component.rb +0 -28
  133. data/lib/phlex/compiler/visitors/component_method.rb +0 -28
  134. data/lib/phlex/rails/tag_helpers.rb +0 -29
  135. data/lib/phlex/view.rb +0 -223
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff6c239c6500dab71edf80613da891503a2aa5a4f11ee468d988145376fd3d1c
4
- data.tar.gz: d1379dba2b5f9c16d908a82a199036ca9a0463867b869b9757a8d6bd35caa2b3
3
+ metadata.gz: 6db4c22ebfaa77371b1d2bc5faaa48bc93056c3c57e2233fc19a9191850f52f9
4
+ data.tar.gz: b6fc151034426d7ccede55fdd2c73a1244fdf47c440cdb9c59850853cc2cec30
5
5
  SHA512:
6
- metadata.gz: 0eb7517edbd6d38babdd25ecb48265a3c997a3d0e909ebb51bab7a7c68c601316ea61c4abb1b0e9ccc5df3cd69386991ee3ecd722e8af29d59a8c3ad6ec5ad6f
7
- data.tar.gz: e2027c09419b22a321799dfc3becba3ecf9dc7ca947174b522b57e9da9528145e4e485abe473af6180d01ea39d2fe4ef1837448f989a9bb241573a64c92e6a91
6
+ metadata.gz: 888e31c18af9de0230970228e1ab20aab4d08d6b2236187ec06fbe2690813032c3af1e9a675bc737016c3c51f3c522be9a4e87bf868882778ff02482ca4c742f
7
+ data.tar.gz: 6a999b53c9e54a1159ee0ce34e135d3e5f4c3e73a6207f3d5dbec4f208387c165a83e66cc39f26d582d8ff5fe1d32bb7667b3cfae33a3b16d8a69179dcab4131
data/.editorconfig CHANGED
@@ -3,3 +3,11 @@ root = true
3
3
  [*]
4
4
  indent_style = tab
5
5
  indent_size = 2
6
+
7
+ [*.yml]
8
+ indent_style = space
9
+ indent_size = 2
10
+
11
+ [*.erb]
12
+ indent_style = space
13
+ indent_size = 2
data/.rubocop.yml CHANGED
@@ -1,10 +1,26 @@
1
- inherit_from: "https://www.goodcop.style"
1
+ inherit_from:
2
+ - "https://www.goodcop.style/rubocop.yml"
3
+ - "https://www.goodcop.style/tabs.yml"
2
4
 
3
5
  AllCops:
4
6
  TargetRubyVersion: 2.7
5
7
 
6
- Layout/IndentationStyle:
7
- EnforcedStyle: tabs
8
+ Style/PercentLiteralDelimiters:
9
+ Enabled: false
8
10
 
9
- Layout/IndentationWidth:
10
- Width: 1
11
+ Layout/CaseIndentation:
12
+ Enabled: false
13
+
14
+ Style/StringConcatenation:
15
+ Enabled: false
16
+
17
+
18
+
19
+ Security/Eval:
20
+ Enabled: false
21
+
22
+ Style/MethodCallWithoutArgsParentheses:
23
+ Enabled: false
24
+
25
+ Style/MixinUsage:
26
+ Enabled: false
data/Gemfile CHANGED
@@ -5,18 +5,32 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
6
  gemspec
7
7
 
8
- gem "rake"
9
-
10
- gem "sus", group: [:test]
11
- gem "rails", group: [:test]
12
- gem "rouge", group: [:docs]
13
- gem "listen", group: [:docs]
14
- gem "webrick", group: [:docs]
15
- gem "zeitwerk", group: [:docs]
16
- gem "redcarpet", group: [:docs]
17
- gem "combustion", group: [:test]
18
8
  gem "benchmark-ips"
19
- gem "htmlbeautifier", group: [:docs]
20
9
  gem "benchmark-memory"
21
- gem "rubocop", require: false, github: "joeldrapper/rubocop", branch: "rubocop-user-agent"
10
+ gem "capybara"
11
+ gem "rails"
12
+ gem "rubocop"
13
+ gem "solargraph"
14
+ gem "sus"
22
15
  gem "syntax_suggest"
16
+ gem "zeitwerk"
17
+
18
+ group :test do
19
+ gem "i18n"
20
+ gem "memory_profiler"
21
+ gem "covered"
22
+ end
23
+
24
+ group :rails do
25
+ gem "combustion"
26
+ end
27
+
28
+ group :docs do
29
+ gem "filewatcher"
30
+ gem "htmlbeautifier"
31
+ gem "redcarpet"
32
+ gem "commonmarker", "~> 0.23"
33
+ gem "webrick"
34
+ gem "rouge"
35
+ gem "kramdown"
36
+ end
data/Procfile.dev ADDED
@@ -0,0 +1,3 @@
1
+ server: ruby -run -e httpd docs/dist
2
+ docs: bundle exec docs/build.rb --watch
3
+ tailwind: npx tailwindcss -i ./docs/assets/application.css -o ./docs/dist/application.css --watch
data/README.md CHANGED
@@ -20,7 +20,7 @@ Maintaining a library is a lot of work. If your company benefits from this work
20
20
 
21
21
  ### Security 🚨
22
22
 
23
- If you’ve found a potential security issue, please email [joel+security@drapper.me](mailto:joel+security@drapper.me).
23
+ If you’ve found a potential security issue, please email [security@phlex.fun](mailto:security@phlex.fun).
24
24
 
25
25
  ### Thanks 🙏
26
26
 
data/Rakefile CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
 
5
- begin
6
- require "rspec/core/rake_task"
7
- RSpec::Core::RakeTask.new(:spec)
5
+ task :sus do
6
+ sh "bundle exec sus"
8
7
  end
9
-
10
- task default: :spec
8
+ task default: :sus
data/SECURITY.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  ## Reporting a Vulnerability
4
4
 
5
- If you found a possible security vulnerability in Phlex, please email joel+security@drapper.me.
5
+ If you found a possible security vulnerability in Phlex, please email security@phlex.fun.
data/bench.rb CHANGED
@@ -9,6 +9,13 @@ require_relative "fixtures/layout"
9
9
 
10
10
  puts RUBY_DESCRIPTION
11
11
 
12
+ a = Example::Page.new.call
13
+ # Example::Page.compile
14
+ # Example::LayoutComponent.compile
15
+ b = Example::Page.new.call
16
+
17
+ raise unless a == b
18
+
12
19
  Benchmark.ips do |x|
13
20
  x.report("Page") { Example::Page.new.call }
14
21
  end
data/config/sus.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "covered/sus"
4
+ include Covered::Sus
5
+
6
+ require "phlex"
7
+ require "bundler"
8
+ require "view_component"
9
+
10
+ Bundler.require :test
11
+
12
+ require_relative "../fixtures/view_helper"
13
+ require_relative "../fixtures/compiler_test_helpers"
14
+
15
+ Zeitwerk::Loader.eager_load_all
@@ -24,3 +24,9 @@ pre { tab-size: 2; }
24
24
  p {
25
25
  @apply my-5
26
26
  }
27
+
28
+ .prose ol, .prose ul {
29
+ list-style: revert;
30
+ margin: 0 0 0 1rem;
31
+ padding: 0 0 0 1rem;
32
+ }
data/docs/build.rb CHANGED
@@ -1,22 +1,29 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ $stdout.sync = true
5
+
4
6
  require "phlex"
5
7
  require "bundler"
6
8
  require "fileutils"
9
+ require "i18n"
7
10
 
8
11
  Bundler.require :docs
9
12
 
10
- Zeitwerk::Loader.new.tap do |loader|
11
- loader.push_dir(__dir__)
12
- loader.ignore(__FILE__)
13
- loader.setup
14
- loader.eager_load
15
- end
16
-
17
- FileUtils.mkdir_p("#{__dir__}/dist")
18
- FileUtils.cp_r("#{__dir__}/assets", "#{__dir__}/dist")
13
+ loader = Zeitwerk::Loader.new
14
+ loader.push_dir(__dir__)
15
+ loader.ignore(__FILE__)
16
+ loader.inflector.inflect("rspec" => "RSpec")
17
+ loader.enable_reloading
18
+ loader.setup
19
+ loader.eager_load
19
20
 
20
21
  PageBuilder.build_all
21
22
 
22
- system "npx tailwindcss -i ./docs/assets/application.css -o ./docs/dist/application.css"
23
+ if ARGV.include? "--watch"
24
+ Filewatcher.new("#{__dir__}/**/*rb").watch do |_changes|
25
+ loader.reload
26
+ loader.eager_load
27
+ PageBuilder.build_all
28
+ end
29
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Callout < Phlex::View
4
+ class Callout < Phlex::HTML
5
5
  def template(&block)
6
6
  div(class: "rounded bg-orange-50 text-sm p-5 border border-orange-100", &block)
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class CodeBlock < Phlex::View
4
+ class CodeBlock < Phlex::HTML
5
5
  FORMATTER = Rouge::Formatters::HTML.new
6
6
 
7
7
  def initialize(code, syntax:)
@@ -11,7 +11,7 @@ module Components
11
11
 
12
12
  def template
13
13
  pre(class: "highlight p-5 whitespace-pre-wrap bg-stone-50") {
14
- raw FORMATTER.format(
14
+ unsafe_raw FORMATTER.format(
15
15
  lexer.lex(@code)
16
16
  )
17
17
  }
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class CodeSpan < Phlex::HTML
5
+ def template(&block)
6
+ code(class: "bg-stone-50 inline-block font-medium rounded border px-1 -mt-1", &block)
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Example < Phlex::View
4
+ class Example < Phlex::HTML
5
5
  def initialize
6
6
  @sandbox = Module.new
7
7
  end
@@ -13,18 +13,18 @@ module Components
13
13
  end
14
14
  end
15
15
 
16
- def tab(name, code)
16
+ def tab(name, code, syntax: :ruby)
17
17
  @t.tab(name) do
18
- render CodeBlock.new(code, syntax: :ruby)
18
+ render CodeBlock.new(code, syntax: syntax)
19
19
  end
20
20
 
21
- @sandbox.class_eval(code)
21
+ @sandbox.class_eval(code) if syntax == :ruby
22
22
  end
23
23
 
24
24
  def execute(code)
25
25
  output = @sandbox.class_eval(code)
26
26
 
27
- @t.tab("HTML Output") do
27
+ @t.tab("👀 Output") do
28
28
  render CodeBlock.new(HtmlBeautifier.beautify(output), syntax: :html)
29
29
  end
30
30
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Heading < Phlex::View
4
+ class Heading < Phlex::HTML
5
5
  def template(&block)
6
- h2(class: "text-xl font-bold mt-10 mb-5", &block)
6
+ h2(class: "text-2xl font-semibold mt-10 mb-5", &block)
7
7
  end
8
8
  end
9
9
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Layout < Phlex::View
4
+ class Layout < Phlex::HTML
5
5
  register_element :style
6
6
 
7
7
  def initialize(title:)
@@ -14,30 +14,75 @@ module Components
14
14
  html do
15
15
  head do
16
16
  meta charset: "utf-8"
17
- title @title
17
+ meta name: "viewport", content: "width=device-width, initial-scale=1"
18
+ title { @title }
18
19
  link href: "/application.css", rel: "stylesheet"
19
- style { raw Rouge::Theme.find("github").render(scope: ".highlight") }
20
+ style { unsafe_raw Rouge::Theme.find("github").render(scope: ".highlight") }
20
21
  end
21
22
 
22
- body class: "p-12" do
23
- div class: "max-w-screen-lg mx-auto grid grid-cols-4 gap-10" do
24
- header class: "col-span-1" do
25
- a(href: "/", class: "block") { img src: "/assets/logo.png", width: "150" }
26
-
27
- nav do
28
- ul do
29
- li { a "Introduction", href: "/" }
30
- li { a "Templates", href: "/templates" }
31
- li { a "Views", href: "/views" }
32
- li { a "Rails integration", href: "/rails-integration" }
33
- li { a "Source code", href: "https://github.com/joeldrapper/phlex" }
23
+ body class: "text-stone-700" do
24
+ div class: "flex flex-col" do
25
+ header class: "border-b py-4 px-4 lg:px-10 flex justify-between items-center sticky top-0 left-0 bg-white z-50" do
26
+ div class: "flex flex-row items-center gap-2" do
27
+ label for: "nav-toggle", class: "cursor-pointer lg:hidden" do
28
+ unsafe_raw '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8"> <path fill-rule="evenodd" d="M3 6.75A.75.75 0 013.75 6h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 6.75zM3 12a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 12zm0 5.25a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" /> </svg>'
29
+ end
30
+
31
+ a(href: "/", class: "block") { img src: "/assets/logo.png", width: "100" }
32
+ end
33
+
34
+ nav(class: "text-stone-500 font-medium") do
35
+ ul(class: "flex space-x-8") do
36
+ li { a(href: "https://github.com/sponsors/joeldrapper") { "💖️ Sponsor" } }
37
+ li { a(href: "https://github.com/joeldrapper/phlex") { "GitHub" } }
34
38
  end
35
39
  end
36
40
  end
37
41
 
38
- main(class: "col-span-3", &block)
42
+ div do
43
+ div class: "flex flex-row" do
44
+ input type: "checkbox", id: "nav-toggle", class: "flex-0 peer hidden"
45
+ label for: "nav-toggle", class: "top-0 bottom-0 left-0 right-0 bg-gray-900 opacity-0 transition-all peer-checked:fixed peer-checked:z-30 peer-checked:block peer-checked:opacity-50 lg:hidden lg:peer-checked:hidden"
46
+ nav class: "fixed lg:relative w-3/4 border-r-2 border-gray-100 lg:border-0 lg:w-1/4 text-lg lg:text-base h-full z-40 px-10 py-5 -left-full transition-all peer-checked:left-0 lg:left-0 bg-white" do
47
+ h2(class: "text-lg font-semibold pt-5") { "Guide" }
48
+
49
+ ul do
50
+ render Nav::Item.new("Introduction", to: Pages::Index, active_page: @_parent)
51
+ render Nav::Item.new("Views", to: Pages::Views, active_page: @_parent)
52
+ render Nav::Item.new("Templates", to: Pages::Templates, active_page: @_parent)
53
+ render Nav::Item.new("Helpers", to: Pages::Helpers, active_page: @_parent)
54
+ render Nav::Item.new("Translations", to: Pages::Translations, active_page: @_parent)
55
+ end
56
+
57
+ h2(class: "text-lg font-semibold pt-5") { "Testing" }
58
+
59
+ ul do
60
+ render Nav::Item.new("Getting Started", to: Pages::Testing::GettingStarted, active_page: @_parent)
61
+ render Nav::Item.new("Nokogiri", to: Pages::Testing::Nokogiri, active_page: @_parent)
62
+ render Nav::Item.new("Capybara", to: Pages::Testing::Capybara, active_page: @_parent)
63
+ render Nav::Item.new("Rails", to: Pages::Testing::Rails, active_page: @_parent)
64
+ end
65
+
66
+ h2(class: "text-lg font-semibold pt-5") { "Rails" }
39
67
 
40
- footer class: "text-sm text-right col-span-4 py-10"
68
+ ul do
69
+ render Nav::Item.new("Getting started", to: Pages::Rails::GettingStarted, active_page: @_parent)
70
+ render Nav::Item.new("Rendering views", to: Pages::Rails::RenderingViews, active_page: @_parent)
71
+ render Nav::Item.new("Layouts", to: Pages::Rails::Layouts, active_page: @_parent)
72
+ render Nav::Item.new("Helpers", to: Pages::Rails::Helpers, active_page: @_parent)
73
+ render Nav::Item.new("Migrating to Phlex", to: Pages::Rails::Migrating, active_page: @_parent)
74
+ end
75
+ end
76
+
77
+ main class: "w-full lg:w-3/4 px-6 lg:px-20 py-5 border-0 lg:border-l-2 border-gray-100" do
78
+ div(class: "max-w-full lg:max-w-prose prose", &block)
79
+ end
80
+ end
81
+
82
+ footer class: "border-t p-20 flex justify-center text-stone-500 text-lg font-medium" do
83
+ a(href: "https://github.com/sponsors/joeldrapper") { "Sponsor this project 💖" }
84
+ end
85
+ end
41
86
  end
42
87
  end
43
88
  end
@@ -1,26 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Markdown < Phlex::View
5
- class Render < Redcarpet::Render::HTML
6
- def header(text, level)
7
- case level
8
- when 1
9
- Title.new.call { text }
10
- else
11
- Heading.new.call { text }
12
- end
13
- end
4
+ class Markdown < Phlex::Markdown
5
+ def code(&content)
6
+ render CodeSpan.new, &content
14
7
  end
15
8
 
16
- MARKDOWN = Redcarpet::Markdown.new(Render.new, autolink: true, tables: true)
9
+ def code_block(code, language:)
10
+ render CodeBlock.new(code.gsub(/(?:^|\G) {4}/m, " "), syntax: language)
11
+ end
12
+
13
+ def h1(&content)
14
+ render Title.new, &content
15
+ end
17
16
 
18
- def initialize(content)
19
- @content = content
17
+ def h2(&content)
18
+ render Heading.new, &content
20
19
  end
21
20
 
22
- def template
23
- raw MARKDOWN.render(@content)
21
+ def a(**attributes, &content)
22
+ super(class: "font-bold text-red-600 underline underline-offset-4", **attributes, &content)
24
23
  end
25
24
  end
26
25
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Nav::Item < Phlex::HTML
5
+ def initialize(text, to:, active_page:)
6
+ @text = text
7
+ @to = to
8
+ @active_page = active_page
9
+ end
10
+
11
+ def template
12
+ li do
13
+ a(**link_classes, href: "/#{link}") { @text }
14
+ end
15
+ end
16
+
17
+ def link_classes
18
+ classes("pb-1 block font-medium text-stone-500", active?: "text-red-600 font-bold")
19
+ end
20
+
21
+ def link
22
+ path == "index" ? "" : path
23
+ end
24
+
25
+ def path
26
+ @to.name.split("::")[1..].map { _1.gsub(/(.)([A-Z])/, '\1-\2') }.map(&:downcase).join("/")
27
+ end
28
+
29
+ def active?
30
+ @active_page.instance_of?(@to)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Nav < Phlex::HTML
5
+ end
6
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Components
4
4
  class Tabs
5
- class Tab < Phlex::View
5
+ class Tab < Phlex::HTML
6
6
  def initialize(name:, checked:)
7
7
  @name = name
8
8
  @checked = checked
@@ -11,7 +11,9 @@ module Components
11
11
  def template(&block)
12
12
  input class: "opacity-0 fixed peer", type: "radio", name: @_parent.unique_identifier, id: unique_identifier, checked: @checked
13
13
 
14
- label @name, id: "#{unique_identifier}-label", for: unique_identifier, role: "tab", aria_controls: "#{unique_identifier}-panel", class: "order-1 py-2 px-5 bg-white text-sm border border-b-0 border-l-0 font-medium first-of-type:border-l first-of-type:rounded-tl last-of-type:rounded-tr before:absolute before:pointer-events-none before:w-full before:ring before:h-full before:left-0 before:top-0 before:hidden before:rounded peer-focus:before:block cursor-pointer"
14
+ label id: "#{unique_identifier}-label", for: unique_identifier, role: "tab", aria_controls: "#{unique_identifier}-panel", class: "order-1 py-2 px-5 bg-white text-sm border border-b-0 border-l-0 font-medium first-of-type:border-l first-of-type:rounded-tl last-of-type:rounded-tr before:absolute before:pointer-events-none before:w-full before:ring before:h-full before:left-0 before:top-0 before:hidden before:rounded peer-focus:before:block cursor-pointer" do
15
+ @name
16
+ end
15
17
 
16
18
  div id: "#{unique_identifier}-panel", role: "tabpanel", aria_labelledby: "#{unique_identifier}-label", class: "tab hidden order-2 w-full border rounded-b rounded-tr overflow-hidden" do
17
19
  @_parent.instance_exec(&block)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Tabs < Phlex::View
4
+ class Tabs < Phlex::HTML
5
5
  def initialize
6
6
  @index = 1
7
7
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Title < Phlex::View
4
+ class Title < Phlex::HTML
5
5
  def template(&block)
6
- h1(class: "text-2xl font-bold my-5", &block)
6
+ h1(class: "text-3xl font-semibold my-5", &block)
7
7
  end
8
8
  end
9
9
  end
data/docs/page_builder.rb CHANGED
@@ -4,6 +4,8 @@ class PageBuilder
4
4
  ROOT = Pages::ApplicationPage
5
5
 
6
6
  def self.build_all
7
+ FileUtils.mkdir_p("#{__dir__}/dist")
8
+ FileUtils.cp_r("#{__dir__}/assets", "#{__dir__}/dist")
7
9
  ROOT.subclasses.each { |page| new(page).call }
8
10
  end
9
11
 
@@ -12,6 +14,7 @@ class PageBuilder
12
14
  end
13
15
 
14
16
  def call
17
+ puts "Building #{@page.name}"
15
18
  FileUtils.mkdir_p(directory)
16
19
  File.write(file, @page.new.call)
17
20
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pages
4
- class ApplicationPage < Phlex::View
4
+ class ApplicationPage < Phlex::HTML
5
5
  include ::Components
6
6
  end
7
7
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ class Helpers < ApplicationPage
5
+ def template
6
+ render Layout.new(title: "Helpers") do
7
+ render Markdown.new(<<~MD)
8
+ # Helpers
9
+
10
+ ## Conditional tokens and classes
11
+
12
+ The `tokens` method helps you define conditional HTML attribute tokens (such as CSS classes). It accepts a splat of tokens that should always be output as well as optional keyword arguments for conditional tokens.
13
+
14
+ The keyword arguments allow you to specify under which conditions certain tokens are applicable. The keys are the conditions and the values are the tokens. Conditions can be Procs which are evaluated, or Symbols that map to an instance method. The `:active?` Symbol, for example, maps to the `active?` instance method.
15
+
16
+ Here we have a `Link` view that produces an `<a>` tag with the CSS class `nav-item`. If the link is _active_, we also apply the CSS class `nav-item-active`.
17
+ MD
18
+
19
+ render Example.new do |e|
20
+ e.tab "link.rb", <<~RUBY
21
+ class Link < Phlex::HTML
22
+ def initialize(text, to:, active:)
23
+ @text = text
24
+ @to = to
25
+ @active = active
26
+ end
27
+
28
+ def template
29
+ a(href: @to, class: tokens("nav-item",
30
+ active?: "nav-item-active")) { @text }
31
+ end
32
+
33
+ private
34
+
35
+ def active? = @active
36
+ end
37
+ RUBY
38
+
39
+ e.tab "example.rb", <<~RUBY
40
+ class Example < Phlex::HTML
41
+ def template
42
+ nav do
43
+ ul do
44
+ li { render Link.new("Home", to: "/", active: true) }
45
+ li { render Link.new("About", to: "/about", active: false) }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ RUBY
51
+
52
+ e.execute "Example.new.call"
53
+ end
54
+
55
+ render Markdown.new(<<~MD)
56
+ You can also use the `classes` helper method to create a token list of classes. Since this method returns a hash, e.g. `{ class: "your CSS classes here" }`, you can destructure it into a `class:` keyword argument using the `**` prefix operator.
57
+ MD
58
+
59
+ render Example.new do |e|
60
+ e.tab "link.rb", <<~RUBY
61
+ class Link < Phlex::HTML
62
+ def initialize(text, to:, active:)
63
+ @text = text
64
+ @to = to
65
+ @active = active
66
+ end
67
+
68
+ def template
69
+ a(href: @to, **classes("nav-item",
70
+ active?: "nav-item-active")) { @text }
71
+ end
72
+
73
+ private
74
+
75
+ def active? = @active
76
+ end
77
+ RUBY
78
+
79
+ e.tab "example.rb", <<~RUBY
80
+ class Example < Phlex::HTML
81
+ def template
82
+ nav do
83
+ ul do
84
+ li { render Link.new("Home", to: "/", active: true) }
85
+ li { render Link.new("About", to: "/about", active: false) }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ RUBY
91
+
92
+ e.execute "Example.new.call"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end