showcase-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ //= link showcase.js
2
+ //= link_tree ../builds/ .css
@@ -0,0 +1,28 @@
1
+ window.customElements.define("showcase-sample", class extends HTMLElement {
2
+ connectedCallback() {
3
+ this.events.forEach((name) => this.addEventListener(name, this.emit))
4
+ }
5
+
6
+ disconnectedCallback() {
7
+ this.events.forEach(this.removeEventListener)
8
+ }
9
+
10
+ emit(event) {
11
+ console.log({ originator: this, event })
12
+ this.relay(event)
13
+ }
14
+
15
+ relay({ type, detail }) {
16
+ const node = document.createElement("div")
17
+ node.innerHTML = JSON.stringify({ type, detail })
18
+ this.relayTarget?.appendChild(node)
19
+ }
20
+
21
+ get relayTarget() {
22
+ return this.querySelector("[data-showcase-sample-target='relay']")
23
+ }
24
+
25
+ get events() {
26
+ return JSON.parse(this.getAttribute("events")) || []
27
+ }
28
+ })
@@ -0,0 +1,3 @@
1
+ class Showcase::ApplicationController < ActionController::Base
2
+ layout "showcase"
3
+ end
@@ -0,0 +1,8 @@
1
+ class Showcase::PagesController < Showcase::ApplicationController
2
+ def index
3
+ end
4
+
5
+ def show
6
+ @page = Showcase::Path.new(params[:id]).page_for view_context
7
+ end
8
+ end
@@ -0,0 +1,51 @@
1
+ class Showcase::Page::Options
2
+ include Enumerable
3
+
4
+ def initialize(view_context)
5
+ @view_context = view_context
6
+ @options = []
7
+ @order = [:name, :required, :type, :default, :description]
8
+ end
9
+ delegate :empty?, to: :@options
10
+
11
+ def required(*arguments, **keywords, &block)
12
+ option(*arguments, **keywords, required: true, &block)
13
+ end
14
+
15
+ def optional(*arguments, **keywords, &block)
16
+ option(*arguments, **keywords, required: false, &block)
17
+ end
18
+
19
+ DEFAULT_OMITTED = Object.new
20
+
21
+ def option(name, description = nil, required: false, type: nil, default: DEFAULT_OMITTED, **options, &block)
22
+ description ||= @view_context.capture(&block).remove(/^\s+/).html_safe if block
23
+
24
+ type ||= type_from_default(default)
25
+ default = default == DEFAULT_OMITTED ? nil : default.inspect
26
+
27
+ @options << options.with_defaults(name: name, default: default, type: type, description: description, required: required)
28
+ end
29
+
30
+ def headers
31
+ @headers ||= @order | @options.flat_map(&:keys).uniq.sort
32
+ end
33
+
34
+ def each(&block)
35
+ @options.each do |option|
36
+ yield headers.index_with { option[_1] }
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def type_from_default(default)
43
+ case default
44
+ when DEFAULT_OMITTED then String
45
+ when true, false then "Boolean"
46
+ when nil then "nil"
47
+ else
48
+ default.class
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,43 @@
1
+ class Showcase::Page::Sample
2
+ attr_reader :name, :id, :events, :details
3
+ attr_reader :source
4
+
5
+ def initialize(view_context, name, description: nil, id: name.parameterize, events: nil, **details)
6
+ @view_context = view_context
7
+ @name, @id, @details = name, id, details
8
+ @events = Array(events)
9
+ description description if description
10
+ end
11
+
12
+ def description(content = nil, &block)
13
+ @description = content || @view_context.capture(&block) if content || block_given?
14
+ @description
15
+ end
16
+
17
+ def collect(&block)
18
+ preview(&block)
19
+ extract(&block)
20
+ end
21
+
22
+ def preview(&block)
23
+ block_given? ? @preview = @view_context.capture(&block) : @preview
24
+ end
25
+
26
+ def extract(&block)
27
+ @source = Showcase.sample_renderer.call \
28
+ extract_block_lines_via_matched_indentation_from(*block.source_location)
29
+ end
30
+
31
+ private
32
+
33
+ def extract_block_lines_via_matched_indentation_from(file, starting_index)
34
+ first_line, *lines = File.readlines(file).from(starting_index - 1)
35
+
36
+ indentation = first_line.match(/^\s+(?=<%)/).to_s
37
+ matcher = /^#{indentation}\S/
38
+
39
+ index = lines.index { _1.match?(matcher) }
40
+ lines.slice!(index..) if index
41
+ lines
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ class Showcase::Page
2
+ autoload :Sample, "showcase/page/sample"
3
+ autoload :Options, "showcase/page/options"
4
+
5
+ attr_reader :id, :badges, :samples
6
+
7
+ def initialize(view_context, id:, title: nil)
8
+ @view_context, @id = view_context, id
9
+ @badges, @samples = [], []
10
+ title title
11
+ end
12
+
13
+ def title(content = nil)
14
+ @title = content if content
15
+ @title
16
+ end
17
+
18
+ def description(content = nil, &block)
19
+ @description = content || @view_context.capture(&block) if content || block_given?
20
+ @description
21
+ end
22
+
23
+ def badge(*badges)
24
+ @badges.concat badges
25
+ end
26
+
27
+ def sample(name, **options, &block)
28
+ @samples << sample = Sample.new(@view_context, name, **options)
29
+
30
+ if block.arity.zero?
31
+ sample.collect(&block)
32
+ else
33
+ @view_context.capture(sample, &block)
34
+ end
35
+ end
36
+
37
+ def options
38
+ @options ||= Options.new(@view_context).tap { yield _1 if block_given? }
39
+ end
40
+
41
+ def render_template
42
+ @view_context.render template: id, prefixes: [Showcase.templates_path], locals: { showcase: self }
43
+ nil
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ class Showcase::Path
2
+ class Tree < Struct.new(:id, :paths)
3
+ def name
4
+ root? ? "Pages" : id
5
+ end
6
+
7
+ def root?
8
+ id == "."
9
+ end
10
+ end
11
+
12
+ def self.tree
13
+ all.group_by(&:dirname).map { Tree.new _1, _2 }
14
+ end
15
+
16
+ def self.all
17
+ Showcase.filenames.map { new _1 }.sort_by!(&:id)
18
+ end
19
+
20
+ attr_reader :id, :dirname, :basename
21
+
22
+ def initialize(path)
23
+ @id = path.split(".").first
24
+ @dirname, @basename = File.split(@id)
25
+ end
26
+
27
+ def page_for(view_context)
28
+ Showcase::Page.new(view_context, id: id, title: basename.titleize).tap(&:render_template)
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Showcase</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+
7
+ <%= stylesheet_link_tag "application", "showcase" %>
8
+ <%= javascript_include_tag "application", "showcase" %>
9
+ </head>
10
+
11
+ <body>
12
+ <%= render "showcase/root" %>
13
+ </body>
14
+ </html>
@@ -0,0 +1,13 @@
1
+ <main class="flex flex-wrap">
2
+ <section class="grid grid-cols-12 w-full">
3
+ <nav class="col-span-3 xl:col-span-2 py-5 h-full border-r">
4
+ <h1 class="font-black text-2xl py-2 pl-4 cursor-pointer"><%= link_to "Showcase", root_url %></h1>
5
+
6
+ <%= render partial: "showcase/path/tree", collection: Showcase::Path.tree %>
7
+ </nav>
8
+
9
+ <section class="col-span-9 xl:col-span-10 w-full min-h-screen p-12 pt-7">
10
+ <%= yield %>
11
+ </section>
12
+ </section>
13
+ </main>
@@ -0,0 +1,27 @@
1
+ <section class="w-full overflow-x-auto">
2
+ <h2 class="text-xl font-semibold mb-2">Options</h2>
3
+
4
+ <table class="table border-collapse border border-gray-200">
5
+ <thead>
6
+ <tr class="bg-slate-50">
7
+ <% options.headers.each do |header| %>
8
+ <th class="px-4 py-2"><%= header.to_s.humanize %></th>
9
+ <% end %>
10
+ </tr>
11
+ </thead>
12
+
13
+ <tbody>
14
+ <% options.each do |option| %>
15
+ <tr class="border-t border-gray-200">
16
+ <% option.each do |key, value| %>
17
+ <% if key == :required %>
18
+ <td class="p-4"><%= tag.input type: :checkbox, checked: value, disabled: true if value %></td>
19
+ <% else %>
20
+ <td class="p-4"><%= tag.pre value %></td>
21
+ <% end %>
22
+ <% end %>
23
+ </tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
27
+ </section>
@@ -0,0 +1,26 @@
1
+ <div class="space-y-8">
2
+ <section>
3
+ <% if page.title %>
4
+ <div class="flex items-center space-x-2 mb-2">
5
+ <h2 class="font-semibold text-3xl"><%= page.title %></h2>
6
+
7
+ <%# TODO: Insert default badge support here. %>
8
+ </div>
9
+ <% end %>
10
+
11
+ <% if page.description %>
12
+ <p class="font-normal text-base"><%= page.description %></p>
13
+ <% end %>
14
+ </section>
15
+
16
+ <% if page.samples.any? %>
17
+ <section>
18
+ <h2 class="font-semibold text-xl mb-2">Samples</h2>
19
+ <%= render partial: "showcase/pages/sample", collection: page.samples %>
20
+ </section>
21
+ <% end %>
22
+
23
+ <% if options = page.options.presence %>
24
+ <%= render "showcase/pages/options", options: options %>
25
+ <% end %>
26
+ </div>
@@ -0,0 +1,37 @@
1
+ <section class="mb-4 border border-gray-200 rounded-md">
2
+ <showcase-sample id="<%= sample.id %>" events="<%= sample.events %>">
3
+ <header class="bg-slate-100/50">
4
+ <h3 class="px-4 py-2 font-medium text-base md:text-lg leading-snug truncate"><%= link_to sample.name, "##{sample.id}" %></h3>
5
+
6
+ <% if sample.description %>
7
+ <p class="px-4 py-2 text-base"><%= sample.description %></p>
8
+ <% end %>
9
+ </header>
10
+
11
+ <% if sample.preview %>
12
+ <section class="px-4 py-2 border border-gray-200 border-0 border-b">
13
+ <%= sample.preview %>
14
+ </section>
15
+ <% end %>
16
+
17
+ <% if sample.source %>
18
+ <details>
19
+ <summary class="px-4 py-2 hover:bg-indigo-50 cursor-pointer select-none">View Source</summary>
20
+
21
+ <section class="px-4 py-2 relative overflow-hidden hover:select-all">
22
+ <%= sample.source %>
23
+ </section>
24
+ </details>
25
+ <% end %>
26
+
27
+ <% if sample.events.any? %>
28
+ <section class="px-4 py-2 font-small bg-slate-50">
29
+ <h4 class="mb-2 font-medium text-base">JavaScript Events</h4>
30
+
31
+ <div class="overflow-scroll max-h-20">
32
+ <pre data-showcase-sample-target="relay"></pre>
33
+ </div>
34
+ </section>
35
+ <% end %>
36
+ </showcase-sample>
37
+ </section>
@@ -0,0 +1,33 @@
1
+ <article class="space-y-4 font-normal text-base">
2
+ <p class="font-normal text-xl">👋 Welcome to <span class="italic">Showcase</span> — the UI Pattern Library!</p>
3
+
4
+ <section class="space-y-4">
5
+ <h2 class="font-semibold text-2xl">What is this thing?</h2>
6
+ <p>This resource is intended to be a hub for all-things UI for your developers, with the goal of <span class="italic font-semibold">sharing knowledge</span>, <span class="italic font-semibold">promoting reuse of existing code</span>, and <span class="italic font-semibold">ensuring consistency</span> across your application.</p>
7
+ </section>
8
+
9
+ <section class="space-y-4">
10
+ <h2 class="font-semibold text-2xl">How do I use it?</h2>
11
+ <p>On each page, you can add a series of usage examples for each component, style, or pattern. In the samples blocks, you'll see a live preview, as well as the source used to produce the example.</p>
12
+ <p>At the bottom of most pages, you will see a table, listing any options for configuring the partial or component.</p>
13
+ </section>
14
+
15
+ <section class="space-y-4">
16
+ <h2 class="font-semibold text-2xl">But I don't see the thing I need 🤔</h2>
17
+ <p>If you don't see the pattern or component here, that doesn't mean it doesn't aleady exist or can't be created with some combination of existing building blocks.</p>
18
+ </section>
19
+
20
+ <section class="space-y-4">
21
+ <h2 class="font-semibold text-2xl">I have questions, who do I reach out to?</h2>
22
+ <p>If you need help or have questions, please reach out to <%#= Showcase.links.support %>.</p>
23
+ </section>
24
+
25
+ <section class="space-y-4">
26
+ <h2 class="font-semibold text-2xl">Additional resources</h2>
27
+ <ul class="list-none">
28
+ <li>
29
+ <%= link_to "Bullet Train field partials documentation", "https://bullettrain.co/docs/field-partials", target: "_blank" %>
30
+ </li>
31
+ </ul>
32
+ </section>
33
+ </article>
@@ -0,0 +1 @@
1
+ <%= render "showcase/pages/page", page: @page %>
@@ -0,0 +1,9 @@
1
+ <details open class="flex flex-col">
2
+ <%= tag.summary tree.name.titleize, class: "hover:bg-indigo-50 font-medium text-black text-base py-2 pl-4 cursor-pointer" %>
3
+
4
+ <% tree.paths.each do |path| %>
5
+ <article class="hover:bg-indigo-50 <%= "bg-indigo-50" if path.id == params[:id] %>">
6
+ <%= link_to path.basename.titleize, page_path(path.id), class: "inline-block py-2 px-8 w-full" %>
7
+ </article>
8
+ <% end %>
9
+ </details>
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ Showcase::Engine.routes.draw do
2
+ get "pages/*id", to: "pages#show", as: :page
3
+ root to: "pages#index"
4
+ end
@@ -0,0 +1,22 @@
1
+ const defaultTheme = require('tailwindcss/defaultTheme')
2
+
3
+ module.exports = {
4
+ content: [
5
+ './public/*.html',
6
+ './app/helpers/**/*.rb',
7
+ './app/javascript/**/*.js',
8
+ './app/views/**/*.{erb,haml,html,slim}'
9
+ ],
10
+ theme: {
11
+ extend: {
12
+ fontFamily: {
13
+ sans: defaultTheme.fontFamily.sans,
14
+ },
15
+ },
16
+ },
17
+ plugins: [
18
+ require('@tailwindcss/forms'),
19
+ require('@tailwindcss/aspect-ratio'),
20
+ require('@tailwindcss/typography'),
21
+ ]
22
+ }
@@ -0,0 +1,9 @@
1
+ module Showcase
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Showcase
4
+
5
+ initializer "showcase.assets" do |app|
6
+ app.config.assets.precompile += %w[showcase_manifest]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Showcase
2
+ VERSION = "0.1.0"
3
+ end
data/lib/showcase.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "showcase/version"
2
+ require "showcase/engine"
3
+
4
+ module Showcase
5
+ singleton_class.attr_accessor :templates_directory_prefix, :sample_renderer
6
+ @templates_directory_prefix = ""
7
+ @sample_renderer = ->(lines) { lines.join }
8
+
9
+ def self.templates_path
10
+ @templates_path ||= File.join(templates_directory_prefix, "showcase/pages/templates").delete_prefix("/")
11
+ end
12
+
13
+ def self.filenames
14
+ Dir.glob("**/*.*", base: Rails.root.join("app/views", templates_path))
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :showcase do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: showcase-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Pence
8
+ - Kasper Timm Hansen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-12-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 6.1.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 6.1.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: tailwindcss-rails
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description:
43
+ email:
44
+ - hey@kaspth.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - MIT-LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - app/assets/builds/showcase.css
53
+ - app/assets/config/showcase_manifest.js
54
+ - app/assets/javascripts/showcase.js
55
+ - app/controllers/showcase/application_controller.rb
56
+ - app/controllers/showcase/pages_controller.rb
57
+ - app/models/showcase/page.rb
58
+ - app/models/showcase/page/options.rb
59
+ - app/models/showcase/page/sample.rb
60
+ - app/models/showcase/path.rb
61
+ - app/views/layouts/showcase.html.erb
62
+ - app/views/showcase/_root.html.erb
63
+ - app/views/showcase/pages/_options.html.erb
64
+ - app/views/showcase/pages/_page.html.erb
65
+ - app/views/showcase/pages/_sample.html.erb
66
+ - app/views/showcase/pages/index.html.erb
67
+ - app/views/showcase/pages/show.html.erb
68
+ - app/views/showcase/path/_tree.html.erb
69
+ - config/routes.rb
70
+ - config/tailwind.config.js
71
+ - lib/showcase.rb
72
+ - lib/showcase/engine.rb
73
+ - lib/showcase/version.rb
74
+ - lib/tasks/showcase_tasks.rake
75
+ homepage: https://github.com/kaspth/showcase
76
+ licenses:
77
+ - MIT
78
+ metadata:
79
+ homepage_uri: https://github.com/kaspth/showcase
80
+ source_code_uri: https://github.com/kaspth/showcase
81
+ changelog_uri: https://github.com/kaspth/showcase/blob/main/CHANGELOG.md
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.3.24
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Showcase helps you show off and document your partials, components, view
101
+ helpers and Stimulus controllers.
102
+ test_files: []