lookbook 0.2.4 → 0.3.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -0
  3. data/app/assets/lookbook/css/app.css +28 -0
  4. data/app/assets/lookbook/js/app.js +49 -24
  5. data/app/assets/lookbook/js/nav/leaf.js +20 -0
  6. data/app/assets/lookbook/js/nav/node.js +31 -0
  7. data/app/assets/lookbook/js/nav.js +36 -0
  8. data/app/assets/lookbook/js/page.js +33 -0
  9. data/app/assets/lookbook/js/utils/clipboard.js +13 -0
  10. data/app/assets/lookbook/js/utils/morph.js +16 -0
  11. data/app/assets/lookbook/js/{reloader.js → utils/reloader.js} +0 -0
  12. data/app/assets/lookbook/js/utils/screen.js +44 -0
  13. data/app/assets/lookbook/js/{size_observer.js → utils/size_observer.js} +1 -1
  14. data/app/assets/lookbook/js/{split.js → utils/split.js} +4 -4
  15. data/app/assets/lookbook/js/workbench/inspector.js +11 -0
  16. data/app/assets/lookbook/js/workbench/preview.js +39 -0
  17. data/app/assets/lookbook/js/workbench.js +14 -0
  18. data/app/controllers/lookbook/{browser_controller.rb → app_controller.rb} +58 -31
  19. data/app/helpers/lookbook/application_helper.rb +1 -1
  20. data/app/views/lookbook/_sidebar.html.erb +45 -0
  21. data/app/views/lookbook/_workbench.html.erb +12 -0
  22. data/app/views/lookbook/{browser → app}/error.html.erb +0 -0
  23. data/app/views/lookbook/app/index.html.erb +11 -0
  24. data/app/views/lookbook/{browser → app}/not_found.html.erb +1 -1
  25. data/app/views/lookbook/app/show.html.erb +1 -0
  26. data/app/views/lookbook/layouts/app.html.erb +17 -27
  27. data/app/views/lookbook/layouts/group.html.erb +6 -0
  28. data/app/views/lookbook/nav/_collection.html.erb +5 -0
  29. data/app/views/lookbook/nav/_node.html.erb +19 -0
  30. data/app/views/lookbook/nav/_preview.html.erb +29 -0
  31. data/app/views/lookbook/shared/_clipboard.html.erb +11 -0
  32. data/app/views/lookbook/shared/_header.html.erb +8 -0
  33. data/app/views/lookbook/workbench/_header.html.erb +37 -0
  34. data/app/views/lookbook/workbench/_inspector.html.erb +32 -0
  35. data/app/views/lookbook/workbench/_preview.html.erb +24 -0
  36. data/app/views/lookbook/workbench/inspector/_code.html.erb +3 -0
  37. data/app/views/lookbook/workbench/inspector/_notes.html.erb +24 -0
  38. data/app/views/lookbook/{partials → workbench}/inspector/_plain.html.erb +0 -0
  39. data/config/routes.rb +3 -3
  40. data/lib/lookbook/preview.rb +25 -3
  41. data/lib/lookbook/preview_controller.rb +6 -1
  42. data/lib/lookbook/preview_example.rb +3 -2
  43. data/lib/lookbook/preview_group.rb +37 -0
  44. data/lib/lookbook/taggable.rb +5 -1
  45. data/lib/lookbook/version.rb +1 -1
  46. data/lib/lookbook.rb +1 -0
  47. data/lib/tasks/lookbook_tasks.rake +1 -1
  48. data/public/lookbook-assets/app.css +258 -102
  49. data/public/lookbook-assets/app.js +964 -95
  50. data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
  51. metadata +52 -24
  52. data/app/assets/lookbook/js/preview.js +0 -76
  53. data/app/views/lookbook/browser/index.html.erb +0 -8
  54. data/app/views/lookbook/browser/show.html.erb +0 -33
  55. data/app/views/lookbook/partials/_preview.html.erb +0 -18
  56. data/app/views/lookbook/partials/_sidebar.html.erb +0 -21
  57. data/app/views/lookbook/partials/inspector/_code.html.erb +0 -1
  58. data/app/views/lookbook/partials/inspector/_inspector.html.erb +0 -43
  59. data/app/views/lookbook/partials/inspector/_prose.html.erb +0 -3
  60. data/app/views/lookbook/partials/nav/_collection.html.erb +0 -17
  61. data/app/views/lookbook/partials/nav/_label.html.erb +0 -13
  62. data/app/views/lookbook/partials/nav/_nav.html.erb +0 -27
  63. 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&hellip;">
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,3 +0,0 @@
1
- <div class="p-4 text-gray-600 prose prose-sm">
2
- <%= markdown(content) %>
3
- </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 %>