lookbook 0.2.1 → 0.3.0.beta.0
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 +155 -77
- data/app/assets/lookbook/css/app.css +28 -0
- data/app/assets/lookbook/js/app.js +51 -24
- data/app/assets/lookbook/js/nav/leaf.js +20 -0
- data/app/assets/lookbook/js/nav/node.js +31 -0
- data/app/assets/lookbook/js/nav.js +36 -0
- data/app/assets/lookbook/js/page.js +33 -0
- data/app/assets/lookbook/js/utils/clipboard.js +13 -0
- data/app/assets/lookbook/js/utils/morph.js +16 -0
- data/app/assets/lookbook/js/{reloader.js → utils/reloader.js} +0 -0
- data/app/assets/lookbook/js/utils/screen.js +44 -0
- data/app/assets/lookbook/js/{size_observer.js → utils/size_observer.js} +1 -1
- data/app/assets/lookbook/js/{split.js → utils/split.js} +4 -4
- data/app/assets/lookbook/js/workbench/inspector.js +11 -0
- data/app/assets/lookbook/js/workbench/preview.js +39 -0
- data/app/assets/lookbook/js/workbench.js +14 -0
- data/app/controllers/lookbook/{browser_controller.rb → app_controller.rb} +58 -31
- data/app/helpers/lookbook/application_helper.rb +1 -1
- data/app/views/lookbook/_sidebar.html.erb +45 -0
- data/app/views/lookbook/_workbench.html.erb +12 -0
- data/app/views/lookbook/{browser → app}/error.html.erb +0 -0
- data/app/views/lookbook/app/index.html.erb +11 -0
- data/app/views/lookbook/{browser → app}/not_found.html.erb +1 -1
- data/app/views/lookbook/app/show.html.erb +1 -0
- data/app/views/lookbook/layouts/app.html.erb +22 -30
- data/app/views/lookbook/layouts/group.html.erb +6 -0
- data/app/views/lookbook/nav/_collection.html.erb +5 -0
- data/app/views/lookbook/nav/_node.html.erb +19 -0
- data/app/views/lookbook/nav/_preview.html.erb +29 -0
- data/app/views/lookbook/shared/_clipboard.html.erb +11 -0
- data/app/views/lookbook/shared/_header.html.erb +8 -0
- data/app/views/lookbook/workbench/_header.html.erb +37 -0
- data/app/views/lookbook/workbench/_inspector.html.erb +32 -0
- data/app/views/lookbook/workbench/_preview.html.erb +24 -0
- data/app/views/lookbook/workbench/inspector/_code.html.erb +3 -0
- data/app/views/lookbook/workbench/inspector/_notes.html.erb +24 -0
- data/app/views/lookbook/{partials → workbench}/inspector/_plain.html.erb +0 -0
- data/config/routes.rb +6 -4
- data/lib/lookbook/engine.rb +6 -4
- data/lib/lookbook/preview.rb +25 -3
- data/lib/lookbook/preview_controller.rb +6 -1
- data/lib/lookbook/preview_example.rb +3 -2
- data/lib/lookbook/preview_group.rb +37 -0
- data/lib/lookbook/taggable.rb +5 -1
- data/lib/lookbook/version.rb +1 -1
- data/lib/lookbook.rb +1 -0
- data/lib/tasks/lookbook_tasks.rake +1 -1
- data/public/lookbook-assets/app.css +267 -113
- data/public/lookbook-assets/app.js +1014 -116
- data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
- metadata +54 -27
- data/app/assets/lookbook/js/preview.js +0 -76
- data/app/views/lookbook/browser/index.html.erb +0 -8
- data/app/views/lookbook/browser/show.html.erb +0 -33
- data/app/views/lookbook/partials/_preview.html.erb +0 -18
- data/app/views/lookbook/partials/_sidebar.html.erb +0 -21
- data/app/views/lookbook/partials/inspector/_code.html.erb +0 -1
- data/app/views/lookbook/partials/inspector/_inspector.html.erb +0 -43
- data/app/views/lookbook/partials/inspector/_prose.html.erb +0 -3
- data/app/views/lookbook/partials/nav/_collection.html.erb +0 -17
- data/app/views/lookbook/partials/nav/_label.html.erb +0 -13
- data/app/views/lookbook/partials/nav/_nav.html.erb +0 -27
- data/app/views/lookbook/partials/nav/_preview.html.erb +0 -48
- data/config/lookbook_cable.yml +0 -8
@@ -1,7 +1,7 @@
|
|
1
1
|
import Split from "split-grid";
|
2
2
|
|
3
3
|
export default function (props) {
|
4
|
-
const
|
4
|
+
const page = Alpine.store("page");
|
5
5
|
return {
|
6
6
|
init() {
|
7
7
|
Split({
|
@@ -11,14 +11,14 @@ export default function (props) {
|
|
11
11
|
minSize: props.minSize,
|
12
12
|
writeStyle() {},
|
13
13
|
onDrag(dir, track, style) {
|
14
|
-
splits = style.split(" ").map((num) => parseInt(num));
|
14
|
+
const splits = style.split(" ").map((num) => parseInt(num));
|
15
15
|
props.onDrag(splits);
|
16
16
|
},
|
17
17
|
onDragStart() {
|
18
|
-
|
18
|
+
page.reflowing = true;
|
19
19
|
},
|
20
20
|
onDragEnd() {
|
21
|
-
|
21
|
+
page.reflowing = false;
|
22
22
|
},
|
23
23
|
});
|
24
24
|
},
|
@@ -0,0 +1,39 @@
|
|
1
|
+
export default function preview() {
|
2
|
+
const app = Alpine.store("page");
|
3
|
+
const preview = Alpine.store("preview");
|
4
|
+
return {
|
5
|
+
init() {
|
6
|
+
this.root = this.$el;
|
7
|
+
},
|
8
|
+
onResize(e) {
|
9
|
+
const size =
|
10
|
+
this.resizeStartSize - (this.resizeStartPosition - e.pageX) * 2;
|
11
|
+
const parentSize = this.root.parentElement.clientWidth;
|
12
|
+
const percentSize = (Math.round(size) / parentSize) * 100;
|
13
|
+
const minWidth = (300 / parentSize) * 100;
|
14
|
+
preview.width = `${Math.min(Math.max(percentSize, minWidth), 100)}%`;
|
15
|
+
},
|
16
|
+
onResizeStart(e) {
|
17
|
+
app.reflowing = true;
|
18
|
+
this.onResize = this.onResize.bind(this);
|
19
|
+
this.onResizeEnd = this.onResizeEnd.bind(this);
|
20
|
+
this.resizeStartPosition = e.pageX;
|
21
|
+
this.resizeStartSize = this.root.clientWidth;
|
22
|
+
window.addEventListener("pointermove", this.onResize);
|
23
|
+
window.addEventListener("pointerup", this.onResizeEnd);
|
24
|
+
},
|
25
|
+
onResizeEnd() {
|
26
|
+
window.removeEventListener("pointermove", this.onResize);
|
27
|
+
window.removeEventListener("pointerup", this.onResizeEnd);
|
28
|
+
app.reflowing = false;
|
29
|
+
},
|
30
|
+
toggleFullWidth() {
|
31
|
+
if (preview.width === "100%" && preview.lastWidth) {
|
32
|
+
preview.width = preview.lastWidth;
|
33
|
+
} else {
|
34
|
+
preview.lastWidth = preview.width;
|
35
|
+
preview.width = "100%";
|
36
|
+
}
|
37
|
+
},
|
38
|
+
};
|
39
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
export default function workbench() {
|
2
|
+
const inspector = Alpine.store("inspector");
|
3
|
+
return {
|
4
|
+
previewViewportHeight: 0,
|
5
|
+
previewViewportWidth: 0,
|
6
|
+
splitProps: {
|
7
|
+
direction: "vertical",
|
8
|
+
minSize: 200,
|
9
|
+
onDrag(splits) {
|
10
|
+
inspector.height = splits[2];
|
11
|
+
},
|
12
|
+
},
|
13
|
+
};
|
14
|
+
}
|
@@ -1,5 +1,7 @@
|
|
1
|
+
require "htmlbeautifier"
|
2
|
+
|
1
3
|
module Lookbook
|
2
|
-
class
|
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:
|
20
|
+
render html: rendered_example
|
21
21
|
else
|
22
|
-
render "
|
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
|
-
@
|
30
|
-
@
|
31
|
-
|
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("\"", """)
|
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 "
|
35
|
+
render "app/error"
|
43
36
|
end
|
44
37
|
else
|
45
|
-
render "
|
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
|
70
|
-
|
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
|
74
|
-
@
|
90
|
+
def rendered_example
|
91
|
+
if @example.type == :group
|
92
|
+
rendered = @example.examples.map do |example|
|
93
|
+
{
|
94
|
+
label: example.label,
|
95
|
+
content: preview_controller.render_example_to_string(@preview, example.name)
|
96
|
+
}
|
97
|
+
end
|
98
|
+
joined = render_to_string "layouts/group", locals: {items: rendered}, 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
|
-
|
88
|
-
|
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
|
-
|
95
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
125
|
+
template: "notes",
|
126
|
+
hotkey: "n",
|
127
|
+
items: @notes,
|
128
|
+
disabled: @notes.none?
|
102
129
|
}
|
103
130
|
}
|
104
131
|
}
|
@@ -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…"
|
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-full min-h-fill 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,49 +1,41 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
|
-
<html lang="en">
|
2
|
+
<html lang="en" class="h-fill min-h-fill">
|
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>">
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
<% if config.auto_refresh %>
|
12
|
+
<script>
|
13
|
+
window.SOCKET_PATH = "/lookbook/cable";
|
14
|
+
</script>
|
15
|
+
<% end %>
|
14
16
|
<script src="/lookbook-assets/app.js" defer></script>
|
15
17
|
|
16
18
|
<title><%= [@example&.label, @preview&.label, "Lookbook"].compact.join(" :: ") %></title>
|
17
19
|
</head>
|
18
|
-
<body class="text-gray-800 font-sans text-sm antialiased">
|
20
|
+
<body class="text-gray-800 font-sans text-sm antialiased h-fill min-h-fill overflow-hidden">
|
19
21
|
<div
|
20
|
-
|
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"
|
21
27
|
:style="`grid-template-columns: ${$store.nav.width}px 1px 1fr;`"
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
return fetch(document.location).then(async response => {
|
27
|
-
if (!response.ok) return location.reload();
|
28
|
-
const html = await response.text();
|
29
|
-
this.doc = (new DOMParser()).parseFromString(html, 'text/html');
|
30
|
-
$dispatch(eventName, {doc: this.doc})
|
31
|
-
});
|
32
|
-
}
|
33
|
-
}"
|
34
|
-
x-effect="document.title = doc.title; location = window.location.pathname"
|
35
|
-
@refresh.document="update('document:updated')"
|
36
|
-
@popstate.window="update('document:loaded')">
|
37
|
-
<%= render "partials/sidebar" %>
|
38
|
-
<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) }})">
|
39
|
-
<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>
|
28
|
+
class="md:grid w-screen w-fill h-full min-h-fill"
|
29
|
+
>
|
30
|
+
<div class="h-full min-h-fill bg-gray-100 overflow-hidden" x-show="$screen('md') || sidebarOpenMobile" x-cloak>
|
31
|
+
<%= render "./sidebar" %>
|
40
32
|
</div>
|
41
|
-
<div
|
42
|
-
|
33
|
+
<div x-data="split(splitProps)" class="h-fill gutter border-r border-gray-300 relative" x-show="$screen('md')" x-cloak>
|
34
|
+
<div class="w-[9px] h-full min-h-fill 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>
|
43
35
|
</div>
|
44
|
-
|
45
|
-
|
46
|
-
|
36
|
+
<main id="main" x-effect="render" class="h-fill overflow-hidden w-full" x-show="!$screen('md') || !sidebarOpenMobile" x-cloak>
|
37
|
+
<%= yield %>
|
38
|
+
</main>
|
47
39
|
</div>
|
48
40
|
</body>
|
49
41
|
</html>
|
@@ -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" style="<%= nav_padding_style(node.hierarchy_depth) %>">
|
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,29 @@
|
|
1
|
+
<%= render "./nav/node", node: node do %>
|
2
|
+
<% node.get_examples.each do |example| %>
|
3
|
+
<% path = show_path example.path %>
|
4
|
+
<li x-data="navLeaf" :class="{hidden}" x-init="matchers = <%= example.matchers.to_json %>; path = '<%= path %>'; setActive()" @popstate.window="setActive">
|
5
|
+
<a href="<%= path %>"
|
6
|
+
class="pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
|
7
|
+
style="<%= nav_padding_style(example.hierarchy_depth + 1) %>"
|
8
|
+
:class="{'!bg-indigo-100':active}"
|
9
|
+
@click.stop.prevent="navigate"
|
10
|
+
>
|
11
|
+
<div class="relative w-3.5 h-3.5 mr-1.5" :class="active ? 'text-gray-900' : 'text-indigo-500'">
|
12
|
+
<svg class="feather flex-none group-hover:text-indigo-800 transition <% if example.type == :group %> absolute top-[-2px] left-[-2px] w-3 h-3<% else %>w-3.5 h-3.5<% end %>">
|
13
|
+
<use xlink:href="/lookbook-assets/feather-sprite.svg#eye" />
|
14
|
+
</svg>
|
15
|
+
<% if example.type == :group %>
|
16
|
+
<svg class="feather w-3 h-3 flex-none group-hover:text-indigo-800 transition absolute top-[4px] left-[4px]">
|
17
|
+
<use xlink:href="/lookbook-assets/feather-sprite.svg#eye" />
|
18
|
+
</svg>
|
19
|
+
<% end %>
|
20
|
+
</div>
|
21
|
+
<div class="truncate whitespace-nowrap">
|
22
|
+
<%= example.label %>
|
23
|
+
</div>
|
24
|
+
</a>
|
25
|
+
</li>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
|
@@ -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 min-h-fill 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,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>
|
File without changes
|
data/config/routes.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
Lookbook::Engine.routes.draw do
|
2
|
-
|
2
|
+
if Lookbook.config.auto_refresh
|
3
|
+
mount Lookbook::Engine.websocket => Lookbook::Engine.cable.mount_path
|
4
|
+
end
|
3
5
|
|
4
|
-
root to: "
|
6
|
+
root to: "app#index", as: :home
|
5
7
|
|
6
|
-
get "/preview/*path", to: "
|
7
|
-
get "/*path", to: "
|
8
|
+
get "/preview/*path", to: "app#preview", as: :preview
|
9
|
+
get "/*path", to: "app#show", as: :show
|
8
10
|
end
|