phlex 0.1.0 → 0.2.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +4 -0
  4. data/Gemfile +15 -3
  5. data/README.md +14 -30
  6. data/Rakefile +4 -6
  7. data/bench.rb +14 -0
  8. data/config.ru +9 -0
  9. data/docs/assets/application.css +24 -0
  10. data/docs/assets/logo.png +0 -0
  11. data/docs/build.rb +22 -0
  12. data/docs/components/callout.rb +9 -0
  13. data/docs/components/code_block.rb +26 -0
  14. data/docs/components/example.rb +32 -0
  15. data/docs/components/heading.rb +9 -0
  16. data/docs/components/layout.rb +45 -0
  17. data/docs/components/markdown.rb +26 -0
  18. data/docs/components/tabs/tab.rb +26 -0
  19. data/docs/components/tabs.rb +30 -0
  20. data/docs/components/title.rb +9 -0
  21. data/docs/page_builder.rb +36 -0
  22. data/docs/pages/application_page.rb +7 -0
  23. data/docs/pages/components.rb +175 -0
  24. data/docs/pages/index.rb +40 -0
  25. data/docs/pages/templates.rb +242 -0
  26. data/fixtures/component_helper.rb +16 -0
  27. data/fixtures/dummy/app/assets/config/manifest.js +0 -0
  28. data/fixtures/dummy/app/controllers/articles_controller.rb +4 -0
  29. data/fixtures/dummy/app/views/articles/form.rb +13 -0
  30. data/fixtures/dummy/app/views/articles/index.html.erb +11 -0
  31. data/fixtures/dummy/app/views/articles/new.html.erb +1 -0
  32. data/fixtures/dummy/app/views/card.rb +13 -0
  33. data/fixtures/dummy/config/database.yml +3 -0
  34. data/fixtures/dummy/config/routes.rb +5 -0
  35. data/fixtures/dummy/config/storage.yml +3 -0
  36. data/fixtures/dummy/db/schema.rb +6 -0
  37. data/fixtures/dummy/log/.gitignore +1 -0
  38. data/fixtures/dummy/public/favicon.ico +0 -0
  39. data/fixtures/layout.rb +31 -0
  40. data/fixtures/page.rb +41 -0
  41. data/fixtures/test_helper.rb +13 -0
  42. data/lib/generators/phlex/component/USAGE +8 -0
  43. data/lib/generators/phlex/component/component_generator.rb +13 -0
  44. data/lib/generators/phlex/component/templates/component.rb.erb +8 -0
  45. data/lib/overrides/symbol/name.rb +5 -0
  46. data/lib/phlex/block.rb +18 -0
  47. data/lib/phlex/buffered.rb +19 -0
  48. data/lib/phlex/component.rb +169 -23
  49. data/lib/phlex/configuration.rb +7 -0
  50. data/lib/phlex/html.rb +65 -0
  51. data/lib/phlex/rails/tag_helpers.rb +29 -0
  52. data/lib/phlex/rails.rb +8 -0
  53. data/lib/phlex/renderable.rb +35 -0
  54. data/lib/phlex/version.rb +1 -1
  55. data/lib/phlex.rb +25 -15
  56. data/package-lock.json +1195 -0
  57. data/package.json +5 -0
  58. data/phlex_logo.png +0 -0
  59. data/tailwind.config.js +7 -0
  60. metadata +64 -21
  61. data/Gemfile.lock +0 -32
  62. data/lib/phlex/callable.rb +0 -11
  63. data/lib/phlex/context.rb +0 -39
  64. data/lib/phlex/node.rb +0 -13
  65. data/lib/phlex/page.rb +0 -3
  66. data/lib/phlex/tag/standard_element.rb +0 -15
  67. data/lib/phlex/tag/void_element.rb +0 -9
  68. data/lib/phlex/tag.rb +0 -43
  69. data/lib/phlex/tags.rb +0 -108
  70. data/lib/phlex/text.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 899a4ffd2a666431474757fa3732dd3abbd5b0bc747db29df6044ba152aa47e4
4
- data.tar.gz: 9ebbc4b7409dfd827eee58ed41d1a5951c55c99e106a3ce8a5ea7156bff6ecec
3
+ metadata.gz: 2eefd3a74f2982d981f510fbf51664a489edc5ca40bbfddc3be564fd8150646b
4
+ data.tar.gz: 8c5c168a439d41ab0c9020a6ebe6b43585cfdda7fc7135782b90df45ee3a5b56
5
5
  SHA512:
6
- metadata.gz: 86640f197c38cda2906b649efa40dd208215708d221ff7ce1d57f6ed565ad0971dceda4b88a975b71d4f1e60a5b38796ed86f93e1b3e54d6e58c9cb11957c77e
7
- data.tar.gz: d21c4ccc138554170fb1056ada38645fcb45dd34b74864ff1cc03e473c977f299f410a740ad7776a010026ad647ea13e97f7e4d1c14b58de806872c1f1a5e123
6
+ metadata.gz: 3cf930ae8e3aa73ddb806de461d0bad1a80a747c38c52c1737631d78a6fe92054f2323d8d9b5f943bf5b028619096232880a3cce6c05ae3abbacfa17f9df31c7
7
+ data.tar.gz: 477bfe4a00d3c304671730c2fb1be83804fc7452ce5cfd10f29e1883aa1b2c91e69fe3ca96fa08a9c0c06126292dafa248c1bc655f911ec7c5d8cf17a7395d49
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ inherit_from: "https://www.goodcop.style"
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.7
data/Gemfile CHANGED
@@ -1,10 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source "https://rubygems.org"
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
4
5
 
5
- # Specify your gem's dependencies in phlex.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
8
+ gem "rake"
9
9
 
10
- gem "minitest", "~> 5.0"
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
+ gem "benchmark-ips"
19
+ gem "htmlbeautifier", group: [:docs]
20
+ gem "benchmark-memory"
21
+ gem "rubocop", require: false, github: "joeldrapper/rubocop", branch: "rubocop-user-agent"
22
+ gem "syntax_suggest"
data/README.md CHANGED
@@ -1,43 +1,27 @@
1
- # Phlex
1
+ <a href="https://www.phlex.fun"><img alt="Phlex logo" src="phlex_logo.png" width="180" /></a>
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/phlex`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Phlex is a framework that lets you compose web views in pure Ruby.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ### Documentation 📗
6
6
 
7
- ## Installation
7
+ Documentation can be found at [www.phlex.fun](https://www.phlex.fun).
8
8
 
9
- Add this line to your application's Gemfile:
9
+ ### Support
10
10
 
11
- ```ruby
12
- gem 'phlex'
13
- ```
11
+ If you run into any trouble, please [start a discussion](https://github.com/joeldrapper/phlex/discussions/new), or [open an issue](https://github.com/joeldrapper/phlex/issues/new) if you think you’ve found a bug.
14
12
 
15
- And then execute:
13
+ ### Community 🙌
16
14
 
17
- $ bundle install
15
+ Everyone interacting in Phlex codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/joeldrapper/phlex/blob/main/CODE_OF_CONDUCT.md).
18
16
 
19
- Or install it yourself as:
17
+ ### Sponsorship 💖
20
18
 
21
- $ gem install phlex
19
+ Maintaining a library like this is a lot of work. Phlex is being actively developed and maintained by **[Joel Drapper](https://github.com/sponsors/joeldrapper)**. If your company benefits from this work or is likely to benefit from it in the future, please consider sponsorship.
22
20
 
23
- ## Usage
21
+ ### Security 👮
24
22
 
25
- TODO: Write usage instructions here
23
+ If you’ve found a potential security issue, please email [joel+security@drapper.me](mailto:joel+security@drapper.me).
26
24
 
27
- ## Development
25
+ ### Thanks 🙏
28
26
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
-
33
- ## Contributing
34
-
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/phlex. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/phlex/blob/master/CODE_OF_CONDUCT.md).
36
-
37
- ## License
38
-
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
-
41
- ## Code of Conduct
42
-
43
- Everyone interacting in the Phlex project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/phlex/blob/master/CODE_OF_CONDUCT.md).
27
+ Thanks [Logology](https://www.logology.co) for sponsoring our logo.
data/Rakefile CHANGED
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "rake/testtask"
5
4
 
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
5
+ begin
6
+ require "rspec/core/rake_task"
7
+ RSpec::Core::RakeTask.new(:spec)
10
8
  end
11
9
 
12
- task default: :test
10
+ task default: :spec
data/bench.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "phlex"
5
+ require "benchmark/ips"
6
+
7
+ require_relative "fixtures/page"
8
+ require_relative "fixtures/layout"
9
+
10
+ puts RUBY_DESCRIPTION
11
+
12
+ Benchmark.ips do |x|
13
+ x.report("Page") { Example::Page.new.call }
14
+ end
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
@@ -0,0 +1,24 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ .tabs input[type="radio"]:checked + label {
6
+ background: #f8f8f8;
7
+ z-index: 1;
8
+ margin-bottom: -1px;
9
+ padding-bottom: 1px;
10
+ }
11
+
12
+ .tabs input[type="radio"]:focus + label {
13
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
14
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
15
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
16
+ }
17
+
18
+ .tabs input[type="radio"]:checked + label + .tab {
19
+ display: block;
20
+ }
21
+
22
+ p {
23
+ @apply my-5
24
+ }
Binary file
data/docs/build.rb ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "phlex"
5
+ require "bundler"
6
+ require "fileutils"
7
+
8
+ Bundler.require :docs
9
+
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")
19
+
20
+ PageBuilder.build_all
21
+
22
+ system "npx tailwindcss -i ./docs/assets/application.css -o ./docs/dist/application.css"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Callout < Phlex::Component
5
+ def template(&block)
6
+ div(class: "rounded bg-orange-50 text-sm p-5 border border-orange-100", &block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class CodeBlock < Phlex::Component
5
+ FORMATTER = Rouge::Formatters::HTML.new
6
+
7
+ def initialize(code, syntax:)
8
+ @code = code
9
+ @syntax = syntax
10
+ end
11
+
12
+ def template
13
+ pre(class: "highlight p-5 whitespace-pre-wrap bg-stone-50") {
14
+ raw FORMATTER.format(
15
+ lexer.lex(@code)
16
+ )
17
+ }
18
+ end
19
+
20
+ private
21
+
22
+ def lexer
23
+ Rouge::Lexer.find(@syntax)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Example < Phlex::Component
5
+ def initialize
6
+ @sandbox = Module.new
7
+ end
8
+
9
+ def template(&block)
10
+ render Tabs.new do |t|
11
+ @t = t
12
+ yield self
13
+ end
14
+ end
15
+
16
+ def tab(name, code)
17
+ @t.tab(name) do
18
+ render CodeBlock.new(code, syntax: :ruby)
19
+ end
20
+
21
+ @sandbox.instance_eval(code)
22
+ end
23
+
24
+ def execute(code)
25
+ output = @sandbox.instance_eval(code)
26
+
27
+ @t.tab("HTML Output") do
28
+ render CodeBlock.new(HtmlBeautifier.beautify(output), syntax: :html)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Heading < Phlex::Component
5
+ def template(&block)
6
+ h2(class: "text-xl font-bold mt-10 mb-5", &block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Layout < Phlex::Component
5
+ register_element :style
6
+
7
+ def initialize(title:)
8
+ @title = title
9
+ end
10
+
11
+ def template(&block)
12
+ doctype
13
+
14
+ html do
15
+ head do
16
+ meta charset: "utf-8"
17
+ title @title
18
+ link href: "/application.css", rel: "stylesheet"
19
+ style { raw Rouge::Theme.find("github").render(scope: ".highlight") }
20
+ end
21
+
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 "Components", href: "/components" }
32
+ li { a "Source code", href: "https://github.com/joeldrapper/phlex" }
33
+ end
34
+ end
35
+ end
36
+
37
+ main(class: "col-span-3", &block)
38
+
39
+ footer class: "text-sm text-right col-span-4 py-10"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Markdown < Phlex::Component
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
14
+ end
15
+
16
+ MARKDOWN = Redcarpet::Markdown.new(Render.new, autolink: true, tables: true)
17
+
18
+ def initialize(content)
19
+ @content = content
20
+ end
21
+
22
+ def template
23
+ raw MARKDOWN.render(@content)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Tabs
5
+ class Tab < Phlex::Component
6
+ def initialize(name:, checked:)
7
+ @name = name
8
+ @checked = checked
9
+ end
10
+
11
+ def template(&block)
12
+ input class: "opacity-0 fixed peer", type: "radio", name: @_parent.unique_identifier, id: unique_identifier, checked: @checked
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"
15
+
16
+ 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
+ @_parent.instance_exec(&block)
18
+ end
19
+ end
20
+
21
+ def unique_identifier
22
+ @unique_identifier ||= SecureRandom.hex
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Tabs < Phlex::Component
5
+ def initialize
6
+ @index = 1
7
+ end
8
+
9
+ def template(&block)
10
+ div class: "tabs flex flex-wrap relative my-5", role: "tablist" do
11
+ content(&block)
12
+ end
13
+ end
14
+
15
+ def tab(name, &block)
16
+ render(Tab.new(name: name, checked: first?), &block)
17
+ @index += 1
18
+ end
19
+
20
+ def unique_identifier
21
+ @unique_identifier ||= SecureRandom.hex
22
+ end
23
+
24
+ private
25
+
26
+ def first?
27
+ @index == 1
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Title < Phlex::Component
5
+ def template(&block)
6
+ h1(class: "text-2xl font-bold my-5", &block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PageBuilder
4
+ ROOT = Pages::ApplicationPage
5
+
6
+ def self.build_all
7
+ ROOT.subclasses.each { |page| new(page).call }
8
+ end
9
+
10
+ def initialize(page)
11
+ @page = page
12
+ end
13
+
14
+ def call
15
+ FileUtils.mkdir_p(directory)
16
+ File.write(file, @page.new.call)
17
+ end
18
+
19
+ private
20
+
21
+ def file
22
+ "#{directory}/index.html"
23
+ end
24
+
25
+ def directory
26
+ if path == "index"
27
+ "#{__dir__}/dist"
28
+ else
29
+ "#{__dir__}/dist/#{path}"
30
+ end
31
+ end
32
+
33
+ def path
34
+ @page.name.downcase.split("::")[1..].join("/")
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ class ApplicationPage < Phlex::Component
5
+ include ::Components
6
+ end
7
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ class Components < ApplicationPage
5
+ def template
6
+ render Layout.new(title: "Components in Phlex") do
7
+ render Markdown.new(<<~MD)
8
+ # Components
9
+
10
+ ## Yielding content
11
+
12
+ Your components can accept content as a block passed to the template method. You can capture the content block and pass it to the `content` method to yield it.
13
+ MD
14
+
15
+ render Example.new do |e|
16
+ e.tab "card.rb", <<~RUBY
17
+ class Card < Phlex::Component
18
+ def template(&)
19
+ article(class: "drop-shadow rounded p-5") {
20
+ h1 "Amazing content!"
21
+ content(&)
22
+ }
23
+ end
24
+ end
25
+ RUBY
26
+
27
+ e.execute "Card.new.call { 'Your content here.\n' }"
28
+ end
29
+
30
+ render Markdown.new(<<~MD)
31
+ ## Delegating content
32
+
33
+ Alternatively, you can pass the content down as an argument to another component or tag.
34
+ MD
35
+
36
+ render Example.new do |e|
37
+ e.tab "card.rb", <<~RUBY
38
+ class Card < Phlex::Component
39
+ def template(&)
40
+ article(class: "drop-shadow rounded p-5", &)
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ e.execute "Card.new.call { 'Your content here.' }"
46
+ end
47
+
48
+ render Markdown.new(<<~MD)
49
+ ## Nested components
50
+
51
+ Components can render other components and optionally pass them content as a block.
52
+ MD
53
+
54
+ render Example.new do |e|
55
+ e.tab "example.rb", <<~RUBY
56
+ class Example < Phlex::Component
57
+ def template
58
+ render Card.new do
59
+ h1 "Hello"
60
+ end
61
+ end
62
+ end
63
+ RUBY
64
+
65
+ e.tab "card.rb", <<~RUBY
66
+ class Card < Phlex::Component
67
+ def template(&)
68
+ article(class: "drop-shadow rounded p-5", &)
69
+ end
70
+ end
71
+ RUBY
72
+
73
+ e.execute "Example.new.call"
74
+ end
75
+
76
+ render Markdown.new(<<~MD)
77
+ If the block just wraps a string, the string is treated as _text content_.
78
+ MD
79
+
80
+ render Example.new do |e|
81
+ e.tab "example.rb", <<~RUBY
82
+ class Example < Phlex::Component
83
+ def template
84
+ render(Card.new) { "Hi" }
85
+ end
86
+ end
87
+ RUBY
88
+
89
+ e.tab "card.rb", <<~RUBY
90
+ class Card < Phlex::Component
91
+ def template(&)
92
+ article(class: "drop-shadow rounded p-5", &)
93
+ end
94
+ end
95
+ RUBY
96
+
97
+ e.execute "Example.new.call"
98
+ end
99
+
100
+ render Markdown.new(<<~MD)
101
+ ## Component attributes
102
+
103
+ Besides content, components can define attributes in an initializer, which can then be rendered in the template.
104
+ MD
105
+
106
+ render Example.new do |e|
107
+ e.tab "hello.rb", <<~RUBY
108
+ class Hello < Phlex::Component
109
+ def initialize(name:)
110
+ @name = name
111
+ end
112
+
113
+ def template
114
+ h1 "Hello \#{@name}!"
115
+ end
116
+ end
117
+ RUBY
118
+
119
+ e.tab "example.rb", <<~RUBY
120
+ class Example < Phlex::Component
121
+ def template
122
+ render Hello.new(name: "Joel")
123
+ end
124
+ end
125
+ RUBY
126
+
127
+ e.execute "Example.new.call"
128
+ end
129
+
130
+ render Markdown.new(<<~MD)
131
+ It’s usually a good idea to use instance variables directly rather than creating accessor methods for them. Otherwise it’s easy to run into naming conflicts. For example, your layout component might have the attribute `title`, to render into a `<title>` element in the document head. If you define `attr_accessor :title`, that would overwrite the `title` method for creating `<title>` elements.
132
+
133
+ ## Calculations with methods
134
+
135
+ Components are just Ruby classes, so you can perform calculations on component attributes by defining your own methods.
136
+ MD
137
+
138
+ render Example.new do |e|
139
+ e.tab "status.rb", <<~RUBY
140
+ class Status < Phlex::Component
141
+ def initialize(status:)
142
+ @status = status
143
+ end
144
+
145
+ def template
146
+ span status_emoji
147
+ end
148
+
149
+ private
150
+
151
+ def status_emoji
152
+ case @status
153
+ when :success
154
+ "✅"
155
+ when :failure
156
+ "❌"
157
+ end
158
+ end
159
+ end
160
+ RUBY
161
+
162
+ e.tab "example.rb", <<~RUBY
163
+ class Example < Phlex::Component
164
+ def template
165
+ render Status.new(status: :success)
166
+ end
167
+ end
168
+ RUBY
169
+
170
+ e.execute "Example.new.call"
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end