lookbook 2.0.0 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/app/components/lookbook/embed/inspector/component.js +12 -0
- data/app/components/lookbook/embed_code_dropdown/component.css +8 -1
- data/app/components/lookbook/embed_code_dropdown/component.html.erb +58 -16
- data/app/components/lookbook/embed_code_dropdown/component.js +1 -24
- data/app/components/lookbook/embed_code_dropdown/component.rb +9 -0
- data/app/controllers/concerns/lookbook/with_preview_controller_concern.rb +9 -2
- data/assets/js/lib/lookbook.js +3 -1
- data/lib/lookbook/entities/collections/concerns/hierarchical_collection.rb +4 -1
- data/lib/lookbook/entities/collections/page_collection.rb +3 -1
- data/lib/lookbook/entities/collections/preview_collection.rb +2 -0
- data/lib/lookbook/services/entities/{entity_tree_builder.rb → page_tree_builder.rb} +10 -15
- data/lib/lookbook/services/entities/preview_tree_builder.rb +34 -0
- data/lib/lookbook/support/utils/path_utils.rb +2 -2
- data/lib/lookbook/tags/param_tag.rb +10 -3
- data/lib/lookbook/tags/yard_tag.rb +13 -9
- data/lib/lookbook/version.rb +1 -1
- data/public/lookbook-assets/css/lookbook.css +66 -20
- data/public/lookbook-assets/css/lookbook.css.map +1 -1
- data/public/lookbook-assets/js/index.js +186 -191
- data/public/lookbook-assets/js/index.js.map +1 -1
- data/public/lookbook-assets/js/lookbook-core.js +1 -1
- data/public/lookbook-assets/js/lookbook.js +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 367001bca8dd83ecf86085760aaa3befe93665913ada3602c893b5b768700b82
|
4
|
+
data.tar.gz: 733c573b3380072d0acecb9ce63e50f5a652d75ff649bda983c25935c938464b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afe34ddf105269ad47a25d1f0b208eb7dec1acf7858cde29c1aec9f3a59fcafb99ee17f4f48e7ba3b1aef59ac20aa50e2e0382560862cef334c25605f43130b6
|
7
|
+
data.tar.gz: ced6d61a75a6e614dba752215f9ff7226c1c10e2c569048a605af35ed5c165129581dd9e8f34362a0d52863fae04143e23ec2615d33afdc027437b4f8ab96393
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
<p>A UI development environment for Ruby on Rails applications.</p>
|
6
6
|
|
7
|
-
<p><strong><a href="https://lookbook.build">Documentation</a> | <a href="http://demo.lookbook.build/">Demo site</a></strong></p>
|
7
|
+
<p><strong><a href="https://lookbook.build">Documentation</a> | <a href="http://demo.lookbook.build/lookbook">Demo site</a></strong></p>
|
8
8
|
|
9
9
|
<p><a href="https://rubygems.org/gems/lookbook"><img src="https://img.shields.io/gem/v/lookbook" alt="Gem version"></a>
|
10
10
|
<a href="https://github.com/ViewComponent/lookbook/actions/workflows/ci.yml"><img src="https://github.com/ViewComponent/lookbook/actions/workflows/ci.yml/badge.svg" alt="CI status"></a></p>
|
@@ -13,6 +13,13 @@
|
|
13
13
|
|
14
14
|
---
|
15
15
|
|
16
|
+
<div align="center">
|
17
|
+
Lookbook combines a powerful <strong>component browser</strong> and <strong>preview system</strong> with an <strong>integrated documentation engine</strong> to help teams build robust, modular, maintainable user interfaces. <a href="https://lookbook.build"><strong>Learn more →</strong></a>
|
18
|
+
|
19
|
+
</div>
|
20
|
+
|
21
|
+
---
|
22
|
+
|
16
23
|
[![Lookbook UI](.github/assets/lookbook_ui.png)](http://lookbook.build/)
|
17
24
|
|
18
25
|
## Development
|
@@ -36,6 +36,18 @@ export default function embedInspectorComponent(id, embedStore) {
|
|
36
36
|
onResized({ height }) {
|
37
37
|
if (height) {
|
38
38
|
this.viewportHeight = height;
|
39
|
+
|
40
|
+
// Notify parent window of height resize so the parent window can implement
|
41
|
+
// its own iframe resize strategy if not using the Lookbook JS script.
|
42
|
+
// Uses Embedly-compatible postMessage format: https://docs.embed.ly/reference/provider-height-resizing
|
43
|
+
window.parent.postMessage(
|
44
|
+
JSON.stringify({
|
45
|
+
src: window.location.toString(),
|
46
|
+
context: "iframe.resize",
|
47
|
+
height,
|
48
|
+
}),
|
49
|
+
"*"
|
50
|
+
);
|
39
51
|
}
|
40
52
|
},
|
41
53
|
|
@@ -1,12 +1,19 @@
|
|
1
1
|
@layer components {
|
2
2
|
[data-component="embed-code-dropdown"] {
|
3
3
|
& [data-component="code"] {
|
4
|
-
@apply
|
4
|
+
@apply px-2 py-2 bg-lookbook-base-50 border border-dashed border-lookbook-divider;
|
5
5
|
}
|
6
6
|
|
7
7
|
& pre.code.highlight {
|
8
8
|
@apply overflow-hidden whitespace-normal p-0;
|
9
9
|
font-size: 11px;
|
10
10
|
}
|
11
|
+
|
12
|
+
.line-clamp-2 {
|
13
|
+
overflow: hidden;
|
14
|
+
display: -webkit-box;
|
15
|
+
-webkit-box-orient: vertical;
|
16
|
+
-webkit-line-clamp: 2;
|
17
|
+
}
|
11
18
|
}
|
12
19
|
}
|
@@ -1,22 +1,64 @@
|
|
1
|
-
<%= render_component_tag class:"p-3 w-[
|
2
|
-
<h4 class="text-[11px] uppercase tracking-wider mb-2 font-bold">Preview embed code</h4>
|
1
|
+
<%= render_component_tag class:"p-3 w-[360px] text-[11px] text-gray-600" do %>
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
</p>
|
3
|
+
<!-- p class="mb-3">
|
4
|
+
Embed this preview in Lookbook pages or externally using the code or iframe URL below.
|
5
|
+
</!-->
|
8
6
|
|
9
|
-
<div class="
|
10
|
-
|
7
|
+
<div class="mb-4 space-y-2">
|
8
|
+
|
9
|
+
<header class="flex items-center">
|
10
|
+
<h4 class=" uppercase tracking-wider font-bold">Embed Code</h4>
|
11
|
+
<span class="ml-auto" >
|
12
|
+
<a
|
13
|
+
href="https://lookbook.build/guide/sharing/embeds"
|
14
|
+
target="_blank"
|
15
|
+
title="Help"
|
16
|
+
class="text-lookbook-prose underline opacity-70 hover:opacity-100">
|
17
|
+
what's this?
|
18
|
+
</a>
|
19
|
+
</span>
|
20
|
+
</header>
|
21
|
+
|
22
|
+
<div class="relative group">
|
23
|
+
<%= code :html do %><%= embed_code %><% end %>
|
24
|
+
|
25
|
+
<span class="absolute top-px right-px bg-lookbook-base-50 rounded-md transition-opacity duration-300 opacity-0 group-hover:opacity-100">
|
26
|
+
<%= lookbook_render :copy_button, icon: :copy, tooltip: "Copy" do %>
|
27
|
+
<%= escape_once embed_code %>
|
28
|
+
<% end %>
|
29
|
+
</span>
|
30
|
+
</div>
|
31
|
+
|
11
32
|
</div>
|
12
33
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
34
|
+
<div class="space-y-2">
|
35
|
+
|
36
|
+
<header class="flex items-center">
|
37
|
+
<h4 class="text-[11px] uppercase tracking-wider font-bold">Embed URL</h4>
|
38
|
+
<span class="ml-auto" >
|
39
|
+
<a
|
40
|
+
href="https://lookbook.build/guide/sharing/embeds#other-services"
|
41
|
+
target="_blank"
|
42
|
+
title="Help"
|
43
|
+
class="text-lookbook-prose underline opacity-70 hover:opacity-100">
|
44
|
+
what's this?
|
45
|
+
</a>
|
46
|
+
</span>
|
47
|
+
</header>
|
48
|
+
|
49
|
+
<div class="relative group">
|
50
|
+
<div class="font-mono bg-lookbook-base-50 px-2 py-2 break-all border border-dashed border-lookbook-divider">
|
51
|
+
<span class="line-clamp-2">
|
52
|
+
<%= embed_url %>
|
53
|
+
</span>
|
54
|
+
</div>
|
55
|
+
|
56
|
+
<span class="absolute top-px right-px bg-lookbook-base-50 rounded-md transition-opacity duration-300 opacity-0 group-hover:opacity-100">
|
57
|
+
<%= lookbook_render :copy_button, icon: :copy, tooltip: "Copy" do %><%= embed_url %><% end %>
|
58
|
+
</span>
|
59
|
+
</div>
|
18
60
|
|
19
|
-
<div class="hidden" x-ref="copyTarget">
|
20
|
-
<%= escape_once embed_code %>
|
21
61
|
</div>
|
22
|
-
|
62
|
+
|
63
|
+
|
64
|
+
<% end %>
|
@@ -1,26 +1,3 @@
|
|
1
|
-
import { decodeEntities } from "@helpers/string";
|
2
|
-
|
3
1
|
export default function embedCodeDropdownComponent() {
|
4
|
-
|
5
|
-
|
6
|
-
return {
|
7
|
-
copied: false,
|
8
|
-
|
9
|
-
copyEmbedCode() {
|
10
|
-
this.$nextTick(async () => {
|
11
|
-
const content = decodeEntities(this.$refs.copyTarget.innerHTML.trim());
|
12
|
-
|
13
|
-
await window.navigator.clipboard.writeText(content);
|
14
|
-
this.copied = true;
|
15
|
-
|
16
|
-
if (copyTimeout) {
|
17
|
-
clearTimeout(copyTimeout);
|
18
|
-
}
|
19
|
-
|
20
|
-
copyTimeout = setTimeout(() => {
|
21
|
-
this.copied = false;
|
22
|
-
}, 2000);
|
23
|
-
});
|
24
|
-
},
|
25
|
-
};
|
2
|
+
return {};
|
26
3
|
}
|
@@ -33,6 +33,15 @@ module Lookbook
|
|
33
33
|
escape_once embed_tag
|
34
34
|
end
|
35
35
|
|
36
|
+
def embed_url
|
37
|
+
props = {
|
38
|
+
preview: preview_name,
|
39
|
+
scenario: target.name,
|
40
|
+
**external_embed_params.transform_keys { |k| k.tr("-", "_") }
|
41
|
+
}.to_json
|
42
|
+
"#{app_path}embed?props=#{CGI.escape(props)}"
|
43
|
+
end
|
44
|
+
|
36
45
|
private
|
37
46
|
|
38
47
|
def alpine_component
|
@@ -8,8 +8,15 @@ module Lookbook
|
|
8
8
|
# the request needs to look like it's coming from the host app,
|
9
9
|
# not the Lookbook engine. So we try to get the controller and action
|
10
10
|
# for the root path and use that as the 'fake' request context instead.
|
11
|
-
|
12
|
-
|
11
|
+
path_parameters = begin
|
12
|
+
request_path = main_app.respond_to?(:root_path) ? main_app.root_path : "/"
|
13
|
+
Rails.application.routes.recognize_path(request_path)
|
14
|
+
rescue
|
15
|
+
# Fix for authenticated devise paths
|
16
|
+
if main_app.respond_to?(:new_user_session_path)
|
17
|
+
Rails.application.routes.recognize_path(main_app.new_user_session_path)
|
18
|
+
end
|
19
|
+
end
|
13
20
|
|
14
21
|
preview_request = request.clone
|
15
22
|
preview_request.path_parameters = path_parameters if path_parameters.present?
|
data/assets/js/lib/lookbook.js
CHANGED
@@ -114,7 +114,9 @@ function guessBasePath() {
|
|
114
114
|
const scriptSrc = script.src;
|
115
115
|
|
116
116
|
if (scriptSrc && scriptSrc.includes("lookbook-assets")) {
|
117
|
-
return scriptSrc
|
117
|
+
return scriptSrc
|
118
|
+
.split("?")[0]
|
119
|
+
.replace("lookbook-assets/js/lookbook.js", "lookbook");
|
118
120
|
}
|
119
121
|
|
120
122
|
return `//${location.host}/lookbook`;
|
@@ -2,13 +2,16 @@ module Lookbook
|
|
2
2
|
module HierarchicalCollection
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
+
TREE_BUILDER = nil
|
6
|
+
|
5
7
|
included do
|
6
8
|
def entities
|
7
9
|
@_cache[:entities] ||= collect_ordered_entities(to_tree(include_hidden: true))
|
8
10
|
end
|
9
11
|
|
10
12
|
def to_tree(include_hidden: false)
|
11
|
-
|
13
|
+
cache_key = include_hidden ? :tree_with_hidden : :tree
|
14
|
+
@_cache[cache_key] ||= self.class::TREE_BUILDER.call(@entities, include_hidden: include_hidden)
|
12
15
|
end
|
13
16
|
|
14
17
|
protected
|
@@ -2,6 +2,8 @@ module Lookbook
|
|
2
2
|
class PageCollection < EntityCollection
|
3
3
|
include HierarchicalCollection
|
4
4
|
|
5
|
+
TREE_BUILDER = PageTreeBuilder
|
6
|
+
|
5
7
|
def load(page_paths, changes = nil)
|
6
8
|
file_paths = PageCollection.file_paths(page_paths)
|
7
9
|
reload_all(file_paths) # TODO: Fix incremental reloading
|
@@ -44,7 +46,7 @@ module Lookbook
|
|
44
46
|
|
45
47
|
def self.file_paths(directories)
|
46
48
|
directories.flat_map do |dir|
|
47
|
-
PathUtils.normalize_paths(Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*"].sort)
|
49
|
+
PathUtils.normalize_paths(Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*", "#{dir}/**/*.md"].sort)
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
@@ -1,36 +1,31 @@
|
|
1
1
|
module Lookbook
|
2
|
-
class
|
2
|
+
class PageTreeBuilder < Service
|
3
3
|
attr_reader :include_hidden
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(pages, include_hidden: false)
|
6
|
+
@pages = pages.to_a
|
7
7
|
@include_hidden = include_hidden
|
8
8
|
end
|
9
9
|
|
10
10
|
def call
|
11
11
|
root_node = TreeNode.new
|
12
|
-
|
12
|
+
pages.each do |page|
|
13
13
|
current_node = root_node
|
14
|
-
|
14
|
+
|
15
|
+
path_segments = parse_segments(page.relative_file_path)
|
15
16
|
path_segments.each.with_index(1) do |segment, i|
|
16
17
|
name, priority_prefix = segment
|
17
|
-
content =
|
18
|
+
content = page if page.depth == i # pages are always on the leaf nodes
|
18
19
|
|
19
20
|
current_node.add_child(name, content, priority: priority_prefix) unless current_node.has_child?(name)
|
20
21
|
current_node = current_node.get_child(name)
|
21
|
-
|
22
|
-
if content && content.type == :preview
|
23
|
-
content.visible_scenarios.each do |scenario|
|
24
|
-
current_node.add_child(scenario.name, scenario)
|
25
|
-
end
|
26
|
-
end
|
27
22
|
end
|
28
23
|
end
|
29
24
|
root_node
|
30
25
|
end
|
31
26
|
|
32
27
|
def parse_segments(path)
|
33
|
-
path.split("/").map do |segment|
|
28
|
+
path.to_s.split("/").map do |segment|
|
34
29
|
unless segment.start_with?(".")
|
35
30
|
priority, name = PriorityPrefixParser.call(segment)
|
36
31
|
[name, priority || 10000]
|
@@ -38,8 +33,8 @@ module Lookbook
|
|
38
33
|
end.compact
|
39
34
|
end
|
40
35
|
|
41
|
-
def
|
42
|
-
include_hidden ? @
|
36
|
+
def pages
|
37
|
+
include_hidden ? @pages : @pages.select(&:visible?)
|
43
38
|
end
|
44
39
|
end
|
45
40
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Lookbook
|
2
|
+
class PreviewTreeBuilder < Service
|
3
|
+
attr_reader :include_hidden
|
4
|
+
|
5
|
+
def initialize(previews, include_hidden: false)
|
6
|
+
@previews = previews.to_a
|
7
|
+
@include_hidden = include_hidden
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
root_node = TreeNode.new
|
12
|
+
previews.each do |preview|
|
13
|
+
current_node = root_node
|
14
|
+
|
15
|
+
path_segments = preview.logical_path.split("/")
|
16
|
+
path_segments.each.with_index(1) do |name, i|
|
17
|
+
content = preview if preview.depth == i # entities are always on the leaf nodes
|
18
|
+
|
19
|
+
current_node.add_child(name, content) unless current_node.has_child?(name)
|
20
|
+
current_node = current_node.get_child(name)
|
21
|
+
|
22
|
+
content&.visible_scenarios&.each do |scenario|
|
23
|
+
current_node.add_child(scenario.name, scenario)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
root_node
|
28
|
+
end
|
29
|
+
|
30
|
+
def previews
|
31
|
+
include_hidden ? @previews : @previews.select(&:visible?)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -14,11 +14,11 @@ module Lookbook
|
|
14
14
|
file_name = File.basename(path).split(".").first
|
15
15
|
|
16
16
|
segments = [*directory_path&.split("/"), file_name].compact
|
17
|
-
|
17
|
+
segments.map! do |segment|
|
18
18
|
PriorityPrefixParser.call(segment).last.tr("-", "_")
|
19
19
|
end
|
20
20
|
|
21
|
-
to_path(
|
21
|
+
to_path(segments)
|
22
22
|
end
|
23
23
|
|
24
24
|
def to_path(*args)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Lookbook
|
2
2
|
class ParamTag < YardTag
|
3
3
|
VALUE_TYPE_MATCHER = /^(\[\s?([A-Z]{1}\w+)\s?\])/
|
4
|
-
DESCRIPTION_MATCHER = /(?<=\s|^)"(
|
4
|
+
DESCRIPTION_MATCHER = /(?<=\s|^)"(.*?[^\\])"(?:\s|$)/
|
5
5
|
|
6
6
|
supports_options
|
7
7
|
|
@@ -45,19 +45,26 @@ module Lookbook
|
|
45
45
|
value_type = nil
|
46
46
|
description = nil
|
47
47
|
|
48
|
+
# Parse out YARD-style value type definition - i.e. [Boolean]
|
48
49
|
text.match(VALUE_TYPE_MATCHER) do |match_data|
|
49
50
|
value_type = match_data[2]
|
50
51
|
text.gsub!(VALUE_TYPE_MATCHER, "").strip!
|
51
52
|
end
|
52
53
|
|
54
|
+
# Parse and remove any options from string
|
55
|
+
text_with_options = text
|
56
|
+
_, text = parse_options(text)
|
57
|
+
options_str = text_with_options.sub(text, "")
|
58
|
+
|
59
|
+
# Parse description, if provided
|
53
60
|
text.match(DESCRIPTION_MATCHER) do |match_data|
|
54
61
|
description = match_data[1]
|
55
|
-
text.
|
62
|
+
text.sub!(DESCRIPTION_MATCHER, "").strip!
|
56
63
|
end
|
57
64
|
|
58
65
|
input, rest = text.split(" ", 2)
|
59
66
|
|
60
|
-
{input: input, value_type: value_type, description: description, rest: rest}
|
67
|
+
{input: input, value_type: value_type, description: description, rest: [rest, options_str].compact.join(" ")}
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
@@ -40,15 +40,7 @@ module Lookbook
|
|
40
40
|
|
41
41
|
def tag_parts
|
42
42
|
if @tag_parts.nil?
|
43
|
-
options, text =
|
44
|
-
TagOptionsParser.call(@text, {
|
45
|
-
file: host_file,
|
46
|
-
base_dir: host_file&.dirname,
|
47
|
-
eval_context: host_class_instance
|
48
|
-
})
|
49
|
-
else
|
50
|
-
[{}, @text]
|
51
|
-
end
|
43
|
+
options, text = parse_options(@text)
|
52
44
|
end
|
53
45
|
@tag_parts ||= {options: options, text: text}
|
54
46
|
end
|
@@ -85,5 +77,17 @@ module Lookbook
|
|
85
77
|
end
|
86
78
|
host_code_object&.path&.constantize
|
87
79
|
end
|
80
|
+
|
81
|
+
def parse_options(input)
|
82
|
+
if self.class.supports_options?
|
83
|
+
TagOptionsParser.call(input, {
|
84
|
+
file: host_file,
|
85
|
+
base_dir: host_file&.dirname,
|
86
|
+
eval_context: host_class_instance
|
87
|
+
})
|
88
|
+
else
|
89
|
+
[{}, @text]
|
90
|
+
end
|
91
|
+
end
|
88
92
|
end
|
89
93
|
end
|
data/lib/lookbook/version.rb
CHANGED