lookbook 0.4.8 → 0.5.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.
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| %>