lookbook 0.2.2 → 0.3.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -0
  3. data/app/assets/lookbook/css/app.css +28 -0
  4. data/app/assets/lookbook/js/app.js +49 -24
  5. data/app/assets/lookbook/js/nav/leaf.js +20 -0
  6. data/app/assets/lookbook/js/nav/node.js +31 -0
  7. data/app/assets/lookbook/js/nav.js +36 -0
  8. data/app/assets/lookbook/js/page.js +33 -0
  9. data/app/assets/lookbook/js/utils/clipboard.js +13 -0
  10. data/app/assets/lookbook/js/utils/morph.js +16 -0
  11. data/app/assets/lookbook/js/{reloader.js → utils/reloader.js} +0 -0
  12. data/app/assets/lookbook/js/utils/screen.js +44 -0
  13. data/app/assets/lookbook/js/{size_observer.js → utils/size_observer.js} +1 -1
  14. data/app/assets/lookbook/js/{split.js → utils/split.js} +4 -4
  15. data/app/assets/lookbook/js/workbench/inspector.js +11 -0
  16. data/app/assets/lookbook/js/workbench/preview.js +39 -0
  17. data/app/assets/lookbook/js/workbench.js +14 -0
  18. data/app/controllers/lookbook/{browser_controller.rb → app_controller.rb} +58 -31
  19. data/app/helpers/lookbook/application_helper.rb +1 -1
  20. data/app/views/lookbook/_sidebar.html.erb +45 -0
  21. data/app/views/lookbook/_workbench.html.erb +12 -0
  22. data/app/views/lookbook/{browser → app}/error.html.erb +0 -0
  23. data/app/views/lookbook/app/index.html.erb +11 -0
  24. data/app/views/lookbook/{browser → app}/not_found.html.erb +1 -1
  25. data/app/views/lookbook/app/show.html.erb +1 -0
  26. data/app/views/lookbook/layouts/app.html.erb +16 -26
  27. data/app/views/lookbook/layouts/group.html.erb +6 -0
  28. data/app/views/lookbook/nav/_collection.html.erb +5 -0
  29. data/app/views/lookbook/nav/_node.html.erb +19 -0
  30. data/app/views/lookbook/nav/_preview.html.erb +29 -0
  31. data/app/views/lookbook/shared/_clipboard.html.erb +11 -0
  32. data/app/views/lookbook/shared/_header.html.erb +8 -0
  33. data/app/views/lookbook/workbench/_header.html.erb +37 -0
  34. data/app/views/lookbook/workbench/_inspector.html.erb +32 -0
  35. data/app/views/lookbook/workbench/_preview.html.erb +24 -0
  36. data/app/views/lookbook/workbench/inspector/_code.html.erb +3 -0
  37. data/app/views/lookbook/workbench/inspector/_notes.html.erb +24 -0
  38. data/app/views/lookbook/{partials → workbench}/inspector/_plain.html.erb +0 -0
  39. data/config/routes.rb +3 -3
  40. data/lib/lookbook/engine.rb +2 -2
  41. data/lib/lookbook/preview.rb +25 -3
  42. data/lib/lookbook/preview_controller.rb +6 -1
  43. data/lib/lookbook/preview_example.rb +3 -2
  44. data/lib/lookbook/preview_group.rb +37 -0
  45. data/lib/lookbook/taggable.rb +5 -1
  46. data/lib/lookbook/version.rb +1 -1
  47. data/lib/lookbook.rb +1 -0
  48. data/lib/tasks/lookbook_tasks.rake +1 -1
  49. data/public/lookbook-assets/app.css +256 -102
  50. data/public/lookbook-assets/app.js +964 -95
  51. data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
  52. metadata +52 -25
  53. data/app/assets/lookbook/js/preview.js +0 -76
  54. data/app/views/lookbook/browser/index.html.erb +0 -8
  55. data/app/views/lookbook/browser/show.html.erb +0 -33
  56. data/app/views/lookbook/partials/_preview.html.erb +0 -18
  57. data/app/views/lookbook/partials/_sidebar.html.erb +0 -21
  58. data/app/views/lookbook/partials/inspector/_code.html.erb +0 -1
  59. data/app/views/lookbook/partials/inspector/_inspector.html.erb +0 -43
  60. data/app/views/lookbook/partials/inspector/_prose.html.erb +0 -3
  61. data/app/views/lookbook/partials/nav/_collection.html.erb +0 -17
  62. data/app/views/lookbook/partials/nav/_label.html.erb +0 -13
  63. data/app/views/lookbook/partials/nav/_nav.html.erb +0 -27
  64. data/app/views/lookbook/partials/nav/_preview.html.erb +0 -48
  65. data/config/lookbook_cable.yml +0 -8
@@ -1,5 +1,7 @@
1
+ require "htmlbeautifier"
2
+
1
3
  module Lookbook
2
- class BrowserController < ActionController::Base
4
+ class AppController < ActionController::Base
3
5
  EXCEPTIONS = [ViewComponent::PreviewTemplateError, ViewComponent::ComponentError, ViewComponent::TemplateError, ActionView::Template::Error]
4
6
 
5
7
  protect_from_forgery with: :exception
@@ -11,43 +13,40 @@ module Lookbook
11
13
  before_action :find_preview, only: [:preview, :show]
12
14
  before_action :find_example, only: [:preview, :show]
13
15
  before_action :assign_nav, only: [:index, :show]
14
-
15
- def index
16
- end
16
+ before_action :initialize_inspector, only: [:show]
17
17
 
18
18
  def preview
19
19
  if @example
20
- render html: preview_output
20
+ render html: rendered_example
21
21
  else
22
- render "browser/not_found"
22
+ render "app/not_found"
23
23
  end
24
24
  end
25
25
 
26
26
  def show
27
27
  if @example
28
28
  begin
29
- @preview_srcdoc = preview_output.gsub("\"", "&quot;")
30
- @render_args = @preview.render_args(@example.name, params: preview_controller.params.permit!)
31
- @render_output = preview_controller.render_component_to_string(@preview, @example_name)
32
- @render_output_lang = Lookbook::Lang.find(:html)
33
- if using_preview_template?
34
- @source = @example.method_source
35
- @source_lang = @example.source_lang
36
- else
37
- @source = @example.template_source(@render_args[:template])
38
- @source_lang = @example.template_lang(@render_args[:template])
29
+ @rendered_example = rendered_example.gsub("\"", "&quot;")
30
+ (@example.type == :group ? @example.examples : [@example]).each do |example|
31
+ include_example_data(example)
39
32
  end
40
33
  assign_inspector
41
34
  rescue *EXCEPTIONS
42
- render "browser/error"
35
+ render "app/error"
43
36
  end
44
37
  else
45
- render "browser/not_found"
38
+ render "app/not_found"
46
39
  end
47
40
  end
48
41
 
49
42
  private
50
43
 
44
+ def initialize_inspector
45
+ @source = []
46
+ @output = []
47
+ @notes = []
48
+ end
49
+
51
50
  def find_preview
52
51
  candidates = []
53
52
  params[:path].to_s.scan(%r{/|$}) { candidates << $` }
@@ -66,12 +65,39 @@ module Lookbook
66
65
  end
67
66
  end
68
67
 
69
- def using_preview_template?
70
- @render_args[:template] == "view_components/preview"
68
+ def include_example_data(example)
69
+ content = HtmlBeautifier.beautify(preview_controller.render_example_to_string(@preview, example.name))
70
+ @output << {
71
+ label: "<!-- #{example.label} -->",
72
+ content: content,
73
+ lang: Lookbook::Lang.find(:html)
74
+ }
75
+ render_args = @preview.render_args(example.name, params: preview_controller.params.permit!)
76
+ has_template = render_args[:template] != "view_components/preview"
77
+ @source << {
78
+ label: has_template ? "<!-- #{example.label} -->" : "\# #{example.label}",
79
+ content: has_template ? example.template_source(render_args[:template]) : example.method_source,
80
+ lang: has_template ? example.template_lang(render_args[:template]) : example.source_lang
81
+ }
82
+ if example.notes.present?
83
+ @notes << {
84
+ label: example.label,
85
+ content: example.notes
86
+ }
87
+ end
71
88
  end
72
89
 
73
- def preview_output
74
- @preview_output ||= if @preview
90
+ def rendered_example
91
+ if @example.type == :group
92
+ rendered = @example.examples.map do |example|
93
+ {
94
+ label: example.label,
95
+ content: preview_controller.render_example_to_string(@preview, example.name)
96
+ }
97
+ end
98
+ joined = render_to_string "layouts/group", locals: {items: rendered}, layout: nil
99
+ preview_controller.render_in_layout_to_string(joined, @preview.lookbook_layout)
100
+ else
75
101
  preview_controller.request.params[:path] = "#{@preview.preview_name}/#{@example.name}".chomp("/")
76
102
  preview_controller.process(:previews)
77
103
  end
@@ -82,23 +108,24 @@ module Lookbook
82
108
  panes: {
83
109
  source: {
84
110
  label: "Source",
85
- content: @source || "",
86
111
  template: "code",
87
- lang: @source_lang,
88
- clipboard: @source
112
+ hotkey: "s",
113
+ items: @source,
114
+ clipboard: @source.map { |s| @source.many? ? "#{s[:label]}\n#{s[:content]}" : s[:content] }.join("\n\n")
89
115
  },
90
116
  output: {
91
117
  label: "Output",
92
- content: @render_output || "",
93
118
  template: "code",
94
- lang: @render_output_lang,
95
- clipboard: @render_output
119
+ hotkey: "o",
120
+ items: @output,
121
+ clipboard: @output.map { |o| @output.many? ? "#{o[:label]}\n#{o[:content]}" : o[:content] }.join("\n\n")
96
122
  },
97
123
  notes: {
98
124
  label: "Notes",
99
- content: @example.notes.presence || "<em class='opacity-50'>No notes provided.</em>",
100
- template: "prose",
101
- disabled: @example.notes.blank?
125
+ template: "notes",
126
+ hotkey: "n",
127
+ items: @notes,
128
+ disabled: @notes.none?
102
129
  }
103
130
  }
104
131
  }
@@ -18,7 +18,7 @@ module Lookbook
18
18
  end
19
19
 
20
20
  def nav_padding_style(depth)
21
- "padding-left: calc((#{depth} * 12px) + 0.5rem);"
21
+ "padding-left: calc((#{depth - 1} * 12px) + 0.5rem);"
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,45 @@
1
+ <div id="sidebar" class="h-full" x-data="nav" @document:updated.window="updateNav" @navigate.window="navigate">
2
+ <div class="bg-white h-10 border-b border-gray-300 flex items-center relative">
3
+ <button class="flex-none ml-4 " x-show="!$screen('md')" @click="$dispatch('sidebar:toggle')" x-cloak>
4
+ <svg class="feather w-5 h-5 hover:text-indigo-500 transition">
5
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#x" />
6
+ </svg>
7
+ </button>
8
+ <input
9
+ type="text"
10
+ class="text-sm px-4 h-10 w-full border-0 bg-transparent focus:outline-none outline-none focus:ring-0"
11
+ placeholder="Filter previews&hellip;"
12
+ x-ref="filter"
13
+ x-model="$store.nav.filter"
14
+ x-effect="if (sidebarOpenMobile) focusFilter()"
15
+ @keyup.stop="if ($event.key === 'Escape') $store.nav.filtering ? clearFilter() : unfocusFilter()"
16
+ @keyup.f.document="focusFilter"
17
+ >
18
+ <button class="text-gray-400 hover:text-indigo-500 focus:ring-0 focus:outline-none absolute top-1/2 right-2 transform -translate-y-1/2" @click="clearFilter">
19
+ <svg class="feather w-3 h-3">
20
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#x" />
21
+ </svg>
22
+ </button>
23
+ </div>
24
+ <div class="relative overflow-y-auto w-full" style="height: calc(100% - 40px)">
25
+ <div x-ref="shim">
26
+ <nav id="nav" class="select-none" x-data="navNode" @nav:updated.window="filter" x-init="$watch('$store.nav.filterText', () => filter()); $nextTick(() => filter());">
27
+ <% if @nav.items.any? %>
28
+ <ul x-ref="items">
29
+ <% @nav.items.each do |item| %>
30
+ <%= render "./nav/#{item.type.to_s}", node: item %>
31
+ <% end %>
32
+ </ul>
33
+ <div class="p-4 text-center" x-show="hidden" x-cloak>
34
+ <em class="text-gray-400">No matching previews found.</em>
35
+ </div>
36
+ <% else %>
37
+ <div class="p-4">
38
+ <h4 class="text-gray-500 mb-1">No previews found.</h4>
39
+ <p class="text-gray-400 text-xs">Have you set your <a class="underline" href="https://viewcomponent.org/api.html#preview_paths">preview paths</a> config correctly?</p>
40
+ </div>
41
+ <% end %>
42
+ </nav>
43
+ </div>
44
+ </div>
45
+ </div>
@@ -0,0 +1,12 @@
1
+ <div id="workbench" class="bg-gray-50 h-screen flex flex-col" x-data="workbench">
2
+ <%= render "./workbench/header" %>
3
+ <div class="md:grid flex-grow" :style="`grid-template-rows: 1fr 1px ${$store.inspector.height}px`">
4
+ <%= render "./workbench/preview" %>
5
+ <div class="w-full gutter border-t border-gray-300 relative" x-data="split(splitProps)" x-show="$screen('md')">
6
+ <div class="h-[11px] w-full bg-transparent hover:bg-indigo-100 hover:bg-opacity-20 transition absolute left-0 right-0 transform -translate-y-1/2 cursor-[row-resize]"></div>
7
+ </div>
8
+ <% if @inspector %>
9
+ <%= render "./workbench/inspector", **@inspector %>
10
+ <% end %>
11
+ </div>
12
+ </div>
File without changes
@@ -0,0 +1,11 @@
1
+ <div class="flex flex-col h-full min-h-fill w-full">
2
+ <%= render "./shared/header" %>
3
+ <div class="flex flex-col items-center justify-center h-full min-h-fill">
4
+ <div class="p-4 text-center">
5
+ <svg class="feather w-10 h-10 text-gray-300 mx-auto">
6
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#layers" />
7
+ </svg>
8
+ <h5 class="mt-4 text-gray-400 text-base">Select a preview to get started</h5>
9
+ </div>
10
+ </div>
11
+ </div>
@@ -1,7 +1,7 @@
1
1
  <div class="bg-white flex flex-col items-center justify-center h-screen w-full">
2
2
  <div class="p-4 text-center">
3
3
  <svg class="feather w-10 h-10 text-red-300 mx-auto">
4
- <use xlink:href="#alert-triangle" />
4
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#alert-triangle" />
5
5
  </svg>
6
6
  <div class="mt-3 text-gray-700 max-w-xs">
7
7
  <% if @preview %>
@@ -0,0 +1 @@
1
+ <%= render "./workbench" %>
@@ -1,9 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" class="h-screen">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
7
7
 
8
8
  <link href="/lookbook-assets/app.css" rel="stylesheet">
9
9
  <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👀</text></svg>">
@@ -17,35 +17,25 @@
17
17
 
18
18
  <title><%= [@example&.label, @preview&.label, "Lookbook"].compact.join(" :: ") %></title>
19
19
  </head>
20
- <body class="text-gray-800 font-sans text-sm antialiased">
20
+ <body class="text-gray-800 font-sans text-sm antialiased h-screen overflow-hidden">
21
21
  <div
22
- class="grid h-screen w-screen max-w-full overflow-hidden"
22
+ x-data="page"
23
+ x-effect="updateTitle"
24
+ @refresh.document="fetchHTML().then(doc => $dispatch('document:updated', {doc}))"
25
+ @popstate.window="fetchHTML().then(doc => { $dispatch('document:loaded', {doc}); sidebarOpenMobile = false})"
26
+ @sidebar:toggle.window="sidebarOpenMobile = !sidebarOpenMobile"
23
27
  :style="`grid-template-columns: ${$store.nav.width}px 1px 1fr;`"
24
- x-data="{
25
- doc: document,
26
- location: window.location.pathname,
27
- update(eventName){
28
- return fetch(document.location).then(async response => {
29
- if (!response.ok) return location.reload();
30
- const html = await response.text();
31
- this.doc = (new DOMParser()).parseFromString(html, 'text/html');
32
- $dispatch(eventName, {doc: this.doc})
33
- });
34
- }
35
- }"
36
- x-effect="document.title = doc.title; location = window.location.pathname"
37
- @refresh.document="update('document:updated')"
38
- @popstate.window="update('document:loaded')">
39
- <%= render "partials/sidebar" %>
40
- <div class="h-screen gutter border-r border-gray-300 relative" x-data="split({minSize: 200, onDrag: (splits) => { $store.nav.width = Math.min(splits[0], 500) }})">
28
+ class="md:grid w-screen h-screen"
29
+ >
30
+ <div class="h-full bg-gray-100 overflow-hidden" x-show="$screen('md') || sidebarOpenMobile" x-cloak>
31
+ <%= render "./sidebar" %>
32
+ </div>
33
+ <div x-data="split(splitProps)" class="h-full gutter border-r border-gray-300 relative" x-show="$screen('md')" x-cloak>
41
34
  <div class="w-[9px] h-full bg-transparent hover:bg-indigo-100 hover:bg-opacity-20 transition absolute top-0 bottom-0 transform -translate-x-1/2 cursor-[col-resize] z-10"></div>
42
35
  </div>
43
- <div id="main" class="h-screen overflow-hidden w-full" x-effect="$el.innerHTML = doc.getElementById('main').innerHTML">
36
+ <main id="main" x-effect="render" class="h-full overflow-hidden w-full" x-show="!$screen('md') || !sidebarOpenMobile" x-cloak>
44
37
  <%= yield %>
45
- </div>
46
- </div>
47
- <div class="hidden">
48
- <%= render "partials/icon_sprite" %>
38
+ </main>
49
39
  </div>
50
40
  </body>
51
41
  </html>
@@ -0,0 +1,6 @@
1
+ <% items.each do |item| %>
2
+ <div class="lookbook-example" style="margin-bottom: 30px;">
3
+ <h6 class="lookbook-example-label" style="color: #999; font-size: 14px; margin-bottom: 10px;"><%= item[:label] %></h6>
4
+ <%= item[:content] %>
5
+ </div>
6
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%= render "./nav/node", node: node do %>
2
+ <% node.items.each do |item| %>
3
+ <%= render "./nav/#{item.type.to_s}", node: item %>
4
+ <% end %>
5
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <li id="<%= node.id %>" x-data="navNode" <% if node.hierarchy_depth == 1 %> class="py-1 border-b border-gray-300 cursor-pointer"<% end %> :class="{hidden}" x-cloak>
2
+ <div @click="toggle" style="<%= nav_padding_style(node.hierarchy_depth) %>">
3
+ <div class="flex items-center cursor-pointer pr-3 py-[5px]">
4
+ <svg class="feather w-3 h-3 mr-1 text-gray-500 flex-none">
5
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#chevron-down" x-show="open" x-cloak />
6
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#chevron-right" x-show="!open()" x-cloak />
7
+ </svg>
8
+ <svg class="feather h-3.5 w-3.5 mr-1.5 flex-none text-indigo-500">
9
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#<%= node.type == :preview ? 'layers' : 'folder' %>" />
10
+ </svg>
11
+ <div class="truncate whitespace-nowrap text-left <%= "font-bold" if node.type == :preview %>">
12
+ <%= node.label %>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ <ul x-ref="items" x-show="open" x-cloak>
17
+ <%= yield %>
18
+ </ul>
19
+ </li>
@@ -0,0 +1,29 @@
1
+ <%= render "./nav/node", node: node do %>
2
+ <% node.get_examples.each do |example| %>
3
+ <% path = show_path example.path %>
4
+ <li x-data="navLeaf" :class="{hidden}" x-init="matchers = <%= example.matchers.to_json %>; path = '<%= path %>'; setActive()" @popstate.window="setActive">
5
+ <a href="<%= path %>"
6
+ class="pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
7
+ style="<%= nav_padding_style(example.hierarchy_depth + 1) %>"
8
+ :class="{'!bg-indigo-100':active}"
9
+ @click.stop.prevent="navigate"
10
+ >
11
+ <div class="relative w-3.5 h-3.5 mr-1.5" :class="active ? 'text-gray-900' : 'text-indigo-500'">
12
+ <svg class="feather flex-none group-hover:text-indigo-800 transition <% if example.type == :group %> absolute top-[-2px] left-[-2px] w-3 h-3<% else %>w-3.5 h-3.5<% end %>">
13
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#eye" />
14
+ </svg>
15
+ <% if example.type == :group %>
16
+ <svg class="feather w-3 h-3 flex-none group-hover:text-indigo-800 transition absolute top-[4px] left-[4px]">
17
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#eye" />
18
+ </svg>
19
+ <% end %>
20
+ </div>
21
+ <div class="truncate whitespace-nowrap">
22
+ <%= example.label %>
23
+ </div>
24
+ </a>
25
+ </li>
26
+ <% end %>
27
+ <% end %>
28
+
29
+
@@ -0,0 +1,11 @@
1
+ <button
2
+ class="p-1.5 border-b border-l border-gray-200 hover:border-gray-300 rounded-bl-md bg-white absolute top-0 right-0 text-gray-400 hover:text-indigo-500 transition"
3
+ x-data="clipboard"
4
+ x-tooltip.theme.lookbook="done ? 'copied!' : 'copy to clipboard'"
5
+ @click="save"
6
+ data-tippy-placement="left">
7
+ <svg class="feather h-4 w-4 ">
8
+ <use x-bind:href="`/lookbook-assets/feather-sprite.svg#${done ? 'check' : 'clipboard'}`" />
9
+ </svg>
10
+ <div class="hidden" x-init="content = $el.innerText"><%== yield %></div>
11
+ </button>
@@ -0,0 +1,8 @@
1
+ <header class="py-2 px-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10">
2
+ <button class="flex-none mr-3" x-show="!$screen('md')" @click="$dispatch('sidebar:toggle')">
3
+ <svg class="feather w-5 h-5 hover:text-indigo-500 transition">
4
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#menu" />
5
+ </svg>
6
+ </button>
7
+ <%= yield %>
8
+ </header>
@@ -0,0 +1,37 @@
1
+ <%= render "./shared/header" do %>
2
+ <div class="flex items-center">
3
+ <div class="flex items-center space-x-1">
4
+ <strong class="whitespace-nowrap truncate"><%= @preview.label %></strong>
5
+ <span>/</span>
6
+ <span class="whitespace-nowrap truncate"><%= @example.label %></span>
7
+ </div>
8
+ </div>
9
+ <div class="flex text-xs ml-auto font-monospace text-gray-700 space-x-1" x-show="$screen('md')">
10
+ <span x-text="`${previewViewportWidth}px`"></span>
11
+ <span class="text-gray-500">x</span>
12
+ <span x-text="`${previewViewportHeight}px`"></span>
13
+ </div>
14
+ <div class="flex items-center ml-auto md:ml-3 md:pl-3 space-x-3 md:border-l border-gray-300 text-gray-400">
15
+ <a
16
+ href="<%= url_for %>"
17
+ class="block transition hover:text-indigo-800"
18
+ x-tooltip.theme.lookbook="`Refresh preview`"
19
+ @click.prevent.stop="$dispatch('navigate')"
20
+ data-hotkey="r"
21
+ >
22
+ <svg class="feather w-4 h-4">
23
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#refresh-cw" />
24
+ </svg>
25
+ </a>
26
+ <a
27
+ href="<%= preview_path %>"
28
+ class="block transition hover:text-indigo-800" target="_blank"
29
+ x-tooltip.theme.lookbook="`Open in new window`"
30
+ data-hotkey="w"
31
+ >
32
+ <svg class="feather w-4 h-4">
33
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#external-link" />
34
+ </svg>
35
+ </a>
36
+ </div>
37
+ <% end %>
@@ -0,0 +1,32 @@
1
+ <div id="inspector" class="bg-white w-full overflow-hidden flex flex-col" x-data="inspector" x-show="$screen('md')">
2
+ <div class="px-4 border-b border-gray-200 flex items-center flex-none select-none">
3
+ <nav class="-mb-px flex space-x-8 cursor-auto">
4
+ <% panes.each do |key, props| %>
5
+ <a
6
+ href="#<%= key %>"
7
+ class="whitespace-nowrap py-2 px-1 border-b-2 cursor-pointer <%= "!text-gray-300" if props[:disabled] %>"
8
+ :class="{
9
+ 'border-indigo-400': active('<%= key %>'),
10
+ 'border-transparent text-gray-500 hover:text-gray-700': !active('<%= key %>')
11
+ }"
12
+ @click.stop.prevent="switchTo('<%= key %>')"
13
+ <% if props[:hotkey] %>data-hotkey="<%= props[:hotkey] %>"<% end %>
14
+ >
15
+ <%== props[:label] %>
16
+ </a>
17
+ <% end %>
18
+ </nav>
19
+ </div>
20
+ <div class="flex-auto overflow-auto bg-gray-50">
21
+ <% panes.each do |key, props| %>
22
+ <div class="flex flex-col h-full relative" x-show="active('<%= key %>')" x-cloak>
23
+ <% if props[:clipboard].present? %>
24
+ <%= render "./shared/clipboard" do %><%= h props[:clipboard].strip %><% end %>
25
+ <% end %>
26
+ <div class="flex flex-col h-full overflow-auto">
27
+ <%= render "./workbench/inspector/#{props[:template]}", key: key, **props %>
28
+ </div>
29
+ </div>
30
+ <% end %>
31
+ </div>
32
+ </div>
@@ -0,0 +1,24 @@
1
+ <div id="preview" class="h-full md:h-auto md:min-h-0 flex w-full bg-gray-50">
2
+ <div class="relative mx-auto bg-white" x-data="preview" :style="`width: ${$screen('md') ? $store.preview.width : '100%'}`">
3
+ <iframe seamless
4
+ class="absolute h-full inset-0 w-full border-l border-gray-300 md:pr-4 md:-mx-px"
5
+ src="<%= url_for lookbook.preview_path %>"
6
+ <% if config.preview_srcdoc %>srcdoc="<%== @rendered_example %>"<% end %>
7
+ frameborder="0"
8
+ x-data="sizeObserver"
9
+ x-effect="previewViewportWidth = observedWidth; previewViewportHeight = observedHeight;"
10
+ ></iframe>
11
+ <div class="absolute opacity-0 inset-0 pointer-events-none" :class="{ 'pointer-events-none': !$store.page.reflowing }"></div>
12
+ <div
13
+ class="border-l border-r border-gray-300 bg-white hover:bg-indigo-100 hover:bg-opacity-20 transition absolute right-0 inset-y-0 flex items-center w-4 cursor-[col-resize] select-none"
14
+ style="touch-action: none"
15
+ @pointerdown="onResizeStart"
16
+ @dblclick="toggleFullWidth"
17
+ x-show="$screen('md')"
18
+ >
19
+ <svg class="h-4 w-4 text-gray-600 pointer-events-none" fill="currentColor" viewBox="0 0 24 24">
20
+ <path d="M8 5h2v14H8zM14 5h2v14h-2z"></path>
21
+ </svg>
22
+ </div>
23
+ </div>
24
+ </div>
@@ -0,0 +1,3 @@
1
+ <pre class="h-full"><code class="h-full p-4 block highlight font-monospace" style="background-image: none"><% items.each do |item| %><%== highlight( "#{items.many? ? item[:label] + "\n" : ""}#{item[:content].strip}", item[:lang][:name]) %>
2
+
3
+ <% end %></code></pre>
@@ -0,0 +1,24 @@
1
+ <div class="text-gray-600">
2
+ <% if items.many? %>
3
+ <div class="divide-y divide-gray-300">
4
+ <% items.each do |item| %>
5
+ <div class="px-4 pt-10 pb-8 relative">
6
+ <div class="prose prose-sm">
7
+ <%= markdown(item[:content]) %>
8
+ </div>
9
+ <h6 class="text-[11px] text-gray-600 inline-block px-2 py-0.25 bg-gray-100 border border-t-0 border-gray-300 absolute top-0 left-4 rounded-b">
10
+ <%= item[:label] %>
11
+ </h6>
12
+ </div>
13
+ <% end %>
14
+ </div>
15
+ <% else %>
16
+ <div class="p-4 prose prose-sm">
17
+ <% if items.any? %>
18
+ <%= markdown(items[0][:content]) %>
19
+ <% else %>
20
+ <em class='opacity-50'>No notes provided.</em>
21
+ <% end %>
22
+ </div>
23
+ <% end %>
24
+ </div>
data/config/routes.rb CHANGED
@@ -3,8 +3,8 @@ Lookbook::Engine.routes.draw do
3
3
  mount Lookbook::Engine.websocket => Lookbook::Engine.cable.mount_path
4
4
  end
5
5
 
6
- root to: "browser#index", as: :home
6
+ root to: "app#index", as: :home
7
7
 
8
- get "/preview/*path", to: "browser#preview", as: :preview
9
- get "/*path", to: "browser#show", as: :show
8
+ get "/preview/*path", to: "app#preview", as: :preview
9
+ get "/*path", to: "app#show", as: :show
10
10
  end
@@ -28,6 +28,7 @@ module Lookbook
28
28
  options.preview_paths += vc_options.preview_paths
29
29
 
30
30
  options.preview_controller = vc_options.preview_controller if options.preview_controller.nil?
31
+ options.preview_srcdoc = true if options.preview_srcdoc.nil?
31
32
 
32
33
  options.listen_paths = options.listen_paths.map(&:to_s)
33
34
  options.listen_paths += options.preview_paths
@@ -36,8 +37,7 @@ module Lookbook
36
37
 
37
38
  initializer "lookbook.cable.config" do |app|
38
39
  if app.config.lookbook.auto_refresh
39
- config_path = Lookbook::Engine.root.join("config", "lookbook_cable.yml")
40
- Lookbook::Engine.cable.cable = app.config_for(config_path).with_indifferent_access
40
+ Lookbook::Engine.cable.cable = {adapter: "async"}.with_indifferent_access
41
41
  Lookbook::Engine.cable.mount_path = "/cable"
42
42
  Lookbook::Engine.cable.connection_class = -> { Lookbook::Connection }
43
43
  end
@@ -2,6 +2,10 @@ module Lookbook
2
2
  module Preview
3
3
  include Taggable
4
4
 
5
+ def id
6
+ lookbook_path.tr("_", "-")
7
+ end
8
+
5
9
  # Examples::FooBarComponent::Preview -> "Foo Bar"
6
10
  def lookbook_label
7
11
  super.presence || lookbook_path.split("/").last.titleize
@@ -19,9 +23,21 @@ module Lookbook
19
23
  return @lookbook_examples if @lookbook_examples.present?
20
24
  public_methods = public_instance_methods(false)
21
25
  public_method_objects = code_object.meths.filter { |m| public_methods.include?(m.name) }
22
- examples = public_method_objects.map { |m| PreviewExample.new(m.name.to_s, self) }
23
- examples.reject!(&:hidden?)
24
- @lookbook_examples ||= Lookbook.config.sort_examples ? examples.sort_by(&:label) : examples
26
+ visible = public_method_objects.map { |m| PreviewExample.new(m.name.to_s, self) }.reject(&:hidden?)
27
+ sorted = Lookbook.config.sort_examples ? visible.sort_by(&:label) : visible
28
+ @lookbook_examples = []
29
+ if code_object.groups.any?
30
+ sorted.group_by { |m| m.group }.each do |name, examples|
31
+ if name.nil?
32
+ @lookbook_examples += examples
33
+ else
34
+ @lookbook_examples << PreviewGroup.new(name.underscore, self, examples)
35
+ end
36
+ end
37
+ else
38
+ @lookbook_examples = sorted
39
+ end
40
+ @lookbook_examples
25
41
  end
26
42
 
27
43
  # Examples::FooBarComponentPreview -> "Examples::FooBar"
@@ -57,6 +73,10 @@ module Lookbook
57
73
  lookbook_path.tr("_", "-")
58
74
  end
59
75
 
76
+ def lookbook_layout
77
+ defined?(@layout) ? @layout : nil
78
+ end
79
+
60
80
  class << self
61
81
  def all
62
82
  ViewComponent::Preview.all
@@ -69,6 +89,8 @@ module Lookbook
69
89
  def exists?(path)
70
90
  !!find(path)
71
91
  end
92
+
93
+
72
94
  end
73
95
 
74
96
  private
@@ -1,6 +1,6 @@
1
1
  module Lookbook
2
2
  module PreviewController
3
- def render_component_to_string(preview, example_name)
3
+ def render_example_to_string(preview, example_name)
4
4
  prepend_application_view_paths
5
5
  prepend_preview_examples_view_path
6
6
  @preview = preview
@@ -13,5 +13,10 @@ module Lookbook
13
13
  opts[:locals] = locals if locals.present?
14
14
  render_to_string template, opts
15
15
  end
16
+
17
+ def render_in_layout_to_string(content, layout_override)
18
+ layout = determine_layout(layout_override, prepend_views: false)[:layout]
19
+ render_to_string html: content, layout: layout
20
+ end
16
21
  end
17
22
  end