lookbook 0.2.4 → 0.3.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 +81 -0
- data/app/assets/lookbook/css/app.css +28 -0
- data/app/assets/lookbook/js/app.js +49 -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 +39 -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 +16 -26
- data/app/views/lookbook/nav/_collection.html.erb +5 -0
- data/app/views/lookbook/nav/_leaf.html.erb +22 -0
- data/app/views/lookbook/nav/_node.html.erb +19 -0
- data/app/views/lookbook/nav/_preview.html.erb +11 -0
- data/app/views/lookbook/preview_group.html.erb +8 -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 +39 -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 +3 -3
- data/lib/lookbook/preview.rb +26 -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 +229 -99
- data/public/lookbook-assets/app.js +882 -56
- data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
- metadata +51 -22
- 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
@@ -1,76 +0,0 @@
|
|
1
|
-
export default function preview() {
|
2
|
-
const app = Alpine.store("app");
|
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(e) {
|
26
|
-
window.removeEventListener("pointermove", this.onResize);
|
27
|
-
window.removeEventListener("pointerup", this.onResizeEnd);
|
28
|
-
app.reflowing = false;
|
29
|
-
},
|
30
|
-
handle: {
|
31
|
-
["@pointerdown"]: "onResizeStart",
|
32
|
-
["@dblclick"]() {
|
33
|
-
if (preview.width === "100%" && preview.lastWidth) {
|
34
|
-
preview.width = preview.lastWidth;
|
35
|
-
} else {
|
36
|
-
preview.lastWidth = preview.width;
|
37
|
-
preview.width = "100%";
|
38
|
-
}
|
39
|
-
},
|
40
|
-
},
|
41
|
-
};
|
42
|
-
}
|
43
|
-
|
44
|
-
// export default function (dimension, store, { shrink = false, centered = false } = {}) {
|
45
|
-
// const position = (e) => (dimension == "height" ? e.pageY : e.pageX);
|
46
|
-
// const pane = {
|
47
|
-
// onResize(e) {
|
48
|
-
// let size =
|
49
|
-
// this.resizeStartSize -
|
50
|
-
// (shrink
|
51
|
-
// ? (this.resizeStartPosition - position(e)) * (centered ? 2 : 1)
|
52
|
-
// : (position(e) - this.resizeStartPosition) * (centered ? 2 : 1));
|
53
|
-
// const parentSize =
|
54
|
-
// dimension == "height"
|
55
|
-
// ? this.$el.parentElement.clientHeight
|
56
|
-
// : this.$el.parentElement.clientWidth;
|
57
|
-
// const percentSize = (Math.round(size) / parentSize) * 100;
|
58
|
-
// store[dimension] = `${Math.min(Math.max(percentSize, 0), 100)}%`;
|
59
|
-
// },
|
60
|
-
// onResizeStart(e) {
|
61
|
-
// Spruce.store("app").reflowing = true;
|
62
|
-
// this.resizeStartPosition = position(e);
|
63
|
-
// this.resizeStartSize = dimension == "height" ? this.$el.clientHeight : this.$el.clientWidth;
|
64
|
-
// this.onResize = this.onResize.bind(this);
|
65
|
-
// this.onResizeEnd = this.onResizeEnd.bind(this);
|
66
|
-
// window.addEventListener("pointermove", this.onResize);
|
67
|
-
// window.addEventListener("pointerup", this.onResizeEnd);
|
68
|
-
// },
|
69
|
-
// onResizeEnd() {
|
70
|
-
// Spruce.store("app").reflowing = false;
|
71
|
-
// window.removeEventListener("pointermove", this.onResize);
|
72
|
-
// window.removeEventListener("pointerup", this.onResizeEnd);
|
73
|
-
// },
|
74
|
-
// };
|
75
|
-
// return pane;
|
76
|
-
// };
|
@@ -1,8 +0,0 @@
|
|
1
|
-
<div class="flex flex-col items-center justify-center h-screen w-full">
|
2
|
-
<div class="p-4 text-center">
|
3
|
-
<svg class="feather w-10 h-10 text-gray-300 mx-auto">
|
4
|
-
<use xlink:href="#layers" />
|
5
|
-
</svg>
|
6
|
-
<h5 class="mt-4 text-gray-400 text-base">Select a preview to get started</h5>
|
7
|
-
</div>
|
8
|
-
</div>
|
@@ -1,33 +0,0 @@
|
|
1
|
-
<div class="bg-gray-50 h-full flex flex-col" x-data="{viewportHeight: 0, viewportWidth: 0}">
|
2
|
-
|
3
|
-
<header class="py-2 px-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10">
|
4
|
-
<div class="flex items-center ">
|
5
|
-
<div class="flex items-center space-x-1">
|
6
|
-
<strong><%= @preview.label %></strong>
|
7
|
-
<span>/</span>
|
8
|
-
<span><%= @example.label %></span>
|
9
|
-
</div>
|
10
|
-
<a href="<%= preview_path %>" class="block ml-2 text-gray-400 hover:text-indigo-800 transition" x-tooltip.theme.lookbook="`Open preview in new window`" target="_blank">
|
11
|
-
<svg class="feather w-3 h-3 ">
|
12
|
-
<use xlink:href="#external-link" />
|
13
|
-
</svg>
|
14
|
-
</a>
|
15
|
-
</div>
|
16
|
-
<div class="ml-auto flex text-xs font-monospace text-gray-700 space-x-1">
|
17
|
-
<span x-text="`${viewportWidth}px`"></span>
|
18
|
-
<span class="text-gray-500">x</span>
|
19
|
-
<span x-text="`${viewportHeight}px`"></span>
|
20
|
-
</div>
|
21
|
-
</header>
|
22
|
-
|
23
|
-
<div class="grid h-full" x-data :style="`grid-template-rows: 1fr 1px ${$store.inspector.height}px`">
|
24
|
-
<%= render "partials/preview" %>
|
25
|
-
<div class="w-full gutter border-t border-gray-300 relative" x-data="split({direction: 'vertical', minSize: 200, onDrag: (splits) => { $store.inspector.height = splits[2] }})">
|
26
|
-
<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>
|
27
|
-
</div>
|
28
|
-
<% if @inspector %>
|
29
|
-
<%= render "partials/inspector/inspector" %>
|
30
|
-
<% end %>
|
31
|
-
</div>
|
32
|
-
|
33
|
-
</div>
|
@@ -1,18 +0,0 @@
|
|
1
|
-
<div class="w-full bg-gray-50">
|
2
|
-
<div class="h-full relative mx-auto bg-white" x-data="preview" :style="`width: ${$store.preview.width}`">
|
3
|
-
<iframe
|
4
|
-
src="<%= url_for lookbook.preview_path %>"
|
5
|
-
<% if config.preview_srcdoc %>srcdoc="<%== @preview_srcdoc %>"<% end %>
|
6
|
-
frameborder="0"
|
7
|
-
class="h-full w-full border-l border-gray-300 pr-4 -mx-px"
|
8
|
-
x-data="sizeObserver"
|
9
|
-
x-effect="viewportWidth = observedWidth; viewportHeight = observedHeight;"
|
10
|
-
seamless></iframe>
|
11
|
-
<div class="absolute opacity-0 inset-0 pointer-events-none" :class="{ 'pointer-events-none': !$store.app.reflowing }"></div>
|
12
|
-
<div x-bind="handle" 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" style="touch-action: none">
|
13
|
-
<svg class="h-4 w-4 text-gray-600 pointer-events-none" fill="currentColor" viewBox="0 0 24 24">
|
14
|
-
<path d="M8 5h2v14H8zM14 5h2v14h-2z"></path>
|
15
|
-
</svg>
|
16
|
-
</div>
|
17
|
-
</div>
|
18
|
-
</div>
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<div class="h-screen overflow-hidden" x-data="{open: $store.nav.open, isOpen(id){ return this.open[id] }}" @popstate.window="$store.nav.active = document.location.pathname">
|
2
|
-
<div class="bg-white h-10 border-b border-gray-300 flex items-center relative">
|
3
|
-
<input type="text"
|
4
|
-
class="text-sm px-4 h-10 w-full border-0 bg-transparent focus:outline-none outline-none focus:ring-0"
|
5
|
-
x-model="$store.nav.filter"
|
6
|
-
@keyup.stop="if ($event.key === 'Escape') $store.nav.filter = ''"
|
7
|
-
placeholder="Filter previews…">
|
8
|
-
<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="$store.nav.filter = ''">
|
9
|
-
<svg class="feather w-3 h-3">
|
10
|
-
<use xlink:href="#x" />
|
11
|
-
</svg>
|
12
|
-
</button>
|
13
|
-
</div>
|
14
|
-
<div
|
15
|
-
class="bg-gray-100 relative overflow-auto"
|
16
|
-
style="height: calc(100% - 40px)"
|
17
|
-
@scroll.passive="$store.nav.scrollTop = $event.currentTarget.scrollTop;"
|
18
|
-
x-init="setTimeout(() => {$el.scrollTop = $store.nav.scrollTop}, 30)">
|
19
|
-
<%= render "partials/nav/nav" %>
|
20
|
-
</div>
|
21
|
-
</div>
|
@@ -1 +0,0 @@
|
|
1
|
-
<pre class="h-full"><code class="h-full p-4 block highlight font-monospace" style="background-image: none"><%== highlight(content.strip, lang[:name]) %></code></pre>
|
@@ -1,43 +0,0 @@
|
|
1
|
-
<div id="inspector" class="bg-white w-full overflow-hidden flex flex-col">
|
2
|
-
<div class="px-4 border-b border-gray-200 flex items-center flex-none select-none cursor-[ns-resize]">
|
3
|
-
<nav class="-mb-px flex space-x-8 cursor-auto">
|
4
|
-
<% @inspector[: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 ': $store.inspector.active === '<%= key %>',
|
10
|
-
'border-transparent text-gray-500 hover:text-gray-700': $store.inspector.active !== '<%= key %>'
|
11
|
-
}"
|
12
|
-
@click.stop.prevent="$store.inspector.active = '<%= key %>'"
|
13
|
-
>
|
14
|
-
<%== props[:label] %>
|
15
|
-
</a>
|
16
|
-
<% end %>
|
17
|
-
</nav>
|
18
|
-
</div>
|
19
|
-
<div class="flex-auto overflow-auto bg-gray-50">
|
20
|
-
<% @inspector[:panes].each do |key, props| %>
|
21
|
-
<div
|
22
|
-
class="flex flex-col h-full relative"
|
23
|
-
x-show="$store.inspector.active === '<%= key %>'" x-cloak>
|
24
|
-
<% if props[:clipboard].present? %>
|
25
|
-
<button
|
26
|
-
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"
|
27
|
-
x-data="{content: null, done: false}"
|
28
|
-
x-tooltip.theme.lookbook="done ? 'cpied!' : 'copy to clipboard'"
|
29
|
-
data-tippy-placement="left"
|
30
|
-
@click="$clipboard(content); done = true; setTimeout(() => {done = false}, 1000)">
|
31
|
-
<svg class="feather h-4 w-4 ">
|
32
|
-
<use x-bind:href="done ? '#check' : '#clipboard'" />
|
33
|
-
</svg>
|
34
|
-
<div class="hidden" x-init="content = $el.innerText"><%= h props[:clipboard].strip %></div>
|
35
|
-
</button>
|
36
|
-
<% end %>
|
37
|
-
<div class="flex flex-col h-full overflow-auto">
|
38
|
-
<%= render "partials/inspector/#{props[:template]}", key: key, **props %>
|
39
|
-
</div>
|
40
|
-
</div>
|
41
|
-
<% end %>
|
42
|
-
</div>
|
43
|
-
</div>
|
@@ -1,17 +0,0 @@
|
|
1
|
-
<li <% if collection.hierarchy_depth == 1 %> class="py-1 border-b border-gray-300 cursor-pointer"<% end %> data-type="collection"
|
2
|
-
x-data="{id: '<%= collection.id %>', hidden: false}"
|
3
|
-
@filtered="$nextTick(() => {hidden = !$el.querySelectorAll('[data-type=preview]:not(.hidden)').length})"
|
4
|
-
:class="{hidden}">
|
5
|
-
<div @click="open[id] = !open[id]" style="<%= nav_padding_style(collection.hierarchy_depth) %>">
|
6
|
-
<%= render "partials/nav/label", text: collection.label, icon: "folder" %>
|
7
|
-
</div>
|
8
|
-
<ul x-show="isOpen(id)" x-cloak>
|
9
|
-
<% collection.items.each do |item| %>
|
10
|
-
<% if item.type == :collection %>
|
11
|
-
<%= render "partials/nav/collection", collection: item %>
|
12
|
-
<% elsif item.type == :preview %>
|
13
|
-
<%= render "partials/nav/preview", preview: item %>
|
14
|
-
<% end %>
|
15
|
-
<% end %>
|
16
|
-
</ul>
|
17
|
-
</li>
|
@@ -1,13 +0,0 @@
|
|
1
|
-
<% bold ||= false %>
|
2
|
-
<div class="flex items-center cursor-pointer pr-3 py-[4px]">
|
3
|
-
<svg class="feather w-3 h-3 mr-1 text-gray-500 flex-none">
|
4
|
-
<use xlink:href="#chevron-down" x-show="isOpen(id)" x-cloak />
|
5
|
-
<use xlink:href="#chevron-right" x-show="!isOpen(id)" />
|
6
|
-
</svg>
|
7
|
-
<svg class="feather h-3.5 w-3.5 mr-1.5 flex-none text-indigo-500">
|
8
|
-
<use xlink:href="#<%= icon %>" />
|
9
|
-
</svg>
|
10
|
-
<div class="truncate whitespace-nowrap text-left <%= "font-bold" if bold %>">
|
11
|
-
<%= text %>
|
12
|
-
</div>
|
13
|
-
</div>
|
@@ -1,27 +0,0 @@
|
|
1
|
-
<nav id="nav"
|
2
|
-
class="select-none"
|
3
|
-
x-data="{empty: false}"
|
4
|
-
@filtered="$nextTick(() => {
|
5
|
-
setTimeout(() => {empty = !$el.querySelectorAll('#nav > ul > li:not(.hidden)').length}, 0)
|
6
|
-
})"
|
7
|
-
@document:updated.document="$el.innerHTML = $event.detail.doc.getElementById('nav').innerHTML">
|
8
|
-
<% if @nav.items.any? %>
|
9
|
-
<ul>
|
10
|
-
<% @nav.items.each do |item| %>
|
11
|
-
<% if item.type == :collection %>
|
12
|
-
<%= render "partials/nav/collection", collection: item %>
|
13
|
-
<% else %>
|
14
|
-
<%= render "partials/nav/preview", preview: item %>
|
15
|
-
<% end %>
|
16
|
-
<% end %>
|
17
|
-
</ul>
|
18
|
-
<div class="p-4 text-center" x-show="empty" x-cloak>
|
19
|
-
<em class="text-gray-400">No matching previews found.</em>
|
20
|
-
</div>
|
21
|
-
<% else %>
|
22
|
-
<div class="p-4">
|
23
|
-
<h4 class="text-gray-500 mb-1">No previews found.</h4>
|
24
|
-
<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>
|
25
|
-
</div>
|
26
|
-
<% end %>
|
27
|
-
</nav>
|
@@ -1,48 +0,0 @@
|
|
1
|
-
<% examples = preview.get_examples %>
|
2
|
-
<li
|
3
|
-
data-type="preview"
|
4
|
-
x-data="{
|
5
|
-
id: '<%= preview.lookbook_id %>',
|
6
|
-
hidden: false,
|
7
|
-
examples: <%= examples.map { |e| {id: e.id, matched: true, filter: e.filter_match_string} }.to_json %>,
|
8
|
-
update(){
|
9
|
-
this.examples.forEach(e => { e.matched = $store.nav.shouldDisplay(e.filter) });
|
10
|
-
this.hidden = !this.examples.filter(e => e.matched).length;
|
11
|
-
this.$dispatch('filtered');
|
12
|
-
}
|
13
|
-
}"
|
14
|
-
x-init="
|
15
|
-
$watch('$store.nav.filter', (value) => update()); update()"
|
16
|
-
:class="{hidden}"
|
17
|
-
<% if preview.hierarchy_depth == 1 %> class="py-1 border-b border-gray-300 cursor-pointer"<% end %>>
|
18
|
-
<div @click="open[id] = !open[id]" style="<%= nav_padding_style(preview.hierarchy_depth) %>">
|
19
|
-
<%= render "partials/nav/label", text: preview.label, icon: "layers", bold: true %>
|
20
|
-
</div>
|
21
|
-
<ul x-show="isOpen(id)" x-cloak>
|
22
|
-
<% examples.each do |example| %>
|
23
|
-
<li x-show="examples.find(e => e.id === '<%= example.id %>').matched">
|
24
|
-
<a
|
25
|
-
x-data="{path: '<%= show_path example.path %>'}"
|
26
|
-
:href="path"
|
27
|
-
class="pr-3 py-[3px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
|
28
|
-
:class="{'!bg-indigo-100': location === path}"
|
29
|
-
style="<%= nav_padding_style(example.hierarchy_depth + 1) %>"
|
30
|
-
@click.stop.prevent="history.pushState({}, null, $event.currentTarget.href); $dispatch('popstate');"
|
31
|
-
>
|
32
|
-
<div :class="{'text-gray-900': location === path, 'text-indigo-500': location !== path}">
|
33
|
-
<svg class="feather w-3.5 h-3.5 mr-1.5 flex-none group-hover:text-indigo-800 transition">
|
34
|
-
<use xlink:href="#eye" />
|
35
|
-
</svg>
|
36
|
-
</div>
|
37
|
-
<div class="truncate whitespace-nowrap">
|
38
|
-
<%= example.label %>
|
39
|
-
</div>
|
40
|
-
</a>
|
41
|
-
</li>
|
42
|
-
<% end %>
|
43
|
-
</ul>
|
44
|
-
</li>
|
45
|
-
<% if false %>
|
46
|
-
|
47
|
-
|
48
|
-
<% end %>
|