lookbook 0.6.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +233 -5
  3. data/app/assets/lookbook/css/app.css +15 -3
  4. data/app/assets/lookbook/js/app.js +8 -0
  5. data/app/assets/lookbook/js/components/app.js +55 -0
  6. data/app/assets/lookbook/js/components/embed.js +89 -0
  7. data/app/assets/lookbook/js/components/filter.js +13 -2
  8. data/app/assets/lookbook/js/components/nav-group.js +3 -6
  9. data/app/assets/lookbook/js/components/nav-item.js +3 -1
  10. data/app/assets/lookbook/js/components/nav.js +8 -15
  11. data/app/assets/lookbook/js/components/page.js +19 -41
  12. data/app/assets/lookbook/js/components/sidebar.js +16 -1
  13. data/app/assets/lookbook/js/components/splitter.js +1 -1
  14. data/app/assets/lookbook/js/components/tabs.js +3 -1
  15. data/app/assets/lookbook/js/embed.js +1 -0
  16. data/app/assets/lookbook/js/lib/split.js +0 -6
  17. data/app/assets/lookbook/js/stores/layout.js +3 -0
  18. data/app/assets/lookbook/js/stores/pages.js +5 -0
  19. data/app/assets/lookbook/js/stores/sidebar.js +2 -1
  20. data/app/controllers/lookbook/application_controller.rb +28 -0
  21. data/app/controllers/lookbook/page_controller.rb +20 -0
  22. data/app/controllers/lookbook/pages_controller.rb +40 -0
  23. data/app/controllers/lookbook/{app_controller.rb → previews_controller.rb} +39 -67
  24. data/app/helpers/lookbook/application_helper.rb +8 -61
  25. data/app/helpers/lookbook/component_helper.rb +40 -0
  26. data/app/helpers/lookbook/output_helper.rb +15 -0
  27. data/app/helpers/lookbook/page_helper.rb +46 -0
  28. data/app/helpers/lookbook/preview_helper.rb +1 -1
  29. data/app/views/layouts/lookbook/application.html.erb +30 -0
  30. data/app/views/layouts/lookbook/basic.html.erb +7 -0
  31. data/app/views/layouts/lookbook/preview.html.erb +5 -1
  32. data/app/views/layouts/lookbook/skeleton.html.erb +28 -0
  33. data/app/views/lookbook/components/_branding.html.erb +8 -0
  34. data/app/views/lookbook/components/_code.html.erb +2 -2
  35. data/app/views/lookbook/components/_copy_button.html.erb +11 -0
  36. data/app/views/lookbook/components/_drawer.html.erb +4 -16
  37. data/app/views/lookbook/components/_embed.html.erb +39 -0
  38. data/app/views/lookbook/components/_filter.html.erb +8 -5
  39. data/app/views/lookbook/components/_header.html.erb +2 -4
  40. data/app/views/lookbook/components/_icon.html.erb +2 -2
  41. data/app/views/lookbook/components/_nav.html.erb +7 -8
  42. data/app/views/lookbook/components/_nav_group.html.erb +1 -1
  43. data/app/views/lookbook/components/_nav_item.html.erb +4 -3
  44. data/app/views/lookbook/components/_nav_page.html.erb +22 -0
  45. data/app/views/lookbook/components/_nav_preview.html.erb +1 -1
  46. data/app/views/lookbook/components/_not_found.html.erb +11 -0
  47. data/app/views/lookbook/components/_param.html.erb +1 -1
  48. data/app/views/lookbook/components/_preview.html.erb +16 -10
  49. data/app/views/lookbook/components/_sidebar.html.erb +55 -0
  50. data/app/views/lookbook/pages/not_found.html.erb +15 -0
  51. data/app/views/lookbook/pages/show.html.erb +71 -0
  52. data/app/views/lookbook/previews/error.html.erb +1 -0
  53. data/app/views/lookbook/{inputs → previews/inputs}/_select.html.erb +0 -0
  54. data/app/views/lookbook/{inputs → previews/inputs}/_text.html.erb +0 -0
  55. data/app/views/lookbook/{inputs → previews/inputs}/_textarea.html.erb +0 -0
  56. data/app/views/lookbook/{inputs → previews/inputs}/_toggle.html.erb +0 -0
  57. data/app/views/lookbook/{not_found.html.erb → previews/not_found.html.erb} +2 -2
  58. data/app/views/lookbook/{panels → previews/panels}/_notes.html.erb +2 -2
  59. data/app/views/lookbook/{panels → previews/panels}/_output.html.erb +0 -0
  60. data/app/views/lookbook/{panels → previews/panels}/_params.html.erb +0 -0
  61. data/app/views/lookbook/{panels → previews/panels}/_preview.html.erb +1 -1
  62. data/app/views/lookbook/{panels → previews/panels}/_source.html.erb +0 -0
  63. data/app/views/lookbook/{show.html.erb → previews/show.html.erb} +0 -0
  64. data/config/routes.rb +9 -4
  65. data/lib/lookbook/code_formatter.rb +22 -1
  66. data/lib/lookbook/code_inspector.rb +73 -0
  67. data/lib/lookbook/collection.rb +110 -8
  68. data/lib/lookbook/engine.rb +55 -32
  69. data/lib/lookbook/features.rb +1 -1
  70. data/lib/lookbook/markdown.rb +31 -0
  71. data/lib/lookbook/page.rb +146 -0
  72. data/lib/lookbook/page_collection.rb +11 -0
  73. data/lib/lookbook/parser.rb +2 -0
  74. data/lib/lookbook/preview.rb +52 -56
  75. data/lib/lookbook/preview_collection.rb +15 -0
  76. data/lib/lookbook/preview_example.rb +27 -29
  77. data/lib/lookbook/preview_group.rb +12 -6
  78. data/lib/lookbook/utils.rb +74 -0
  79. data/lib/lookbook/version.rb +1 -1
  80. data/lib/lookbook.rb +18 -1
  81. data/public/lookbook-assets/css/app.css +1 -1
  82. data/public/lookbook-assets/css/app.css.map +1 -1
  83. data/public/lookbook-assets/js/app.js +1 -1
  84. data/public/lookbook-assets/js/app.js.map +1 -1
  85. data/public/lookbook-assets/js/embed.js +2 -0
  86. data/public/lookbook-assets/js/embed.js.map +1 -0
  87. metadata +44 -18
  88. data/app/assets/lookbook/js/lib/utils.js +0 -3
  89. data/app/views/layouts/lookbook/app.html.erb +0 -60
  90. data/app/views/lookbook/error.html.erb +0 -1
  91. data/lib/lookbook/taggable.rb +0 -46
@@ -1,47 +1,25 @@
1
- import createSocket from "../lib/socket";
2
-
3
- const morphOpts = {
4
- key(el) {
5
- return el.getAttribute("key") ? el.getAttribute("key") : el.id;
6
- },
7
- updating(el, toEl, childrenOnly, skip) {
8
- if (
9
- el.getAttribute &&
10
- el.getAttribute("data-morph-strategy") === "replace"
11
- ) {
12
- el.innerHTML = toEl.innerHTML;
13
- return skip();
14
- }
15
- },
16
- lookahead: true,
17
- };
18
-
19
1
  export default function page() {
20
2
  return {
21
- init() {
22
- const socket = createSocket(window.SOCKET_PATH);
23
- socket.addListener("Lookbook::ReloadChannel", () => this.refresh());
24
- },
25
- async update() {
26
- const response = await fetch(window.document.location);
27
- if (!response.ok) return window.location.reload();
28
- const html = await response.text();
29
- const newDoc = new DOMParser().parseFromString(html, "text/html");
30
- this.morph(newDoc);
31
- document.title = newDoc.title;
32
- },
33
- setLocation(loc) {
34
- const path = loc instanceof Event ? loc.currentTarget.href : loc;
35
- history.pushState({}, null, path);
36
- this.$dispatch("popstate");
3
+ init() {},
4
+ scrollToTop() {
5
+ this.$refs.scroller.scrollTop = 0;
37
6
  },
38
- refresh() {
39
- this.$dispatch("popstate");
40
- },
41
- morph(dom) {
42
- const pageHtml = dom.getElementById(this.$root.id).outerHTML;
43
- Alpine.morph(this.$root, pageHtml, morphOpts);
44
- this.$dispatch("page:morphed");
7
+ checkForNavigation(event) {
8
+ const link = event.target.closest("a[href]");
9
+ if (
10
+ link &&
11
+ !isExternalLink(link.href) &&
12
+ link.getAttribute("target") !== "_blank"
13
+ ) {
14
+ event.preventDefault();
15
+ this.setLocation(link.href);
16
+ }
45
17
  },
46
18
  };
47
19
  }
20
+
21
+ function isExternalLink(url) {
22
+ const tmp = document.createElement("a");
23
+ tmp.href = url;
24
+ return tmp.host !== window.location.host;
25
+ }
@@ -1,3 +1,18 @@
1
1
  export default function sidebar() {
2
- return {};
2
+ return {
3
+ init() {
4
+ this.$nextTick(() => this.setActiveNavItem());
5
+ },
6
+ setActiveNavItem() {
7
+ const target = this.$el.querySelector(
8
+ `[data-path="${window.location.pathname}"]`
9
+ );
10
+ this.$store.nav.active = target ? target.id : "";
11
+ },
12
+ setSplits(splits) {
13
+ if (splits.length) {
14
+ this.$store.sidebar.panelSplits = [splits[0] || 1.0, splits[2] || 1.0];
15
+ }
16
+ },
17
+ };
3
18
  }
@@ -11,7 +11,7 @@ export default function splitter(direction, props = {}) {
11
11
  minSize: props.minSize || 0,
12
12
  writeStyle() {},
13
13
  onDrag: (dir, track, style) => {
14
- this.splits = style.split(" ").map((num) => parseInt(num));
14
+ this.splits = style.split(" ").map((num) => parseFloat(num, 10));
15
15
  },
16
16
  onDragStart: () => {
17
17
  this.$store.layout.reflowing = true;
@@ -19,7 +19,9 @@ export default function tabs() {
19
19
  });
20
20
  },
21
21
  get tabs() {
22
- return Array.from(this.$refs.tabs.querySelectorAll(":scope > a"));
22
+ return Array.from(
23
+ this.$refs.tabs ? this.$refs.tabs.querySelectorAll(":scope > a") : []
24
+ );
23
25
  },
24
26
  get visibleTabCount() {
25
27
  let cumulativeWidth = 0;
@@ -0,0 +1 @@
1
+ import "iframe-resizer/js/iframeResizer.contentWindow";
@@ -11,11 +11,5 @@ export default function (element, props) {
11
11
  const splits = style.split(" ").map((num) => parseInt(num));
12
12
  props.onDrag(splits);
13
13
  },
14
- // onDragStart() {
15
- // this.reflowing = true;
16
- // },
17
- // onDragEnd() {
18
- // this.reflowing = false;
19
- // },
20
14
  });
21
15
  }
@@ -8,5 +8,8 @@ export default function createLayoutStore() {
8
8
  reflowing: false,
9
9
  desktop: true,
10
10
  desktopWidth: config.desktopWidth,
11
+ get mobile() {
12
+ return !this.desktop;
13
+ },
11
14
  };
12
15
  }
@@ -0,0 +1,5 @@
1
+ export default function createNavStore(Alpine) {
2
+ return {
3
+ embeds: {},
4
+ };
5
+ }
@@ -3,8 +3,9 @@ import config from "../config";
3
3
  export default function createSidebarStore(Alpine) {
4
4
  const { defaultWidth, minWidth, maxWidth } = config.sidebar;
5
5
  return {
6
- open: Alpine.$persist(false).as("sidebar-open"),
6
+ open: Alpine.$persist(true).as("sidebar-open"),
7
7
  width: Alpine.$persist(defaultWidth).as("sidebar-width"),
8
+ panelSplits: Alpine.$persist([1.0, 1.0]).as(`sidebar-panel-splits`),
8
9
  minWidth,
9
10
  maxWidth,
10
11
  toggle() {
@@ -0,0 +1,28 @@
1
+ module Lookbook
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ helper Lookbook::ApplicationHelper
6
+ helper Lookbook::OutputHelper
7
+ helper Lookbook::ComponentHelper
8
+
9
+ def self.controller_path
10
+ "lookbook"
11
+ end
12
+
13
+ def index
14
+ if feature_enabled? :pages
15
+ landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
16
+ if landing.present?
17
+ redirect_to page_path(landing.lookup_path)
18
+ end
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def feature_enabled?(feature)
25
+ Lookbook::Features.enabled?(feature)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module Lookbook
2
+ class PageController < ActionController::Base
3
+ helper Lookbook::ComponentHelper
4
+ helper Lookbook::PageHelper
5
+ helper Lookbook::OutputHelper
6
+
7
+ Lookbook.config.page_paths.each do |path|
8
+ prepend_view_path Rails.root.join(path)
9
+ end
10
+
11
+ def render_page(page, locals = {})
12
+ @page = page
13
+ @pages = Lookbook.pages
14
+ @next_page = @pages.find_next(@page)
15
+ @previous_page = @pages.find_previous(@page)
16
+ content = render_to_string inline: @page.content
17
+ @page.markdown? ? Lookbook::Markdown.render(content) : content
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ module Lookbook
2
+ class PagesController < ApplicationController
3
+ def self.controller_path
4
+ "lookbook/pages"
5
+ end
6
+
7
+ def index
8
+ landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
9
+ if landing.present?
10
+ redirect_to page_path landing.lookup_path
11
+ else
12
+ @title = "Not found"
13
+ render "not_found"
14
+ end
15
+ end
16
+
17
+ def show
18
+ @pages = Lookbook.pages
19
+ @page = @pages.find_by_path(params[:path])
20
+ if @page
21
+ @page_content = page_controller.render_page(@page)
22
+ @next_page = @pages.find_next(@page)
23
+ @previous_page = @pages.find_previous(@page)
24
+ @title = @page.title
25
+ else
26
+ @title = "Not found"
27
+ render "not_found"
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def page_controller
34
+ controller_class = Lookbook.config.page_controller.constantize
35
+ controller = controller_class.new
36
+ controller.request = request
37
+ controller
38
+ end
39
+ end
40
+ end
@@ -1,24 +1,25 @@
1
1
  module Lookbook
2
- class AppController < ActionController::Base
3
- EXCEPTIONS = [ViewComponent::PreviewTemplateError, ViewComponent::ComponentError, ViewComponent::TemplateError, ActionView::Template::Error]
4
-
5
- protect_from_forgery with: :exception
6
- helper Lookbook::ApplicationHelper
7
-
8
- before_action :find_preview, only: [:preview, :show]
9
- before_action :find_example, only: [:preview, :show]
10
- before_action :build_nav
2
+ class PreviewsController < ApplicationController
3
+ EXCEPTIONS = [
4
+ ViewComponent::PreviewTemplateError,
5
+ ViewComponent::ComponentError,
6
+ ViewComponent::TemplateError,
7
+ ActionView::Template::Error
8
+ ]
11
9
 
12
10
  def self.controller_path
13
- "lookbook"
11
+ "lookbook/previews"
14
12
  end
15
13
 
14
+ before_action :lookup_entities, only: [:preview, :show]
15
+ before_action :set_title
16
+
16
17
  def preview
17
18
  if @example
18
19
  set_params
19
20
  render html: render_examples(examples_data)
20
21
  else
21
- render "not_found"
22
+ render_in_layout "not_found"
22
23
  end
23
24
  end
24
25
 
@@ -30,33 +31,29 @@ module Lookbook
30
31
  @drawer_panels = drawer_panels.filter { |name, panel| panel[:show] }
31
32
  @preview_panels = preview_panels.filter { |name, panel| panel[:show] }
32
33
  rescue *EXCEPTIONS
33
- render "error"
34
+ render_in_layout "error"
34
35
  end
35
36
  else
36
- render "not_found"
37
+ render_in_layout "not_found"
37
38
  end
38
39
  end
39
40
 
40
41
  private
41
42
 
42
- def find_preview
43
- candidates = []
44
- params[:path].to_s.scan(%r{/|$}) { candidates << $` }
45
- match = candidates.reverse.detect { |candidate| Lookbook::Preview.exists?(candidate) }
46
- @preview = match ? Lookbook::Preview.find(match) : nil
47
- end
48
-
49
- def find_example
50
- @example = if @preview
51
- if params[:path] == @preview.lookbook_path
52
- redirect_to show_path "#{params[:path]}/#{@preview.lookbook_examples.first.name}"
53
- else
54
- @example_name = File.basename(params[:path])
55
- @preview.lookbook_example(@example_name)
43
+ def lookup_entities
44
+ @example = Lookbook.previews.find_example(params[:path])
45
+ if @example
46
+ @preview = @example.preview
47
+ if params[:path] == @preview&.lookup_path
48
+ redirect_to show_path "#{params[:path]}/#{@preview.default_example.name}"
56
49
  end
57
50
  end
58
51
  end
59
52
 
53
+ def set_title
54
+ @title = @example.present? ? [@example&.label, @preview&.label].compact.join(" :: ") : "Not found"
55
+ end
56
+
60
57
  def examples_data
61
58
  @examples_data ||= (@example.type == :group ? @example.examples : [@example]).map do |example|
62
59
  example_data(example)
@@ -72,23 +69,22 @@ module Lookbook
72
69
  html: preview_controller.render_example_to_string(@preview, example.name),
73
70
  source: has_template ? example.template_source(render_args[:template]) : example.method_source,
74
71
  source_lang: has_template ? example.template_lang(render_args[:template]) : example.source_lang,
75
- params: enabled?(:params) ? example.params : []
72
+ params: example.params
76
73
  }
77
74
  end
78
75
 
79
76
  def render_examples(examples)
80
- preview_controller.render_in_layout_to_string("layouts/lookbook/preview", {examples: examples}, @preview.lookbook_layout)
77
+ preview_controller.render_in_layout_to_string("layouts/lookbook/preview", {examples: examples}, @preview.layout)
81
78
  end
82
79
 
83
80
  def set_params
84
- if enabled?(:params)
85
- # cast known params to type
86
- @example.params.each do |param|
87
- if preview_controller.params.key?(param[:name])
88
- preview_controller.params[param[:name]] = Lookbook::Params.cast(preview_controller.params[param[:name]], param[:type])
89
- end
81
+ # cast known params to type
82
+ @example.params.each do |param|
83
+ if preview_controller.params.key?(param[:name])
84
+ preview_controller.params[param[:name]] = Lookbook::Params.cast(preview_controller.params[param[:name]], param[:type])
90
85
  end
91
86
  end
87
+
92
88
  # set display params
93
89
  preview_controller.params.merge!({
94
90
  lookbook: {
@@ -97,31 +93,11 @@ module Lookbook
97
93
  })
98
94
  end
99
95
 
100
- def build_nav
101
- @nav = Collection.new
102
- previews.reject { |p| p.hidden? }.each do |preview|
103
- current = @nav
104
- if preview.hierarchy_depth == 1
105
- current.add(preview)
106
- else
107
- preview.lookbook_parent_collections.each.with_index(1) do |name, i|
108
- target = current.get_or_create(name)
109
- if preview.hierarchy_depth == i + 1
110
- target.add(preview)
111
- else
112
- current = target
113
- end
114
- end
115
- end
116
- end
117
- @nav
118
- end
119
-
120
96
  def preview_panels
121
97
  {
122
98
  preview: {
123
99
  label: "Preview",
124
- template: "lookbook/panels/preview",
100
+ template: "lookbook/previews/panels/preview",
125
101
  srcdoc: Lookbook.config.preview_srcdoc ? render_examples(examples_data).gsub("\"", "&quot;") : nil,
126
102
  hotkey: "v",
127
103
  show: true,
@@ -130,7 +106,7 @@ module Lookbook
130
106
  },
131
107
  output: {
132
108
  label: "HTML",
133
- template: "lookbook/panels/output",
109
+ template: "lookbook/previews/panels/output",
134
110
  hotkey: "o",
135
111
  show: true,
136
112
  disabled: false,
@@ -143,7 +119,7 @@ module Lookbook
143
119
  {
144
120
  source: {
145
121
  label: "Source",
146
- template: "lookbook/panels/source",
122
+ template: "lookbook/previews/panels/source",
147
123
  hotkey: "s",
148
124
  show: true,
149
125
  disabled: false,
@@ -151,25 +127,21 @@ module Lookbook
151
127
  },
152
128
  notes: {
153
129
  label: "Notes",
154
- template: "lookbook/panels/notes",
130
+ template: "lookbook/previews/panels/notes",
155
131
  hotkey: "n",
156
132
  show: true,
157
133
  disabled: @examples.filter { |e| e[:notes].present? }.none?
158
134
  },
159
135
  params: {
160
136
  label: "Params",
161
- template: "lookbook/panels/params",
137
+ template: "lookbook/previews/panels/params",
162
138
  hotkey: "p",
163
- show: enabled?(:params),
139
+ show: true,
164
140
  disabled: @example.type == :group || @example.params.none?
165
141
  }
166
142
  }
167
143
  end
168
144
 
169
- def previews
170
- Lookbook::Preview.all.sort_by(&:label)
171
- end
172
-
173
145
  def preview_controller
174
146
  return @preview_controller if @preview_controller.present?
175
147
  controller_class = Lookbook.config.preview_controller.constantize
@@ -180,8 +152,8 @@ module Lookbook
180
152
  @preview_controller ||= controller
181
153
  end
182
154
 
183
- def enabled?(feature)
184
- Lookbook::Features.enabled?(feature)
155
+ def render_in_layout(path)
156
+ render "not_found", layout: params[:lookbook_embed] ? "lookbook/basic" : "lookbook/application"
185
157
  end
186
158
  end
187
159
  end
@@ -1,73 +1,20 @@
1
- require "redcarpet"
2
- require "rouge"
3
- require "htmlbeautifier"
4
-
5
1
  module Lookbook
6
2
  module ApplicationHelper
7
3
  def config
8
4
  Lookbook::Engine.config.lookbook
9
5
  end
10
6
 
11
- def markdown(text)
12
- markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, {
13
- tables: true,
14
- fenced_code_blocks: true,
15
- disable_indented_code_blocks: true
16
- })
17
- markdown.render(text).html_safe
18
- end
19
-
20
- def highlight(source, language, opts = {})
21
- formatter = Lookbook::CodeFormatter.new(opts)
22
- lexer = Rouge::Lexer.find(language)
23
- formatter.format(lexer.lex(source)).html_safe
24
- end
25
-
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)
7
+ def feature_enabled?(name)
8
+ Lookbook::Features.enabled?(name)
38
9
  end
39
10
 
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
11
+ def landing_path
12
+ landing = feature_enabled?(:pages) ? Lookbook.pages.find(&:landing) || Lookbook.pages.first : nil
13
+ if landing.present?
14
+ page_path(landing.lookup_path)
15
+ else
16
+ home_path
69
17
  end
70
- tag_values
71
18
  end
72
19
  end
73
20
  end
@@ -0,0 +1,40 @@
1
+ module Lookbook
2
+ module ComponentHelper
3
+ def render_component(name, **attrs, &block)
4
+ attrs[:classes] = class_names(attrs[:class])
5
+ render "lookbook/components/#{name.underscore}", **attrs.except(:class), &block
6
+ end
7
+
8
+ def icon(name, size: 4, **attrs)
9
+ render_component "icon", name: name, size: size, **attrs
10
+ end
11
+
12
+ if Rails.version.to_f < 6.1
13
+ def class_names(*args)
14
+ tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
15
+ safe_join(tokens, " ")
16
+ end
17
+ end
18
+
19
+ alias_method :component, :render_component
20
+
21
+ private
22
+
23
+ def build_tag_values(*args)
24
+ tag_values = []
25
+ args.each do |tag_value|
26
+ case tag_value
27
+ when Hash
28
+ tag_value.each do |key, val|
29
+ tag_values << key.to_s if val && key.present?
30
+ end
31
+ when Array
32
+ tag_values.concat build_tag_values(*tag_value)
33
+ else
34
+ tag_values << tag_value.to_s if tag_value.present?
35
+ end
36
+ end
37
+ tag_values
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Lookbook
2
+ module OutputHelper
3
+ def markdown(text = nil, &block)
4
+ Lookbook::Markdown.render(block ? capture(&block) : text)
5
+ end
6
+
7
+ def highlight(source, language, opts = {})
8
+ Lookbook::CodeFormatter.highlight(source, language, opts)
9
+ end
10
+
11
+ def beautify(source, language = "html")
12
+ Lookbook::CodeFormatter.beautify(source, language)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module Lookbook
2
+ module PageHelper
3
+ include Utils
4
+
5
+ def page_path(id)
6
+ page = id.is_a?(Page) ? id : Lookbook.pages.find(id)
7
+ lookbook.page_path page.lookup_path
8
+ end
9
+
10
+ def code(language = "ruby", line_numbers: false, &block)
11
+ render_component "code", language: language, line_numbers: line_numbers, &block
12
+ end
13
+
14
+ def embed(*args, params: {}, type: :preview, **opts)
15
+ return unless args.any?
16
+
17
+ @embed_counter ||= 0
18
+
19
+ preview_lookup = args.first.is_a?(Symbol) ? args.first : preview_class_name(args.first)
20
+ preview = Lookbook.previews.find(preview_lookup)
21
+
22
+ example = args[1] ? preview&.example(args[1]) : preview&.default_example
23
+
24
+ if example
25
+ @embed_counter += 1
26
+ render_component "embed", {
27
+ id: generate_id("embed", url_for, example.lookup_path, @embed_counter - 1),
28
+ example: example,
29
+ params: params,
30
+ opts: opts
31
+ }
32
+ else
33
+ embed_not_found
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def embed_not_found
40
+ render_component "not_found", {
41
+ title: "Preview not found",
42
+ text: "The preview may have been renamed or deleted."
43
+ }
44
+ end
45
+ end
46
+ end
@@ -1,7 +1,7 @@
1
1
  module Lookbook
2
2
  module PreviewHelper
3
3
  def lookbook_display(key, fallback = nil)
4
- params[:lookbook][:display][key.to_sym] || fallback
4
+ params.dig(:lookbook, :display, key.to_sym) || fallback
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,30 @@
1
+ <%= content_for :body do %>
2
+ <div
3
+ id="app"
4
+ x-data="app"
5
+ :style="`grid-template-columns: ${$store.sidebar.width}px 1px 1fr;`"
6
+ :class="{'grid': $store.sidebar.open && $store.layout.desktop}"
7
+ @popstate.window="await update(); $dispatch('page:changed')"
8
+ @refresh.window="update"
9
+ @resize.window="$store.layout.desktop = window.innerWidth >= $store.layout.desktopWidth"
10
+ class="w-screen h-screen">
11
+ <%= component "sidebar" %>
12
+ <div
13
+ x-data="splitter('vertical', {minSize: $store.sidebar.minWidth})"
14
+ class="h-full gutter border-r border-gray-300 relative"
15
+ x-show="$store.sidebar.open && $store.layout.desktop"
16
+ x-effect="$store.sidebar.width = Math.min(splits[0] || $store.sidebar.width, $store.sidebar.maxWidth)"
17
+ x-cloak>
18
+ <div class="w-[9px] h-full bg-transparent hover:bg-indigo-100 hover:bg-opacity-20 transition absolute top-0 bottom-0 -translate-x-1/2 cursor-[col-resize] z-10"></div>
19
+ </div>
20
+ <main
21
+ id="main"
22
+ class="h-full overflow-hidden w-full"
23
+ x-show="$store.layout.desktop || ($store.layout.mobile && !$store.sidebar.open)"
24
+ x-cloak>
25
+ <%= yield %>
26
+ </main>
27
+ </div>
28
+ <% end %>
29
+
30
+ <%= render template: "layouts/lookbook/skeleton" %>