lookbook 0.4.6 → 0.4.7

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/lookbook/css/app.css +55 -5
  3. data/app/assets/lookbook/js/app.js +38 -54
  4. data/app/assets/lookbook/js/components/copy.js +16 -0
  5. data/app/assets/lookbook/js/components/filter.js +24 -0
  6. data/app/assets/lookbook/js/components/inspector.js +21 -0
  7. data/app/assets/lookbook/js/{nav/node.js → components/nav-group.js} +16 -15
  8. data/app/assets/lookbook/js/components/nav-item.js +26 -0
  9. data/app/assets/lookbook/js/components/nav.js +35 -0
  10. data/app/assets/lookbook/js/components/page.js +33 -0
  11. data/app/assets/lookbook/js/{workbench → components}/param.js +3 -4
  12. data/app/assets/lookbook/js/{workbench/preview.js → components/preview-window.js} +9 -10
  13. data/app/assets/lookbook/js/components/sidebar.js +3 -0
  14. data/app/assets/lookbook/js/components/sizes.js +16 -0
  15. data/app/assets/lookbook/js/components/splitter.js +25 -0
  16. data/app/assets/lookbook/js/config.js +14 -0
  17. data/app/assets/lookbook/js/{utils/reloader.js → lib/socket.js} +7 -12
  18. data/app/assets/lookbook/js/lib/split.js +21 -0
  19. data/app/assets/lookbook/js/lib/utils.js +3 -0
  20. data/app/assets/lookbook/js/stores/filter.js +11 -0
  21. data/app/assets/lookbook/js/stores/inspector.js +17 -0
  22. data/app/assets/lookbook/js/stores/layout.js +12 -0
  23. data/app/assets/lookbook/js/stores/nav.js +21 -0
  24. data/app/assets/lookbook/js/stores/sidebar.js +14 -0
  25. data/app/controllers/lookbook/app_controller.rb +70 -97
  26. data/app/helpers/lookbook/application_helper.rb +48 -4
  27. data/app/views/layouts/lookbook/app.html.erb +54 -0
  28. data/app/views/layouts/lookbook/preview.html.erb +12 -0
  29. data/app/views/lookbook/components/_code.html.erb +8 -0
  30. data/app/views/lookbook/{shared/_clipboard.html.erb → components/_copy.html.erb} +4 -5
  31. data/app/views/lookbook/components/_filter.html.erb +15 -0
  32. data/app/views/lookbook/{shared → components}/_header.html.erb +3 -3
  33. data/app/views/lookbook/components/_icon.html.erb +5 -0
  34. data/app/views/lookbook/components/_nav.html.erb +17 -0
  35. data/app/views/lookbook/components/_nav_collection.html.erb +5 -0
  36. data/app/views/lookbook/components/_nav_group.html.erb +17 -0
  37. data/app/views/lookbook/components/_nav_item.html.erb +21 -0
  38. data/app/views/lookbook/components/_nav_preview.html.erb +11 -0
  39. data/app/views/lookbook/components/_param.html.erb +20 -0
  40. data/app/views/lookbook/{workbench → components}/_preview.html.erb +8 -8
  41. data/app/views/lookbook/{app/error.html.erb → error.html.erb} +0 -0
  42. data/app/views/lookbook/index.html.erb +9 -0
  43. data/app/views/lookbook/{workbench/inspector/params → inputs}/_select.html.erb +1 -1
  44. data/app/views/lookbook/{workbench/inspector/params → inputs}/_text.html.erb +1 -1
  45. data/app/views/lookbook/{workbench/inspector/params → inputs}/_textarea.html.erb +1 -1
  46. data/app/views/lookbook/{workbench/inspector/params → inputs}/_toggle.html.erb +1 -1
  47. data/app/views/lookbook/{app/not_found.html.erb → not_found.html.erb} +2 -4
  48. data/app/views/lookbook/panels/_notes.html.erb +25 -0
  49. data/app/views/lookbook/panels/_output.html.erb +18 -0
  50. data/app/views/lookbook/panels/_params.html.erb +17 -0
  51. data/app/views/lookbook/panels/_source.html.erb +20 -0
  52. data/app/views/lookbook/show.html.erb +90 -0
  53. data/lib/lookbook/code_formatter.rb +20 -0
  54. data/lib/lookbook/engine.rb +4 -1
  55. data/lib/lookbook/lang.rb +10 -5
  56. data/lib/lookbook/preview_controller.rb +1 -1
  57. data/lib/lookbook/preview_group.rb +5 -1
  58. data/lib/lookbook/version.rb +1 -1
  59. data/lib/lookbook.rb +1 -0
  60. data/public/lookbook-assets/css/app.css +2 -0
  61. data/public/lookbook-assets/css/app.css.map +1 -0
  62. data/public/lookbook-assets/js/app.js +2 -0
  63. data/public/lookbook-assets/js/app.js.map +1 -0
  64. metadata +54 -43
  65. data/app/assets/lookbook/js/nav/leaf.js +0 -20
  66. data/app/assets/lookbook/js/nav.js +0 -38
  67. data/app/assets/lookbook/js/page.js +0 -38
  68. data/app/assets/lookbook/js/utils/clipboard.js +0 -13
  69. data/app/assets/lookbook/js/utils/morph.js +0 -19
  70. data/app/assets/lookbook/js/utils/screen.js +0 -44
  71. data/app/assets/lookbook/js/utils/size_observer.js +0 -16
  72. data/app/assets/lookbook/js/utils/split.js +0 -26
  73. data/app/assets/lookbook/js/workbench/inspector.js +0 -11
  74. data/app/assets/lookbook/js/workbench.js +0 -14
  75. data/app/views/lookbook/app/index.html.erb +0 -11
  76. data/app/views/lookbook/app/show.html.erb +0 -1
  77. data/app/views/lookbook/layouts/app.html.erb +0 -41
  78. data/app/views/lookbook/nav/_collection.html.erb +0 -5
  79. data/app/views/lookbook/nav/_leaf.html.erb +0 -22
  80. data/app/views/lookbook/nav/_node.html.erb +0 -19
  81. data/app/views/lookbook/nav/_preview.html.erb +0 -11
  82. data/app/views/lookbook/preview/group.html.erb +0 -8
  83. data/app/views/lookbook/shared/_sidebar.html.erb +0 -45
  84. data/app/views/lookbook/shared/_workbench.html.erb +0 -12
  85. data/app/views/lookbook/workbench/_header.html.erb +0 -39
  86. data/app/views/lookbook/workbench/_inspector.html.erb +0 -38
  87. data/app/views/lookbook/workbench/inspector/_code.html.erb +0 -3
  88. data/app/views/lookbook/workbench/inspector/_notes.html.erb +0 -24
  89. data/app/views/lookbook/workbench/inspector/_params.html.erb +0 -28
  90. data/app/views/lookbook/workbench/inspector/_plain.html.erb +0 -3
  91. data/public/lookbook-assets/app.css +0 -2626
  92. data/public/lookbook-assets/app.js +0 -8718
@@ -0,0 +1,14 @@
1
+ import config from "../config";
2
+
3
+ export default function createSidebarStore(Alpine) {
4
+ const { defaultWidth, minWidth, maxWidth } = config.sidebar;
5
+ return {
6
+ open: Alpine.$persist(false).as("sidebar-open"),
7
+ width: Alpine.$persist(defaultWidth).as("sidebar-width"),
8
+ minWidth,
9
+ maxWidth,
10
+ toggle() {
11
+ Alpine.store("sidebar").open = !Alpine.store("sidebar").open;
12
+ },
13
+ };
14
+ }
@@ -1,52 +1,44 @@
1
- require "htmlbeautifier"
2
-
3
1
  module Lookbook
4
2
  class AppController < ActionController::Base
5
3
  EXCEPTIONS = [ViewComponent::PreviewTemplateError, ViewComponent::ComponentError, ViewComponent::TemplateError, ActionView::Template::Error]
6
4
 
7
5
  protect_from_forgery with: :exception
8
- prepend_view_path File.expand_path("../../views/lookbook", __dir__)
9
-
10
- layout "layouts/app"
11
6
  helper Lookbook::ApplicationHelper
12
7
 
13
8
  before_action :find_preview, only: [:preview, :show]
14
9
  before_action :find_example, only: [:preview, :show]
15
- before_action :assign_nav, only: [:index, :show]
16
- before_action :initialize_inspector, only: [:show]
10
+ before_action :build_nav
11
+
12
+ def self.controller_path
13
+ "lookbook"
14
+ end
17
15
 
18
16
  def preview
19
17
  if @example
20
- render html: rendered_example
18
+ set_params
19
+ render html: render_examples(examples_data)
21
20
  else
22
- render "app/not_found"
21
+ render "not_found"
23
22
  end
24
23
  end
25
24
 
26
25
  def show
27
26
  if @example
28
27
  begin
29
- @rendered_example = rendered_example.gsub("\"", "&quot;")
30
- (@example.type == :group ? @example.examples : [@example]).each do |example|
31
- include_example_data(example)
32
- end
33
- assign_inspector
28
+ set_params
29
+ @examples = examples_data
30
+ @preview_srcdoc = render_examples(examples_data).gsub("\"", "&quot;")
31
+ @panels = panels.filter { |name, panel| panel[:show] }
34
32
  rescue *EXCEPTIONS
35
- render "app/error"
33
+ render "error"
36
34
  end
37
35
  else
38
- render "app/not_found"
36
+ render "not_found"
39
37
  end
40
38
  end
41
39
 
42
40
  private
43
41
 
44
- def initialize_inspector
45
- @source = []
46
- @output = []
47
- @notes = []
48
- end
49
-
50
42
  def find_preview
51
43
  candidates = []
52
44
  params[:path].to_s.scan(%r{/|$}) { candidates << $` }
@@ -65,101 +57,47 @@ module Lookbook
65
57
  end
66
58
  end
67
59
 
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
- }
60
+ def examples_data
61
+ @examples_data ||= (@example.type == :group ? @example.examples : [@example]).map do |example|
62
+ example_data(example)
63
+ end
64
+ end
65
+
66
+ def example_data(example)
75
67
  render_args = @preview.render_args(example.name, params: preview_controller.params.permit!)
76
68
  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
69
+ {
70
+ label: example.label,
71
+ notes: example.notes,
72
+ html: preview_controller.render_example_to_string(@preview, example.name),
73
+ source: has_template ? example.template_source(render_args[:template]) : example.method_source,
74
+ source_lang: has_template ? example.template_lang(render_args[:template]) : example.source_lang,
75
+ params: enabled?(:params) ? example.params : []
81
76
  }
82
- if example.notes.present?
83
- @notes << {
84
- label: example.label,
85
- content: example.notes
86
- }
87
- end
88
77
  end
89
78
 
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
- set_params
99
- preview_controller.render_in_layout_to_string("lookbook/preview/group", {examples: examples}, @preview.lookbook_layout)
100
- else
101
- set_params(@example)
102
- preview_controller.params[:path] = "#{@preview.preview_name}/#{@example.name}".chomp("/")
103
- preview_controller.process(:previews)
104
- end
79
+ def render_examples(examples)
80
+ preview_controller.render_in_layout_to_string("layouts/lookbook/preview", {examples: examples}, @preview.lookbook_layout)
105
81
  end
106
82
 
107
- def set_params(example = nil)
108
- if example.present? && enabled?(:params)
83
+ def set_params
84
+ if enabled?(:params)
109
85
  # cast known params to type
110
- example.params.each do |param|
86
+ @example.params.each do |param|
111
87
  if preview_controller.params.key?(param[:name])
112
88
  preview_controller.params[param[:name]] = Lookbook::Params.cast(preview_controller.params[param[:name]], param[:type])
113
89
  end
114
90
  end
115
91
  end
116
92
  # set display params
117
- example_params = example.nil? ? @preview.display_params : example.display_params
118
93
  preview_controller.params.merge!({
119
94
  lookbook: {
120
- display: example_params
95
+ display: @example.display_params
121
96
  }
122
97
  })
123
98
  end
124
99
 
125
- def assign_inspector
126
- @inspector = {
127
- panes: {
128
- source: {
129
- label: "Source",
130
- template: "code",
131
- hotkey: "s",
132
- items: @source,
133
- clipboard: @source.map { |s| @source.many? ? "#{s[:label]}\n#{s[:content]}" : s[:content] }.join("\n\n")
134
- },
135
- output: {
136
- label: "Output",
137
- template: "code",
138
- hotkey: "o",
139
- items: @output,
140
- clipboard: @output.map { |o| @output.many? ? "#{o[:label]}\n#{o[:content]}" : o[:content] }.join("\n\n")
141
- },
142
- notes: {
143
- label: "Notes",
144
- template: "notes",
145
- hotkey: "n",
146
- items: @notes,
147
- disabled: @notes.none?
148
- }
149
- }
150
- }
151
- if enabled?(:params)
152
- @inspector[:panes][:params] = {
153
- label: "Params",
154
- template: "params",
155
- hotkey: "p",
156
- items: @source.many? ? [] : @example.params,
157
- disabled: @source.many? || @example.params.none?
158
- }
159
- end
160
- end
161
-
162
- def assign_nav
100
+ def build_nav
163
101
  @nav = Collection.new
164
102
  previews.reject { |p| p.hidden? }.each do |preview|
165
103
  current = @nav
@@ -179,6 +117,41 @@ module Lookbook
179
117
  @nav
180
118
  end
181
119
 
120
+ def panels
121
+ {
122
+ source: {
123
+ label: "Source",
124
+ template: "lookbook/panels/source",
125
+ hotkey: "s",
126
+ show: true,
127
+ disabled: false,
128
+ copy: true
129
+ },
130
+ output: {
131
+ label: "Output",
132
+ template: "lookbook/panels/output",
133
+ hotkey: "o",
134
+ show: true,
135
+ disabled: false,
136
+ copy: true
137
+ },
138
+ notes: {
139
+ label: "Notes",
140
+ template: "lookbook/panels/notes",
141
+ hotkey: "n",
142
+ show: true,
143
+ disabled: @examples.filter { |e| e[:notes].present? }.none?
144
+ },
145
+ params: {
146
+ label: "Params",
147
+ template: "lookbook/panels/params",
148
+ hotkey: "p",
149
+ show: enabled?(:params),
150
+ disabled: @example.type == :group || @example.params.none?
151
+ }
152
+ }
153
+ end
154
+
182
155
  def previews
183
156
  Lookbook::Preview.all.sort_by(&:label)
184
157
  end
@@ -1,5 +1,6 @@
1
1
  require "redcarpet"
2
2
  require "rouge"
3
+ require "htmlbeautifier"
3
4
 
4
5
  module Lookbook
5
6
  module ApplicationHelper
@@ -16,14 +17,57 @@ module Lookbook
16
17
  markdown.render(text).html_safe
17
18
  end
18
19
 
19
- def highlight(source, language)
20
- formatter = Rouge::Formatters::HTML.new(css_class: "highlight")
20
+ def highlight(source, language, opts = {})
21
+ formatter = Lookbook::CodeFormatter.new(opts)
21
22
  lexer = Rouge::Lexer.find(language)
22
23
  formatter.format(lexer.lex(source)).html_safe
23
24
  end
24
25
 
25
- def nav_padding_style(depth)
26
- "padding-left: calc((#{depth - 1} * 12px) + 0.5rem);"
26
+ def beautify(source, language = "html")
27
+ source = source.strip
28
+ result = language.downcase == "html" ? HtmlBeautifier.beautify(source) : source
29
+ result.strip.html_safe
30
+ end
31
+
32
+ def icon(name = nil, size: 4, **attrs)
33
+ render "lookbook/components/icon",
34
+ name: name,
35
+ size: size,
36
+ classes: class_names(attrs[:class]),
37
+ **attrs.except(:class)
38
+ end
39
+
40
+ def component(name, **attrs, &block)
41
+ render "lookbook/components/#{name}",
42
+ classes: class_names(attrs[:class]),
43
+ **attrs.except(:class),
44
+ &block
45
+ end
46
+
47
+ if Rails.version.to_f < 6.1
48
+ def class_names(*args)
49
+ tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
50
+ safe_join(tokens, " ")
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def build_tag_values(*args)
57
+ tag_values = []
58
+ args.each do |tag_value|
59
+ case tag_value
60
+ when Hash
61
+ tag_value.each do |key, val|
62
+ tag_values << key.to_s if val && key.present?
63
+ end
64
+ when Array
65
+ tag_values.concat build_tag_values(*tag_value)
66
+ else
67
+ tag_values << tag_value.to_s if tag_value.present?
68
+ end
69
+ end
70
+ tag_values
27
71
  end
28
72
  end
29
73
  end
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-screen">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
7
+
8
+ <link href="/lookbook-assets/css/app.css?v=<%= Lookbook::VERSION %>" rel="stylesheet">
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>">
10
+
11
+ <% if config.auto_refresh %>
12
+ <script>
13
+ window.SOCKET_PATH = "<%= Lookbook::Engine.websocket_mount_path %>";
14
+ </script>
15
+ <% end %>
16
+ <script src="/lookbook-assets/js/app.js?v=<%= Lookbook::VERSION %>" defer></script>
17
+
18
+ <title><%= [@example&.label, @preview&.label, "Lookbook"].compact.join(" :: ") %></title>
19
+ </head>
20
+ <body class="text-gray-800 font-sans text-sm antialiased overflow-hidden">
21
+ <div
22
+ id="page"
23
+ x-data="page"
24
+ :style="`grid-template-columns: ${$store.sidebar.width}px 1px 1fr;`"
25
+ @popstate.window="update"
26
+ @resize.window="$store.layout.desktop = window.innerWidth >= $store.layout.desktopWidth"
27
+ class="md:grid w-screen h-screen"
28
+ >
29
+ <div class="h-full bg-gray-100 overflow-hidden flex flex-col" x-show="$store.layout.desktop || $store.sidebar.open" x-cloak>
30
+ <div class="bg-white h-10 border-b border-gray-300 flex flex-none items-center relative">
31
+ <button class="flex-none ml-4 md:hidden" @click="$store.sidebar.toggle">
32
+ <%= icon "x", size: 4, class: "hover:text-indigo-500" %>
33
+ </button>
34
+ <%= component "filter" %>
35
+ </div>
36
+ <div class="flex-grow overflow-y-auto">
37
+ <%= component "nav", items: @nav.items %>
38
+ </div>
39
+ </div>
40
+ <div
41
+ x-data="splitter('vertical', {minSize: $store.sidebar.minWidth})"
42
+ class="h-full gutter border-r border-gray-300 relative"
43
+ x-show="$store.layout.desktop"
44
+ x-effect="$store.sidebar.width = Math.min(splits[0] || $store.sidebar.width, $store.sidebar.maxWidth)"
45
+ x-cloak
46
+ >
47
+ <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>
48
+ </div>
49
+ <main id="main" class="h-full overflow-hidden w-full" x-show="$store.layout.desktop || !$store.sidebar.open" x-cloak>
50
+ <%= yield %>
51
+ </main>
52
+ </div>
53
+ </body>
54
+ </html>
@@ -0,0 +1,12 @@
1
+ <% if examples.many? %>
2
+ <% examples.each do |example| %>
3
+ <div style="all: unset; margin-bottom: 30px; display: block;">
4
+ <h6 style="all: unset; display: block; color: #999; font-family: sans-serif; font-size: 14px; margin-top: 0; margin-bottom: 10px;">
5
+ <%= example[:label] %>
6
+ </h6>
7
+ <%= example[:html] %>
8
+ </div>
9
+ <% end %>
10
+ <% else %>
11
+ <%= examples.first[:html] %>
12
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%
2
+ line_numbers ||= false
3
+ language ||= "html"
4
+ %>
5
+ <% code = capture do %><%= yield %><% end %>
6
+ <div class="code <%= "numbered" if line_numbers %> <%= classes %>">
7
+ <pre><code class="highlight"><%= highlight(code.strip, language, line_numbers: line_numbers) %></code></pre>
8
+ </div>
@@ -1,11 +1,10 @@
1
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"
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 transition"
3
+ x-data="copy('<%= target %>')"
4
4
  x-tooltip.theme.lookbook="done ? 'copied!' : 'copy to clipboard'"
5
5
  @click="save"
6
+ :class="{'!text-green-600 hover:text-green-600': done, 'hover:text-indigo-500': !done}"
6
7
  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>
8
+ <%= icon "${done ? 'check' : 'clipboard'}", size: 4 %>
10
9
  <div class="hidden" x-init="content = $el.innerText"><%== yield %></div>
11
10
  </button>
@@ -0,0 +1,15 @@
1
+ <div x-data="filter" class="flex items-center w-full">
2
+ <input
3
+ type="text"
4
+ class="text-sm px-4 h-10 w-full border-0 bg-transparent focus:outline-none outline-none focus:ring-0"
5
+ placeholder="Filter previews&hellip;"
6
+ x-ref="input"
7
+ x-model="$store.filter.raw"
8
+ x-effect="if (!$store.layout.desktop && $store.sidebar.open) focus()"
9
+ @keyup.stop="checkEsc"
10
+ @keyup.f.document="focus"
11
+ >
12
+ <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="clear">
13
+ <%= icon "x", size: 3, class: "hover:text-indigo-500" %>
14
+ </button>
15
+ </div>
@@ -1,8 +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')">
1
+ <header class="py-2 px-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10 select-none">
2
+ <button class="flex-none mr-3" x-show="!$store.layout.desktop" @click="$store.sidebar.toggle">
3
3
  <svg class="feather w-5 h-5 hover:text-indigo-500 transition">
4
4
  <use xlink:href="/lookbook-assets/feather-sprite.svg#menu" />
5
5
  </svg>
6
6
  </button>
7
7
  <%= yield %>
8
- </header>
8
+ </header>
@@ -0,0 +1,5 @@
1
+ <i class="transition block flex-none leading-none w-<%= size %> h-<%= size %> <%= classes %>">
2
+ <svg class="feather w-full h-full">
3
+ <use :href="`/lookbook-assets/feather-sprite.svg#<%= name %>`" x-cloak />
4
+ </svg>
5
+ </i>
@@ -0,0 +1,17 @@
1
+ <nav id="nav" x-data="nav" @popstate.window="setActive">
2
+ <% if items.any? %>
3
+ <ul x-ref="items" class="divide-y divide-gray-300">
4
+ <% items.each do |node| %>
5
+ <%= component "nav_#{node.type}", node: node %>
6
+ <% end %>
7
+ </ul>
8
+ <div class="p-4 text-center" x-show="empty" x-cloak>
9
+ <em class="text-gray-400">No matching previews found.</em>
10
+ </div>
11
+ <% else %>
12
+ <div class="p-4">
13
+ <h4 class="text-gray-500 mb-1">No previews found.</h4>
14
+ <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>
15
+ </div>
16
+ <% end %>
17
+ </nav>
@@ -0,0 +1,5 @@
1
+ <%= component "nav_group", node: node do %>
2
+ <% node.items.each do |item| %>
3
+ <%= component "nav_#{item.type}", node: item %>
4
+ <% end %>
5
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <li id="nav-group-<%= node.id %>" class="<%= classes %>" x-data="navGroup" :class="{hidden}" x-cloak>
2
+ <div @click="toggle" class="nav-toggle py-[5px]" style="padding-left: calc((<%= node.hierarchy_depth - 1 %> * 12px) + 0.5rem);">
3
+ <%= icon "${open ? 'chevron-down' : 'chevron-right'}", size: 3, class: "mr-1 text-gray-500" %>
4
+ <%= icon node.type == :preview ? "layers" : "folder", size: 3.5, class: "mr-1.5 text-indigo-500" %>
5
+ <div class="nav-label <%= "font-bold" if node.type == :preview %>" <% if node.type == :preview %> @click.stop="toggle(); navigateToFirstChild()"<% end %>>
6
+ <%= node.label %>
7
+ </div>
8
+ </div>
9
+ <ul
10
+ x-ref="items"
11
+ x-show="open"
12
+ id="nav-group-<%= node.id %>-children-<%= node.type == :preview ? node.get_examples.reject(&:hidden?).size : node.items.size %> %>"
13
+ x-cloak
14
+ >
15
+ <%= yield %>
16
+ </ul>
17
+ </li>
@@ -0,0 +1,21 @@
1
+ <%
2
+ path = show_path item.path
3
+ display ||= :item
4
+ label ||= item.label
5
+ %>
6
+ <li id="nav-item-<%= item.id %>" x-data="navItem(<%= item.matchers.to_json %>)" :class="{hidden}" data-path="<%= path %>" x-cloak>
7
+ <a href="<%= path %>"
8
+ class="nav-link pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
9
+ style="padding-left: calc((<%= depth - 1 %> * 12px) + 0.5rem);"
10
+ x-ref="link"
11
+ :class="{'!bg-indigo-100':active}"
12
+ @click.stop.prevent="navigate"
13
+ >
14
+ <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'">
15
+ <%= icon display == :node ? "layers" : "eye", size: 3.5, class: "group-hover:text-indigo-800" %>
16
+ </div>
17
+ <div class="truncate whitespace-nowrap select-none <%= "font-bold" if display == :node %>">
18
+ <%= label %>
19
+ </div>
20
+ </a>
21
+ </li>
@@ -0,0 +1,11 @@
1
+ <% examples = node.get_examples.reject(&:hidden?) %>
2
+ <% if examples.many? %>
3
+ <%= component "nav_group", node: node, path: show_path(examples.first.path) do %>
4
+ <% examples.each do |example| %>
5
+ <%= component "nav_item", item: example, depth: example.hierarchy_depth + 1 %>
6
+ <% end %>
7
+ <% end %>
8
+ <% else %>
9
+ <% example = examples.first %>
10
+ <%= component "nav_item", item: example, depth: example.hierarchy_depth, label: node.label, display: :node %>
11
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <div
2
+ class="px-4 py-3"
3
+ x-data="param"
4
+ <% if i == 0 %>x-effect="if ($store.inspector.panels.active === 'params') setFocus()"<% end %>
5
+ >
6
+ <div class="flex items-start max-w-[800px]">
7
+ <div class="w-[200px] flex-none py-2">
8
+ <label for="param-<%= param[:name] %>" class="font-bold">
9
+ <%= param[:name].titleize %>
10
+ </label>
11
+ </div>
12
+ <div class="flex-grow">
13
+ <%= render "lookbook/inputs/#{param[:input]}",
14
+ **param,
15
+ value: params.key?(param[:name]) ? params[param[:name]] : param[:default],
16
+ id: "#{@example.id}-param-#{param[:name]}-input"
17
+ %>
18
+ </div>
19
+ </div>
20
+ </div>
@@ -1,24 +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%'}`">
1
+ <div id="preview" class="h-full md:h-auto md:min-h-0 w-full bg-gray-50">
2
+ <div class="relative mx-auto bg-white h-full w-full" x-data="previewWindow" :style="`width: ${$store.layout.desktop ? $store.inspector.preview.width : '100%'}`" x-show="!showSource">
3
3
  <iframe seamless
4
4
  class="absolute h-full inset-0 w-full border-l border-gray-300 md:pr-4 md:-mx-px"
5
5
  src="<%= url_for lookbook.preview_path %>"
6
- <% if config.preview_srcdoc %>srcdoc="<%== @rendered_example %>"<% end %>
6
+ srcdoc="<%== srcdoc %>"
7
7
  frameborder="0"
8
- x-data="sizeObserver"
9
- x-effect="previewViewportWidth = observedWidth; previewViewportHeight = observedHeight;"
8
+ x-data="sizes"
9
+ x-effect="preview.width = width; preview.height = height;"
10
10
  ></iframe>
11
- <div class="absolute opacity-0 inset-0 pointer-events-none" :class="{ 'pointer-events-none': !$store.page.reflowing }"></div>
11
+ <div class="absolute opacity-0 inset-0 pointer-events-none" :class="{ 'pointer-events-none': !$store.layout.reflowing }"></div>
12
12
  <div
13
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
14
  style="touch-action: none"
15
15
  @pointerdown="onResizeStart"
16
16
  @dblclick="toggleFullWidth"
17
- x-show="$screen('md')"
17
+ x-show="$store.layout.desktop"
18
18
  >
19
19
  <svg class="h-4 w-4 text-gray-600 pointer-events-none" fill="currentColor" viewBox="0 0 24 24">
20
20
  <path d="M8 5h2v14H8zM14 5h2v14h-2z"></path>
21
21
  </svg>
22
22
  </div>
23
23
  </div>
24
- </div>
24
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="flex flex-col h-full w-full">
2
+ <%= component "header" %>
3
+ <div class="flex flex-col items-center justify-center h-full">
4
+ <div class="p-4 text-center">
5
+ <%= icon "layers", size: 10, class: "text-gray-300 mx-auto" %>
6
+ <h5 class="mt-4 text-gray-400 text-base">Select a preview to get started</h5>
7
+ </div>
8
+ </div>
9
+ </div>
@@ -3,6 +3,6 @@
3
3
  id="<%= id %>"
4
4
  class="form-input"
5
5
  @change.stop="update($el.name, $el.value)"
6
- data-param-input>
6
+ x-ref="input">
7
7
  <%= options_for_select(options, value) %>
8
8
  </select>
@@ -5,4 +5,4 @@
5
5
  name="<%= name %>"
6
6
  value="<%= value %>"
7
7
  @keyup.stop.debounce.400="if (validate()) update($el.name, $el.value)"
8
- data-param-input>
8
+ x-ref="input">
@@ -4,5 +4,5 @@
4
4
  name="<%= name %>"
5
5
  rows="4"
6
6
  @keyup.stop.debounce.300="update($el.name, $el.value)"
7
- data-param-input
7
+ x-ref="input"
8
8
  ><%= value %></textarea>
@@ -1,4 +1,4 @@
1
- <div id="<%= id %>" x-init="checked = <%= value == true || value == "true" ? "true" : "false" %>" data-param-input>
1
+ <div id="<%= id %>" x-init="checked = <%= value == true || value == "true" ? "true" : "false" %>" x-ref="input">
2
2
  <button type="button"
3
3
  class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400"
4
4
  :class="{'bg-indigo-500': checked, 'bg-gray-300': !checked}"
@@ -1,14 +1,12 @@
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
- <svg class="feather w-10 h-10 text-red-300 mx-auto">
4
- <use xlink:href="/lookbook-assets/feather-sprite.svg#alert-triangle" />
5
- </svg>
3
+ <%= icon "alert-triangle", size: 10, class: "text-red-300 mx-auto" %>
6
4
  <div class="mt-3 text-gray-700 max-w-xs">
7
5
  <% if @preview %>
8
6
  <div data-role="example-not-found">
9
7
  <h5 class="text-base">Not found</h5>
10
8
  <p class="mt-2 text-gray-400 text-sm">
11
- The "<span class="text-gray-500"><%= @preview.lookbook_label %></span>" preview does not have an example named "<span class="text-gray-500"><%= @example_name %></span>".
9
+ The "<span class="text-gray-500"><%= @preview.label %></span>" preview does not have an example named "<span class="text-gray-500"><%= @example_name %></span>".
12
10
  </p>
13
11
  </div>
14
12
  <% else %>