lookbook 2.0.0 → 2.0.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.
- 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
|
[](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