lookbook 0.2.3 → 0.3.0.beta.2

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.
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 +39 -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/nav/_collection.html.erb +5 -0
  28. data/app/views/lookbook/nav/_leaf.html.erb +21 -0
  29. data/app/views/lookbook/nav/_node.html.erb +19 -0
  30. data/app/views/lookbook/nav/_preview.html.erb +11 -0
  31. data/app/views/lookbook/preview_group.html.erb +8 -0
  32. data/app/views/lookbook/shared/_clipboard.html.erb +11 -0
  33. data/app/views/lookbook/shared/_header.html.erb +8 -0
  34. data/app/views/lookbook/workbench/_header.html.erb +37 -0
  35. data/app/views/lookbook/workbench/_inspector.html.erb +32 -0
  36. data/app/views/lookbook/workbench/_preview.html.erb +24 -0
  37. data/app/views/lookbook/workbench/inspector/_code.html.erb +3 -0
  38. data/app/views/lookbook/workbench/inspector/_notes.html.erb +24 -0
  39. data/app/views/lookbook/{partials → workbench}/inspector/_plain.html.erb +0 -0
  40. data/config/routes.rb +3 -3
  41. data/lib/lookbook/engine.rb +1 -0
  42. data/lib/lookbook/preview.rb +26 -3
  43. data/lib/lookbook/preview_controller.rb +6 -1
  44. data/lib/lookbook/preview_example.rb +3 -2
  45. data/lib/lookbook/preview_group.rb +37 -0
  46. data/lib/lookbook/taggable.rb +5 -1
  47. data/lib/lookbook/version.rb +1 -1
  48. data/lib/lookbook.rb +1 -0
  49. data/lib/tasks/lookbook_tasks.rake +1 -1
  50. data/public/lookbook-assets/app.css +229 -99
  51. data/public/lookbook-assets/app.js +882 -56
  52. data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
  53. metadata +53 -24
  54. data/app/assets/lookbook/js/preview.js +0 -76
  55. data/app/views/lookbook/browser/index.html.erb +0 -8
  56. data/app/views/lookbook/browser/show.html.erb +0 -33
  57. data/app/views/lookbook/partials/_preview.html.erb +0 -18
  58. data/app/views/lookbook/partials/_sidebar.html.erb +0 -21
  59. data/app/views/lookbook/partials/inspector/_code.html.erb +0 -1
  60. data/app/views/lookbook/partials/inspector/_inspector.html.erb +0 -43
  61. data/app/views/lookbook/partials/inspector/_prose.html.erb +0 -3
  62. data/app/views/lookbook/partials/nav/_collection.html.erb +0 -17
  63. data/app/views/lookbook/partials/nav/_label.html.erb +0 -13
  64. data/app/views/lookbook/partials/nav/_nav.html.erb +0 -27
  65. data/app/views/lookbook/partials/nav/_preview.html.erb +0 -48
@@ -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
+ examples = @example.examples.map do |example|
93
+ {
94
+ label: example.label,
95
+ html: preview_controller.render_example_to_string(@preview, example.name)
96
+ }
97
+ end
98
+ joined = render_to_string "./preview_group", locals: {examples: examples}, 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,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,21 @@
1
+ <%
2
+ path = show_path leaf.path
3
+ display ||= :leaf
4
+ %>
5
+ <li x-data="navLeaf" :class="{hidden}" x-init="matchers = <%= leaf.matchers.to_json %>; path = '<%= path %>'; setActive()" @popstate.window="setActive">
6
+ <a href="<%= path %>"
7
+ class="pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
8
+ style="<%= nav_padding_style(depth) %>"
9
+ :class="{'!bg-indigo-100':active}"
10
+ @click.stop.prevent="navigate"
11
+ >
12
+ <div class="relative w-3.5 h-3.5 mr-1.5 <%= "ml-[3px]" if display == :node %> " :class="active ? 'text-gray-900' : 'text-indigo-500'">
13
+ <svg class="feather flex-none group-hover:text-indigo-800 transition w-3.5 h-3.5">
14
+ <use xlink:href="/lookbook-assets/feather-sprite.svg#<%= display == :node ? "layers" : "eye" %>" />
15
+ </svg>
16
+ </div>
17
+ <div class="truncate whitespace-nowrap <%= "font-bold" if display == :node %>">
18
+ <%= leaf.label %>
19
+ </div>
20
+ </a>
21
+ </li>
@@ -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(); if (open()) { <%= "navigate('#{path}')" if defined?(path) %>}" style="<%= nav_padding_style(node.hierarchy_depth) %>" class="hover:bg-gray-200 hover:bg-opacity-50">
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,11 @@
1
+ <% examples = node.get_examples %>
2
+ <% if examples.many? %>
3
+ <%= render "./nav/node", node: node, path: show_path(examples.first.path) do %>
4
+ <% examples.each do |example| %>
5
+ <%= render "./nav/leaf", leaf: example, depth: example.hierarchy_depth + 1 %>
6
+ <% end %>
7
+ <% end %>
8
+ <% else %>
9
+ <% example = examples.first %>
10
+ <%= render "./nav/leaf", leaf: example, depth: example.hierarchy_depth, display: :node %>
11
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <% examples.each do |example| %>
2
+ <div style="margin-bottom: 30px;">
3
+ <h6 style="color: #999; font-family: sans-serif; font-size: 14px; margin-top: 0; margin-bottom: 10px;">
4
+ <%= example[:label] %>
5
+ </h6>
6
+ <%= example[:html] %>
7
+ </div>
8
+ <% end %>
@@ -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
@@ -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,22 @@ 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
+ name = lookbook_label if name.strip == ""
35
+ @lookbook_examples << PreviewGroup.new(name.underscore, self, examples)
36
+ end
37
+ end
38
+ else
39
+ @lookbook_examples = sorted
40
+ end
41
+ @lookbook_examples
25
42
  end
26
43
 
27
44
  # Examples::FooBarComponentPreview -> "Examples::FooBar"
@@ -57,6 +74,10 @@ module Lookbook
57
74
  lookbook_path.tr("_", "-")
58
75
  end
59
76
 
77
+ def lookbook_layout
78
+ defined?(@layout) ? @layout : nil
79
+ end
80
+
60
81
  class << self
61
82
  def all
62
83
  ViewComponent::Preview.all
@@ -69,6 +90,8 @@ module Lookbook
69
90
  def exists?(path)
70
91
  !!find(path)
71
92
  end
93
+
94
+
72
95
  end
73
96
 
74
97
  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