lookbook 0.2.1 → 0.3.0.beta.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|