lookbook 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -21
  3. data/app/assets/lookbook/css/app.css +24 -13
  4. data/app/assets/lookbook/css/tooltip_theme.css +28 -0
  5. data/app/assets/lookbook/js/app.js +4 -0
  6. data/app/assets/lookbook/js/components/code.js +5 -0
  7. data/app/assets/lookbook/js/components/copy.js +4 -2
  8. data/app/assets/lookbook/js/components/filter.js +1 -1
  9. data/app/assets/lookbook/js/components/inspector.js +54 -9
  10. data/app/assets/lookbook/js/components/nav-group.js +1 -1
  11. data/app/assets/lookbook/js/components/nav-item.js +1 -0
  12. data/app/assets/lookbook/js/components/nav.js +1 -1
  13. data/app/assets/lookbook/js/components/page.js +17 -5
  14. data/app/assets/lookbook/js/components/param.js +23 -7
  15. data/app/assets/lookbook/js/components/preview-window.js +95 -26
  16. data/app/assets/lookbook/js/components/tabs.js +50 -0
  17. data/app/assets/lookbook/js/config.js +11 -4
  18. data/app/assets/lookbook/js/lib/socket.js +1 -1
  19. data/app/assets/lookbook/js/stores/inspector.js +13 -5
  20. data/app/controllers/lookbook/app_controller.rb +23 -9
  21. data/app/views/layouts/lookbook/app.html.erb +9 -3
  22. data/app/views/lookbook/components/_code.html.erb +6 -1
  23. data/app/views/lookbook/components/_drawer.html.erb +124 -0
  24. data/app/views/lookbook/components/_filter.html.erb +1 -1
  25. data/app/views/lookbook/components/_header.html.erb +2 -2
  26. data/app/views/lookbook/components/_nav.html.erb +1 -1
  27. data/app/views/lookbook/components/_nav_group.html.erb +11 -14
  28. data/app/views/lookbook/components/_nav_item.html.erb +17 -15
  29. data/app/views/lookbook/components/_nav_preview.html.erb +4 -2
  30. data/app/views/lookbook/components/_param.html.erb +6 -5
  31. data/app/views/lookbook/components/_preview.html.erb +67 -20
  32. data/app/views/lookbook/inputs/_select.html.erb +2 -3
  33. data/app/views/lookbook/inputs/_text.html.erb +3 -3
  34. data/app/views/lookbook/inputs/_textarea.html.erb +3 -3
  35. data/app/views/lookbook/inputs/_toggle.html.erb +5 -5
  36. data/app/views/lookbook/panels/_notes.html.erb +1 -1
  37. data/app/views/lookbook/panels/_output.html.erb +2 -2
  38. data/app/views/lookbook/panels/_params.html.erb +1 -1
  39. data/app/views/lookbook/panels/_preview.html.erb +52 -0
  40. data/app/views/lookbook/panels/_source.html.erb +2 -2
  41. data/app/views/lookbook/show.html.erb +22 -88
  42. data/lib/lookbook/code_formatter.rb +3 -3
  43. data/lib/lookbook/features.rb +1 -1
  44. data/lib/lookbook/preview.rb +1 -1
  45. data/lib/lookbook/version.rb +1 -1
  46. data/public/lookbook-assets/css/app.css +3 -1
  47. data/public/lookbook-assets/css/app.css.map +1 -1
  48. data/public/lookbook-assets/js/app.js +1 -1
  49. data/public/lookbook-assets/js/app.js.map +1 -1
  50. metadata +6 -3
  51. data/app/views/lookbook/components/_copy.html.erb +0 -10
@@ -0,0 +1,50 @@
1
+ import tippy from "tippy.js";
2
+
3
+ export default function tabs() {
4
+ return {
5
+ width: 0,
6
+ tabsWidth: 0,
7
+ init() {
8
+ const ro = new ResizeObserver((entries) => {
9
+ this.width = Math.round(entries[0].contentRect.width);
10
+ });
11
+ ro.observe(this.$refs.tabs);
12
+ this.dropdown = tippy(this.$refs.toggle, {
13
+ allowHTML: true,
14
+ interactive: true,
15
+ trigger: "click",
16
+ placement: "bottom-end",
17
+ theme: "menu",
18
+ content: this.$refs.dropdown,
19
+ });
20
+ },
21
+ get tabs() {
22
+ return Array.from(this.$refs.tabs.querySelectorAll(":scope > a"));
23
+ },
24
+ get visibleTabCount() {
25
+ let cumulativeWidth = 0;
26
+ for (let i = 0; i < this.tabs.length; i++) {
27
+ const el = this.tabs[i];
28
+ const margin = parseInt(
29
+ window
30
+ .getComputedStyle(el)
31
+ .getPropertyValue("margin-left")
32
+ .replace("px", ""),
33
+ 10
34
+ );
35
+ cumulativeWidth += el.clientWidth + margin;
36
+ if (cumulativeWidth > this.width) {
37
+ this.tabsWidth = cumulativeWidth - el.clientWidth;
38
+ return i;
39
+ }
40
+ }
41
+ return this.tabs.length;
42
+ },
43
+ get hiddenTabs() {
44
+ return this.tabs.slice(this.visibleTabCount, -1);
45
+ },
46
+ hideDropdown() {
47
+ this.dropdown.hide();
48
+ },
49
+ };
50
+ }
@@ -3,12 +3,19 @@ export default {
3
3
  sidebar: {
4
4
  defaultWidth: 280,
5
5
  minWidth: 200,
6
- maxWidth: 500,
6
+ maxWidth: 350,
7
7
  },
8
8
  inspector: {
9
- tabs: {
10
- default: "source",
11
- defaultHeight: 200,
9
+ drawer: {
10
+ orientation: "horizontal",
11
+ defaultPanel: "source",
12
+ defaultHeight: 300,
13
+ defaultWidth: 500,
14
+ minWidth: 350,
15
+ minHeight: 200,
16
+ },
17
+ preview: {
18
+ defaultPanel: "preview",
12
19
  },
13
20
  },
14
21
  };
@@ -10,7 +10,7 @@ export default function socket(endpoint) {
10
10
  received: debounce((data) => {
11
11
  console.log("Lookbook files changed");
12
12
  callback(data);
13
- }, 300),
13
+ }, 200),
14
14
  connected() {
15
15
  console.log("Lookbook websocket connected");
16
16
  },
@@ -1,17 +1,25 @@
1
1
  import config from "../config";
2
2
 
3
3
  export default function createInspectorStore(Alpine) {
4
- const { tabs } = config.inspector;
4
+ const { drawer, preview } = config.inspector;
5
5
  return {
6
- panels: {
7
- active: Alpine.$persist(tabs.default).as("inspector-panel-active"),
8
- height: Alpine.$persist(tabs.defaultHeight).as("inspector-height"),
6
+ drawer: {
7
+ hidden: Alpine.$persist(false).as("drawer-hidden"),
8
+ orientation: Alpine.$persist(drawer.orientation).as("drawer-orientation"),
9
+ panel: Alpine.$persist(drawer.defaultPanel).as("drawer-panel"),
10
+ height: Alpine.$persist(drawer.defaultHeight).as("drawer-height"),
11
+ width: Alpine.$persist(drawer.defaultWidth).as("drawer-width"),
12
+ minWidth: drawer.minWidth,
13
+ minHeight: drawer.minHeight,
14
+ visibleTabCount: Infinity,
9
15
  },
10
16
  preview: {
11
17
  width: Alpine.$persist("100%").as("preview-width"),
12
18
  height: Alpine.$persist("100%").as("preview-height"),
13
- source: Alpine.$persist(false).as("preview-source"),
19
+ panel: Alpine.$persist(preview.defaultPanel).as("preview-panel"),
14
20
  lastWidth: null,
21
+ lastHeight: null,
22
+ resizing: false,
15
23
  },
16
24
  };
17
25
  }
@@ -27,8 +27,8 @@ module Lookbook
27
27
  begin
28
28
  set_params
29
29
  @examples = examples_data
30
- @preview_srcdoc = render_examples(examples_data).gsub("\"", "&quot;")
31
- @panels = panels.filter { |name, panel| panel[:show] }
30
+ @drawer_panels = drawer_panels.filter { |name, panel| panel[:show] }
31
+ @preview_panels = preview_panels.filter { |name, panel| panel[:show] }
32
32
  rescue *EXCEPTIONS
33
33
  render "error"
34
34
  end
@@ -117,23 +117,37 @@ module Lookbook
117
117
  @nav
118
118
  end
119
119
 
120
- def panels
120
+ def preview_panels
121
121
  {
122
- source: {
123
- label: "Source",
124
- template: "lookbook/panels/source",
125
- hotkey: "s",
122
+ preview: {
123
+ label: "Preview",
124
+ template: "lookbook/panels/preview",
125
+ srcdoc: Lookbook.config.preview_srcdoc ? render_examples(examples_data).gsub("\"", "&quot;") : nil,
126
+ hotkey: "v",
126
127
  show: true,
127
128
  disabled: false,
128
- copy: true
129
+ copy: false
129
130
  },
130
131
  output: {
131
- label: "Output",
132
+ label: "HTML",
132
133
  template: "lookbook/panels/output",
133
134
  hotkey: "o",
134
135
  show: true,
135
136
  disabled: false,
136
137
  copy: true
138
+ }
139
+ }
140
+ end
141
+
142
+ def drawer_panels
143
+ {
144
+ source: {
145
+ label: "Source",
146
+ template: "lookbook/panels/source",
147
+ hotkey: "s",
148
+ show: true,
149
+ disabled: false,
150
+ copy: true
137
151
  },
138
152
  notes: {
139
153
  label: "Notes",
@@ -6,7 +6,9 @@
6
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/css/app.css?v=<%= Lookbook::VERSION %>" rel="stylesheet">
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>">
9
+ <% if config.ui_favicon != false %>
10
+ <link rel="icon" href="<%= config.ui_favicon || "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>" %>">
11
+ <% end %>
10
12
 
11
13
  <% if config.auto_refresh %>
12
14
  <script>
@@ -44,9 +46,13 @@
44
46
  x-effect="$store.sidebar.width = Math.min(splits[0] || $store.sidebar.width, $store.sidebar.maxWidth)"
45
47
  x-cloak
46
48
  >
47
- <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>
49
+ <div class="w-[9px] h-full bg-transparent hover:bg-indigo-100 hover:bg-opacity-20 transition absolute top-0 bottom-0 -translate-x-1/2 cursor-[col-resize] z-10"></div>
48
50
  </div>
49
- <main id="main" class="h-full overflow-hidden w-full" x-show="$store.layout.desktop || !$store.sidebar.open" x-cloak>
51
+ <main
52
+ id="main"
53
+ class="h-full overflow-hidden w-full"
54
+ x-show="$store.layout.desktop || !$store.sidebar.open" x-cloak
55
+ >
50
56
  <%= yield %>
51
57
  </main>
52
58
  </div>
@@ -1,8 +1,13 @@
1
1
  <%
2
2
  line_numbers ||= false
3
3
  language ||= "html"
4
+ wrap ||= nil;
4
5
  %>
5
6
  <% code = capture do %><%= yield %><% end %>
6
- <div class="code <%= "numbered" if line_numbers %> <%= classes %>">
7
+ <div class="code <%= "numbered" if line_numbers %> <%= classes %>"
8
+ x-data="code"
9
+ :class="{'wrapped': wrap}"
10
+ <% if wrap.present? %>x-effect="wrap = <%= wrap %>"<% end %>
11
+ >
7
12
  <pre><code class="highlight"><%= highlight(code.strip, language, line_numbers: line_numbers) %></code></pre>
8
13
  </div>
@@ -0,0 +1,124 @@
1
+ <div id="drawer" class="bg-white w-full h-full flex flex-col min-w-0">
2
+ <div class="pl-4 border-b border-gray-300 select-none flex-none" x-show="!drawerHidden">
3
+ <div class="flex cursor-auto relative">
4
+ <div
5
+ id="inspector-tabs-<%= example.id %>"
6
+ x-data="tabs"
7
+ class="min-w-0 -mb-px"
8
+ x-effect="$store.inspector.drawer.visibleTabCount = visibleTabCount"
9
+ >
10
+ <nav x-ref="tabs" class="flex h-10 space-x-8 flex-grow pr-8">
11
+ <% panels.each.with_index(1) do |(key,props),i| %>
12
+ <a
13
+ id="inspector-tab-<%= key %>-<%= example.id %>"
14
+ href="#inspector-panel-<%= key %>"
15
+ class="whitespace-nowrap pt-2.5 pb-1.5 px-1 border-b-2 cursor-pointer <%= "!text-gray-300" if props[:disabled] %>"
16
+ :class="{
17
+ 'border-indigo-400': isActiveDrawerPanel('<%= key %>'),
18
+ 'border-transparent text-gray-500 hover:text-gray-700': !isActiveDrawerPanel('<%= key %>'),
19
+ 'invisible': (<%= i %> > visibleTabCount)
20
+ }"
21
+ @click.stop.prevent="switchDrawerPanel('<%= key %>')"
22
+ <% if props[:hotkey] %>data-hotkey="<%= props[:hotkey] %>"<% end %>
23
+ >
24
+ <%== props[:label] %>
25
+ </a>
26
+ <% end %>
27
+ <div
28
+ x-ref="toggle"
29
+ x-show="visibleTabCount !== tabs.length"
30
+ class="flex-none absolute top-[9px]"
31
+ :style="`left: ${tabsWidth - 48}px`"
32
+ >
33
+ <button class="py-1 px-2 text-gray-500 hover:text-indigo-800">
34
+ <%= icon "chevrons-right", size: 3.5 %>
35
+ </button>
36
+ </div>
37
+ </nav>
38
+ <nav class="hidden">
39
+ <div x-ref="dropdown" class="min-w-[120px]">
40
+ <% panels.each.with_index(1) do |(key,props),i| %>
41
+ <template id="inspector-dropdown-tab-<%= key %>-<%= example.id %>" x-if="<%= i %> > $store.inspector.drawer.visibleTabCount">
42
+ <div :class="{'border-t border-gray-300': (<%= i %> > $store.inspector.drawer.visibleTabCount + 1)}">
43
+ <a
44
+ href="#inspector-panel-<%= key %>"
45
+ class="block whitespace-nowrap py-2 px-4 border-l-2 cursor-pointer <%= "!text-gray-300" if props[:disabled] %>"
46
+ :class="{
47
+ 'border-indigo-400': $store.inspector.drawer.panel === '<%= key %>',
48
+ 'border-transparent text-gray-500 hover:text-gray-700': $store.inspector.drawer.panel !== '<%= key %>',
49
+ }"
50
+ @click.stop.prevent=" hideDropdown(); switchDrawerPanel('<%= key %>')"
51
+ >
52
+ <%== props[:label] %>
53
+ </a>
54
+ </div>
55
+ </template>
56
+ <% end %>
57
+ </div>
58
+ </nav>
59
+ </div>
60
+ <div class="flex items-center bg-white border-l border-gray-200 ml-auto space-x-3 px-3">
61
+ <div>
62
+ <% panels.each do |key, props| %>
63
+ <div
64
+ ref="<%= "inspector-panel-#{example.id}-#{key}-copy" %>"
65
+ class="flex items-center"
66
+ :class="{'pointer-events-none opacity-30': <%= !props[:copy].present? %>}"
67
+ x-show="isActiveDrawerPanel('<%= key %>')"
68
+ x-cloak
69
+ >
70
+ <button
71
+ data-target="<%= "inspector-panel-#{example.id}-#{key}-clipboard" %>"
72
+ class="text-gray-400 transition"
73
+ x-data="copy"
74
+ x-tooltip.theme.lookbook="done ? 'copied!' : 'copy to clipboard'"
75
+ @click="save"
76
+ :class="{
77
+ '!text-green-600 hover:text-green-600': done,
78
+ 'hover:text-indigo-500': !done}"
79
+ x-cloak
80
+ <% unless props[:copy].present? %>disabled<% end %>
81
+ >
82
+ <%= icon "${done ? 'check' : 'clipboard'}", size: 4 %>
83
+ </button>
84
+ </div>
85
+ <% end %>
86
+ </div>
87
+ <button
88
+ x-tooltip.theme.lookbook="`${horizontal ? 'pin drawer on right' : 'pin drawer on bottom'}`"
89
+ @click="toggleOrientation"
90
+ :class="{'pointer-events-none opacity-30': !canBeVertical}"
91
+ >
92
+ <%= icon "${horizontal ? 'sidebar' : 'credit-card'}",
93
+ size: 4,
94
+ class: "scale-[-1] text-gray-400 hover:text-indigo-800" %>
95
+ </button>
96
+ <button
97
+ x-tooltip.theme.lookbook="`hide drawer`"
98
+ @click="toggleDrawer"
99
+ >
100
+ <%= icon "x-circle",
101
+ size: 4,
102
+ class: "text-gray-400 hover:text-indigo-800" %>
103
+ </button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ <div class="bg-white relative flex-grow">
108
+ <% panels.each do |key, props| %>
109
+ <div
110
+ class="h-full w-full absolute inset-0"
111
+ x-show="isActiveDrawerPanel('<%= key %>')"
112
+ x-cloak
113
+ >
114
+ <div id="inspector-panel-<%= example.id %>-<%= key %>" class="h-full">
115
+ <%= render props[:template],
116
+ key: key,
117
+ examples: examples,
118
+ clipboard_id: "inspector-panel-#{example.id}-#{key}-clipboard",
119
+ **props %>
120
+ </div>
121
+ </div>
122
+ <% end %>
123
+ </div>
124
+ </div>
@@ -9,7 +9,7 @@
9
9
  @keyup.stop="checkEsc"
10
10
  @keyup.f.document="focus"
11
11
  >
12
- <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="clear">
12
+ <button class="text-gray-400 hover:text-indigo-500 focus:ring-0 focus:outline-none absolute top-1/2 right-2 -translate-y-1/2" @click="clear">
13
13
  <%= icon "x", size: 3, class: "hover:text-indigo-500" %>
14
14
  </button>
15
15
  </div>
@@ -1,5 +1,5 @@
1
- <header class="py-2 px-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10 select-none">
2
- <button class="flex-none mr-3" x-show="!$store.layout.desktop" @click="$store.sidebar.toggle">
1
+ <header class="pl-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10 select-none min-w-0">
2
+ <button class="flex-none mr-6" x-show="!$store.layout.desktop" @click="$store.sidebar.toggle">
3
3
  <svg class="feather w-5 h-5 hover:text-indigo-500 transition">
4
4
  <use xlink:href="/lookbook-assets/feather-sprite.svg#menu" />
5
5
  </svg>
@@ -1,6 +1,6 @@
1
1
  <nav id="nav" x-data="nav" @popstate.window="setActive">
2
2
  <% if items.any? %>
3
- <ul x-ref="items" class="divide-y divide-gray-300">
3
+ <ul x-ref="items">
4
4
  <% items.each do |node| %>
5
5
  <%= component "nav_#{node.type}", node: node %>
6
6
  <% end %>
@@ -1,17 +1,14 @@
1
- <li id="nav-group-<%= node.id %>" class="<%= classes %>" x-data="navGroup" :class="{hidden}" x-cloak>
2
- <div @click="toggle" class="nav-toggle py-[5px]" style="padding-left: calc((<%= node.hierarchy_depth - 1 %> * 12px) + 0.5rem);">
3
- <%= icon "${open ? 'chevron-down' : 'chevron-right'}", size: 3, class: "mr-1 text-gray-500" %>
4
- <%= icon node.type == :preview ? "layers" : "folder", size: 3.5, class: "mr-1.5 text-indigo-500" %>
5
- <div class="nav-label <%= "font-bold" if node.type == :preview %>" <% if node.type == :preview %> @click.stop="toggle(); navigateToFirstChild()"<% end %>>
6
- <%= node.label %>
1
+ <li key="<%= node.id %>" class="<%= classes %>">
2
+ <div id="nav-group-<%= node.id %>" x-data="navGroup" :class="{hidden}" x-cloak>
3
+ <div @click="toggle" class="nav-toggle py-[5px]" style="padding-left: calc((<%= node.hierarchy_depth - 1 %> * 12px) + 0.5rem);">
4
+ <%= icon "${open ? 'chevron-down' : 'chevron-right'}", size: 3, class: "mr-1 text-gray-500" %>
5
+ <%= icon node.type == :preview ? "layers" : "folder", size: 3.5, class: "mr-1.5 text-indigo-500" %>
6
+ <div class="nav-label <%= "font-bold" if node.type == :preview %>" <% if node.type == :preview %> @click.stop="toggle(); navigateToFirstChild()"<% end %>>
7
+ <%= node.label %>
8
+ </div>
7
9
  </div>
10
+ <ul x-ref="items" x-show="open" x-cloak>
11
+ <%= yield %>
12
+ </ul>
8
13
  </div>
9
- <ul
10
- x-ref="items"
11
- x-show="open"
12
- id="nav-group-<%= node.id %>-children-<%= node.type == :preview ? node.get_examples.reject(&:hidden?).size : node.items.size %> %>"
13
- x-cloak
14
- >
15
- <%= yield %>
16
- </ul>
17
14
  </li>
@@ -3,19 +3,21 @@ path = show_path item.path
3
3
  display ||= :item
4
4
  label ||= item.label
5
5
  %>
6
- <li id="nav-item-<%= item.id %>" x-data="navItem(<%= item.matchers.to_json %>)" :class="{hidden}" data-path="<%= path %>" x-cloak>
7
- <a href="<%= path %>"
8
- class="nav-link pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
9
- style="padding-left: calc((<%= depth - 1 %> * 12px) + 0.5rem);"
10
- x-ref="link"
11
- :class="{'!bg-indigo-100':active}"
12
- @click.stop.prevent="navigate"
13
- >
14
- <div class="relative w-3.5 h-3.5 mr-1.5 <%= "ml-[3px]" if display == :node %> " :class="active ? 'text-gray-900' : 'text-indigo-500'">
15
- <%= icon display == :node ? "layers" : "eye", size: 3.5, class: "group-hover:text-indigo-800" %>
16
- </div>
17
- <div class="truncate whitespace-nowrap select-none <%= "font-bold" if display == :node %>">
18
- <%= label %>
19
- </div>
20
- </a>
6
+ <li key="<%= item.id %>">
7
+ <div id="nav-item-<%= item.id %>" x-data="navItem(<%= item.matchers.to_json %>)" :class="{hidden}" data-path="<%= path %>" x-cloak>
8
+ <a href="<%= path %>"
9
+ class="nav-link pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
10
+ style="padding-left: calc((<%= depth - 1 %> * 12px) + 0.5rem);"
11
+ x-ref="link"
12
+ :class="{'!bg-indigo-100':active}"
13
+ @click.stop.prevent="navigate"
14
+ >
15
+ <div class="relative w-3.5 h-3.5 mr-1.5 <%= "ml-[3px]" if display == :node %> " :class="active ? 'text-gray-900' : 'text-indigo-500'">
16
+ <%= icon display == :node ? "layers" : "eye", size: 3.5, class: "group-hover:text-indigo-800" %>
17
+ </div>
18
+ <div class="truncate whitespace-nowrap select-none <%= "font-bold" if display == :node %>">
19
+ <%= label %>
20
+ </div>
21
+ </a>
22
+ </div>
21
23
  </li>
@@ -6,6 +6,8 @@
6
6
  <% end %>
7
7
  <% end %>
8
8
  <% else %>
9
- <% example = examples.first %>
10
- <%= component "nav_item", item: example, depth: example.hierarchy_depth, label: node.label, display: :node %>
9
+ <% if examples.any? %>
10
+ <% example = examples.first %>
11
+ <%= component "nav_item", item: example, depth: example.hierarchy_depth, label: node.label, display: :node %>
12
+ <% end %>
11
13
  <% end %>
@@ -1,10 +1,12 @@
1
+ <%
2
+ value = params.key?(param[:name]) ? params[param[:name]] : param[:default]
3
+ %>
1
4
  <div
5
+ ref="<%= @example.id %>-param-<%= param[:name] %>-input"
2
6
  class="px-4 py-3"
3
- x-data="param"
4
- <% if i == 0 %>x-effect="if ($store.inspector.panels.active === 'params') setFocus()"<% end %>
5
7
  >
6
8
  <div class="flex items-start max-w-[800px]">
7
- <div class="w-[200px] flex-none py-2">
9
+ <div class="flex-none py-2" :style="`width: ${horizontal ? '200' : '120' }px`">
8
10
  <label for="param-<%= param[:name] %>" class="font-bold">
9
11
  <%= param[:name].titleize %>
10
12
  </label>
@@ -12,8 +14,7 @@
12
14
  <div class="flex-grow">
13
15
  <%= render "lookbook/inputs/#{param[:input]}",
14
16
  **param,
15
- value: params.key?(param[:name]) ? params[param[:name]] : param[:default],
16
- id: "#{@example.id}-param-#{param[:name]}-input"
17
+ value: value
17
18
  %>
18
19
  </div>
19
20
  </div>
@@ -1,24 +1,71 @@
1
- <div id="preview" class="h-full md:h-auto md:min-h-0 w-full bg-gray-50">
2
- <div class="relative mx-auto bg-white h-full w-full" x-data="previewWindow" :style="`width: ${$store.layout.desktop ? $store.inspector.preview.width : '100%'}`" x-show="!showSource">
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
- srcdoc="<%== srcdoc %>"
7
- frameborder="0"
8
- x-data="sizes"
9
- x-effect="preview.width = width; preview.height = height;"
10
- ></iframe>
11
- <div class="absolute opacity-0 inset-0 pointer-events-none" :class="{ 'pointer-events-none': !$store.layout.reflowing }"></div>
1
+ <div id="preview" class="grid grid-rows-[40px_1fr]">
2
+ <%= component "header" do %>
12
3
  <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="$store.layout.desktop"
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>
4
+ id="preview-tabs-<%= @example.id %>" class="min-w-0 -mb-px">
5
+ <nav class="flex h-10 space-x-8 flex-grow pr-8">
6
+ <% panels.each do |key, props| %>
7
+ <a
8
+ id="preview-tab-<%= key %>-<%= @example.id %>"
9
+ href="#preview-panel-<%= key %>"
10
+ class="whitespace-nowrap pt-2.5 pb-1.5 px-1 border-b-2 cursor-pointer"
11
+ :class="{
12
+ 'border-indigo-400': isActivePreviewPanel('<%= key %>'),
13
+ 'border-transparent text-gray-500 hover:text-gray-700': !isActivePreviewPanel('<%= key %>'),
14
+ }"
15
+ @click.stop.prevent="switchPreviewPanel('<%= key %>')"
16
+ <% if props[:hotkey] %>data-hotkey="<%= props[:hotkey] %>"<% end %>
17
+ >
18
+ <%== props[:label] %>
19
+ </a>
20
+ <% end %>
21
+ </nav>
22
22
  </div>
23
+ <div class="flex items-stretch h-full ml-auto space-x-3">
24
+ <div
25
+ class="flex items-center text-xs font-monospace text-gray-700 space-x-1 opacity-50 hover:opacity-100 transition"
26
+ :class="{'opacity-100': $store.inspector.preview.resizing}"
27
+ x-show="isActivePreviewPanel('preview')">
28
+ <span x-text="`${preview.width}px`"></span>
29
+ <span class="text-gray-500">x</span>
30
+ <span x-text="`${preview.height}px`"></span>
31
+ </div>
32
+ <div class="flex items-center bg-white border-l border-gray-200 space-x-3 text-gray-400 divide-x divide-gray-300 px-3">
33
+ <div class="flex items-center space-x-3">
34
+ <button
35
+ x-tooltip.theme.lookbook="`Refresh preview`"
36
+ @click.prevent.stop="refresh"
37
+ data-hotkey="r"
38
+ >
39
+ <%= icon "refresh-cw", size: 4, class: "hover:text-indigo-800" %>
40
+ </button>
41
+ <a
42
+ href="<%= preview_path %>"
43
+ target="_blank"
44
+ x-tooltip.theme.lookbook="`Open in new window`"
45
+ data-hotkey="w"
46
+ >
47
+ <%= icon "external-link", size: 4, class: "hover:text-indigo-800" %>
48
+ </a>
49
+ <button
50
+ x-tooltip.theme.lookbook="`${drawerHidden ? 'show' : 'hide'} drawer`"
51
+ @click="toggleDrawer"
52
+ x-show="drawerHidden"
53
+ data-hotkey="i"
54
+ >
55
+ <%= icon "${horizontal ? 'credit-card' : 'sidebar'}", size: 4, class: "hover:text-indigo-800 scale-[-1]" %>
56
+ </button>
57
+ </div>
58
+
59
+ </div>
60
+ </div>
61
+ <% end %>
62
+ <div class="bg-white relative flex-grow">
63
+ <% panels.each do |key, props| %>
64
+ <div class="h-full w-full absolute inset-0" x-show="isActivePreviewPanel('<%= key %>')" x-cloak>
65
+ <div id="preview-panel-<%= example.id %>-<%= key %>" class="h-full">
66
+ <%= render props[:template], key: key, examples: examples, **props %>
67
+ </div>
68
+ </div>
69
+ <% end %>
23
70
  </div>
24
71
  </div>
@@ -1,8 +1,7 @@
1
1
  <select
2
2
  name="<%= name %>"
3
- id="<%= id %>"
4
3
  class="form-input"
5
- @change.stop="update($el.name, $el.value)"
6
- x-ref="input">
4
+ x-model="value"
5
+ x-data="param('<%= name %>', '<%= value %>')">
7
6
  <%= options_for_select(options, value) %>
8
7
  </select>
@@ -1,8 +1,8 @@
1
1
  <input
2
- id="<%= id %>"
3
2
  class="form-input"
4
3
  type="<%= input_type %>"
5
4
  name="<%= name %>"
6
5
  value="<%= value %>"
7
- @keyup.stop.debounce.400="if (validate()) update($el.name, $el.value)"
8
- x-ref="input">
6
+ x-data="param('<%= name %>', '<%= value %>', {debounce: 300})"
7
+ x-model="value"
8
+ @keyup.stop>
@@ -1,8 +1,8 @@
1
1
  <textarea
2
- id="<%= id %>"
3
2
  class="form-input"
4
3
  name="<%= name %>"
5
4
  rows="4"
6
- @keyup.stop.debounce.300="update($el.name, $el.value)"
7
- x-ref="input"
5
+ @keyup.stop
6
+ x-model="value"
7
+ x-data="param('<%= name %>', '<%= value %>', {debounce: 300})"
8
8
  ><%= value %></textarea>
@@ -1,13 +1,13 @@
1
- <div id="<%= id %>" x-init="checked = <%= value == true || value == "true" ? "true" : "false" %>" x-ref="input">
1
+ <div x-data="param('<%= name %>', <%= value %>)" data-morph-strategy="replace">
2
2
  <button type="button"
3
3
  class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400"
4
- :class="{'bg-indigo-500': checked, 'bg-gray-300': !checked}"
4
+ :class="{'bg-indigo-500': value, 'bg-gray-300': !value}"
5
5
  role="switch"
6
- @click.stop="checked = !checked; update('<%= name %>', checked)">
6
+ @click.stop="value = !value;">
7
7
  <span
8
8
  aria-hidden="true"
9
- class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
10
- :class="{'translate-x-5': checked, 'translate-x-0': !checked}"
9
+ class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow ring-0 transition ease-in-out duration-200"
10
+ :class="{'translate-x-5': value, 'translate-x-0': !value}"
11
11
  ></span>
12
12
  </button>
13
13
  </div>
@@ -1,5 +1,5 @@
1
1
  <% items = examples.filter { |example| example[:notes].present? } %>
2
- <div class="text-gray-600 bg-gray-50 flex-grow min-h-full">
2
+ <div class="text-gray-600 bg-gray-50 h-full overflow-auto" data-morph-strategy="replace">
3
3
  <% if items.many? %>
4
4
  <div class="divide-y divide-dashed divide-gray-300">
5
5
  <% items.each do |item| %>