lookbook 0.4.7 → 0.5.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  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/filter.js +1 -1
  8. data/app/assets/lookbook/js/components/inspector.js +44 -7
  9. data/app/assets/lookbook/js/components/nav-group.js +1 -1
  10. data/app/assets/lookbook/js/components/nav-item.js +1 -0
  11. data/app/assets/lookbook/js/components/nav.js +1 -1
  12. data/app/assets/lookbook/js/components/page.js +17 -5
  13. data/app/assets/lookbook/js/components/param.js +23 -7
  14. data/app/assets/lookbook/js/components/preview-window.js +95 -26
  15. data/app/assets/lookbook/js/components/tabs.js +50 -0
  16. data/app/assets/lookbook/js/config.js +9 -3
  17. data/app/assets/lookbook/js/lib/socket.js +1 -1
  18. data/app/assets/lookbook/js/stores/inspector.js +12 -5
  19. data/app/controllers/lookbook/app_controller.rb +3 -1
  20. data/app/views/layouts/lookbook/app.html.erb +7 -3
  21. data/app/views/lookbook/components/_code.html.erb +6 -1
  22. data/app/views/lookbook/components/_copy.html.erb +7 -3
  23. data/app/views/lookbook/components/_drawer.html.erb +121 -0
  24. data/app/views/lookbook/components/_filter.html.erb +1 -1
  25. data/app/views/lookbook/components/_header.html.erb +1 -1
  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/_param.html.erb +6 -5
  30. data/app/views/lookbook/components/_preview.html.erb +49 -21
  31. data/app/views/lookbook/inputs/_select.html.erb +2 -3
  32. data/app/views/lookbook/inputs/_text.html.erb +3 -3
  33. data/app/views/lookbook/inputs/_textarea.html.erb +3 -3
  34. data/app/views/lookbook/inputs/_toggle.html.erb +5 -5
  35. data/app/views/lookbook/panels/_notes.html.erb +1 -1
  36. data/app/views/lookbook/panels/_output.html.erb +2 -2
  37. data/app/views/lookbook/panels/_params.html.erb +1 -1
  38. data/app/views/lookbook/panels/_source.html.erb +2 -2
  39. data/app/views/lookbook/show.html.erb +64 -81
  40. data/lib/lookbook/code_formatter.rb +3 -3
  41. data/lib/lookbook/engine.rb +4 -0
  42. data/lib/lookbook/preview.rb +1 -1
  43. data/lib/lookbook/version.rb +1 -1
  44. data/lib/lookbook.rb +1 -0
  45. data/public/lookbook-assets/css/app.css +3 -1
  46. data/public/lookbook-assets/css/app.css.map +1 -1
  47. data/public/lookbook-assets/js/app.js +1 -1
  48. data/public/lookbook-assets/js/app.js.map +1 -1
  49. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce922c11cf8df5a1161c555189786265e51e29881c5a5e0948153fa65e855a50
4
- data.tar.gz: 38fe97d9e04c6505ebc8e1b8bc877fe761db215b525117b42a044cadf7b2eb50
3
+ metadata.gz: 7838a877b435b1952e1eed9163cad4f21fa44b2578d9114430bfc1d262b15c23
4
+ data.tar.gz: 4f8512d21d07f2b93164706569f0a28483f6b43e60446f19857411b4e66ac6ee
5
5
  SHA512:
6
- metadata.gz: d319e159fc2b87e710532dbd6c426360f2a6173e174d3e0eac05c677bdfd945cf0c5a391c426a8f987cb7539797aa10241e45cdddd55048f25af84689d0af9b2
7
- data.tar.gz: 80a217bf6303a778676872bd9d6c1412a3811323903031d491e945c0788ebba20cc2ccbb059d011e823b80d74b5342994c9b42f7590fd4fbfd305166b187d345
6
+ metadata.gz: bf6f6045ce9ea8d5bca9932bfb3538e0e5c307b856cdbc0a209520be5112bed281f623cc879af59e51216f1d284aa2de08ed6ef1b483f794ea74c434a150bb13
7
+ data.tar.gz: 4b2d545082ea784bc1122a51c947b0d5391cc829e895adfe224107591acdb68fabb23e8d21fe8be1f1d6acf405e774130dad1e1ba9676470a5bea9240f82cd34
data/README.md CHANGED
@@ -79,7 +79,7 @@ Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/do
79
79
 
80
80
  ```ruby
81
81
  # @label Basic Button
82
- # @display bg_color #fff
82
+ # @display bg_color "#fff"
83
83
  class ButtonComponentPreview < ViewComponent::Preview
84
84
 
85
85
  # Primary button
@@ -110,7 +110,7 @@ class ButtonComponentPreview < ViewComponent::Preview
110
110
  # ---------------
111
111
  # For light-on-dark screens
112
112
  #
113
- # @display bg_color #000
113
+ # @display bg_color "#000"
114
114
  def secondary
115
115
  render ButtonComponent.new(style: :inverted) do
116
116
  "Click me"
@@ -188,7 +188,7 @@ end
188
188
  The `@display` tag lets you pass custom parameters to your preview layout so that the component preview can be customised on a per-example basis.
189
189
 
190
190
  ```ruby
191
- # @display bg_color #eee
191
+ # @display bg_color "#eee"
192
192
  class FooComponentPreview < ViewComponent::Preview
193
193
 
194
194
  # @display max_width 500px
@@ -207,6 +207,8 @@ The `@display` tag can be applied at the preview (class) or at the example (meth
207
207
  - `<key>` must be a valid Ruby hash key name, without quotes or spaces
208
208
  - `<value>` will be parsed using the [Ruby YAML parser](https://yaml.org/YAML_for_ruby.html) to resolve the value
209
209
 
210
+ > Note: Ruby YAML does not (generally) require quoting of string values. However in some cases it _is_ required due to the presence of [indicator characters](https://yaml.org/YAML_for_ruby.html#indicators_in_strings) (such as `#`, `:` etc) - hence why the hex color code in the example above is surrounded by quotes. It's perfectly ok to quote all string values if you prefer.
211
+
210
212
  These display parameters can then be accessed via the `params` hash in your preview layout using `params[:lookbook][:display][<key>]`:
211
213
 
212
214
  ```html
@@ -2,6 +2,7 @@
2
2
  @import "tailwindcss/components";
3
3
  @import "tailwindcss/utilities";
4
4
  @import "tippy.js/dist/tippy";
5
+ @import "tippy.js/dist/border";
5
6
  @import "code_theme";
6
7
  @import "tooltip_theme";
7
8
 
@@ -57,8 +58,8 @@
57
58
  }
58
59
 
59
60
  @layer components {
60
- #nav > ul > li {
61
- @apply py-1;
61
+ #nav > ul > li > div {
62
+ @apply py-1 border-b border-gray-300;
62
63
  }
63
64
 
64
65
  .nav-toggle {
@@ -77,8 +78,12 @@
77
78
  @apply block;
78
79
  }
79
80
 
81
+ .code.wrapped pre {
82
+ @apply whitespace-pre-wrap;
83
+ }
84
+
80
85
  .code .line {
81
- @apply flex items-center leading-relaxed;
86
+ @apply leading-relaxed;
82
87
  }
83
88
 
84
89
  .code.numbered {
@@ -87,35 +92,41 @@
87
92
 
88
93
  .code.numbered:before {
89
94
  content: "";
90
- left: calc(2.7em + 8px);
95
+ left: 2.7em;
91
96
  @apply absolute top-0 bottom-0 border-r border-gray-200;
92
97
  }
93
98
 
99
+ .code.numbered .line {
100
+ padding-left: calc(2.7em + 8px);
101
+ @apply relative;
102
+ }
103
+
94
104
  .code .line-number {
105
+ display: inline-block;
95
106
  width: calc(2.7em + 8px);
96
107
  padding-top: 3px;
97
108
  padding-bottom: 3px;
98
109
  padding-right: 8px;
99
110
  margin-right: 16px;
100
- @apply font-mono text-right text-gray-400 flex-none text-xs;
111
+ @apply font-mono text-right text-gray-400 flex-none text-xs absolute left-0;
101
112
  }
102
113
 
103
114
  .code .line-content {
104
115
  @apply flex-none pr-4;
105
116
  }
106
117
 
107
- /* .code .line:before {
108
- content: counter(line);
109
- width: calc(3em + 8px);
110
- padding-top: 2px;
111
- padding-bottom: 2px;
112
- padding-right: 8px;
113
- @apply font-mono inline-block text-right mr-4 text-gray-400 border-r border-gray-200;
114
- } */
118
+ .resize-handle {
119
+ @apply flex items-center justify-center h-full w-full border-gray-300 bg-white hover:bg-indigo-100 hover:bg-opacity-20 text-gray-400 hover:text-gray-700 transition select-none touch-none;
120
+ }
115
121
  }
116
122
 
117
123
  @layer utilities {
118
124
  .form-input {
119
125
  @apply border-gray-300 text-gray-700 focus:ring-indigo-300 focus:border-indigo-300 rounded-sm text-sm w-full;
120
126
  }
127
+
128
+ .checked-bg {
129
+ background-color: #ffffff;
130
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cg fill='%23f3f3f3' fill-opacity='1'%3E%3Cpath fill-rule='evenodd' d='M0 0h4v4H0V0zm4 4h4v4H4V4z'/%3E%3C/g%3E%3C/svg%3E");
131
+ }
121
132
  }
@@ -4,13 +4,41 @@
4
4
  &[data-placement^="top"] > .tippy-arrow::before {
5
5
  border-top-color: theme("colors.indigo.500");
6
6
  }
7
+
7
8
  &[data-placement^="bottom"] > .tippy-arrow::before {
8
9
  border-bottom-color: theme("colors.indigo.500");
9
10
  }
11
+
10
12
  &[data-placement^="left"] > .tippy-arrow::before {
11
13
  border-left-color: theme("colors.indigo.500");
12
14
  }
15
+
13
16
  &[data-placement^="right"] > .tippy-arrow::before {
14
17
  border-right-color: theme("colors.indigo.500");
15
18
  }
16
19
  }
20
+
21
+ .tippy-box[data-theme~="menu"] {
22
+ border: 1px solid theme("colors.gray.300");
23
+ @apply bg-white text-gray-600 shadow-md rounded;
24
+
25
+ & > .tippy-content {
26
+ padding: 0;
27
+ }
28
+
29
+ &[data-placement^="top"] > .tippy-arrow::before {
30
+ border-top-color: white;
31
+ }
32
+
33
+ &[data-placement^="bottom"] > .tippy-arrow::before {
34
+ border-bottom-color: white;
35
+ }
36
+
37
+ &[data-placement^="left"] > .tippy-arrow::before {
38
+ border-left-color: white;
39
+ }
40
+
41
+ &[data-placement^="right"] > .tippy-arrow::before {
42
+ border-right-color: white;
43
+ }
44
+ }
@@ -13,7 +13,9 @@ import nav from "./components/nav";
13
13
  import navItem from "./components/nav-item";
14
14
  import navGroup from "./components/nav-group";
15
15
  import splitter from "./components/splitter";
16
+ import tabs from "./components/tabs";
16
17
  import copy from "./components/copy";
18
+ import code from "./components/code";
17
19
  import sizes from "./components/sizes";
18
20
 
19
21
  import initFilterStore from "./stores/filter";
@@ -42,11 +44,13 @@ Alpine.data("page", page);
42
44
  Alpine.data("splitter", splitter);
43
45
  Alpine.data("previewWindow", previewWindow);
44
46
  Alpine.data("copy", copy);
47
+ Alpine.data("code", code);
45
48
  Alpine.data("inspector", inspector);
46
49
  Alpine.data("filter", filter);
47
50
  Alpine.data("param", param);
48
51
  Alpine.data("sizes", sizes);
49
52
  Alpine.data("nav", nav);
53
+ Alpine.data("tabs", tabs);
50
54
  Alpine.data("navItem", navItem);
51
55
  Alpine.data("navGroup", navGroup);
52
56
 
@@ -0,0 +1,5 @@
1
+ export default function code() {
2
+ return {
3
+ wrap: false,
4
+ };
5
+ }
@@ -12,7 +12,7 @@ export default function filter() {
12
12
  this.$store.filter.raw = "";
13
13
  },
14
14
  focus($event) {
15
- if ($event.target.tagName === "INPUT") {
15
+ if ($event && $event.target.tagName === "INPUT") {
16
16
  return;
17
17
  }
18
18
  setTimeout(() => this.$refs.input.focus(), 0);
@@ -1,17 +1,54 @@
1
+ import sizeObserver from "./sizes";
2
+
1
3
  export default function inspector() {
2
4
  return {
5
+ width: 0,
6
+ height: 0,
7
+ init() {
8
+ const ro = new ResizeObserver((entries) => {
9
+ const rect = entries[0].contentRect;
10
+ this.width = Math.round(rect.width);
11
+ this.height = Math.round(rect.height);
12
+ });
13
+ ro.observe(this.$el);
14
+ this.width = Math.round(this.$el.clientWidth);
15
+ this.height = Math.round(this.$el.clientHeight);
16
+ },
17
+ get orientation() {
18
+ return this.$store.inspector.drawer.orientation;
19
+ },
20
+ get view() {
21
+ return this.$store.inspector.preview.view;
22
+ },
23
+ get horizontal() {
24
+ return this.canBeVertical ? this.orientation === "horizontal" : true;
25
+ },
26
+ get vertical() {
27
+ return !this.horizontal;
28
+ },
29
+ get canBeVertical() {
30
+ return this.width > 800;
31
+ },
32
+ get drawerHidden() {
33
+ return this.$store.inspector.drawer.hidden;
34
+ },
3
35
  isActivePanel(panel) {
4
- return this.$store.inspector.panels.active == panel;
36
+ return this.$store.inspector.drawer.active == panel;
5
37
  },
6
38
  switchPanel(panel) {
7
- this.$store.inspector.panels.active = panel;
39
+ this.$store.inspector.drawer.active = panel;
40
+ },
41
+ toggleView() {
42
+ this.$store.inspector.preview.view =
43
+ this.view === "html" ? "preview" : "html";
8
44
  },
9
- get showSource() {
10
- return this.$store.inspector.preview.source;
45
+ toggleOrientation() {
46
+ this.$store.inspector.drawer.orientation =
47
+ this.orientation === "horizontal" ? "vertical" : "horizontal";
11
48
  },
12
- toggleSource() {
13
- this.$store.inspector.preview.source =
14
- !this.$store.inspector.preview.source;
49
+ toggleDrawer() {
50
+ this.$store.inspector.drawer.hidden =
51
+ !this.$store.inspector.drawer.hidden;
15
52
  },
16
53
  preview: {
17
54
  width: null,
@@ -15,7 +15,7 @@ export default function navGroup() {
15
15
  },
16
16
  getChildren() {
17
17
  return this.$refs.items
18
- ? Array.from(this.$refs.items.querySelectorAll(":scope > li"))
18
+ ? Array.from(this.$refs.items.querySelectorAll(":scope > li > div"))
19
19
  : [];
20
20
  },
21
21
  navigateToFirstChild() {
@@ -15,6 +15,7 @@ export default function navItem(matchers) {
15
15
  this.$store.sidebar.open = false;
16
16
  },
17
17
  filter(text) {
18
+ this.hidden = false;
18
19
  if (text.length) {
19
20
  const matched = matchers.map((m) => m.includes(text));
20
21
  this.hidden = !matched.filter((m) => m).length;
@@ -22,7 +22,7 @@ export default function nav() {
22
22
  },
23
23
  getChildren() {
24
24
  return this.$refs.items
25
- ? Array.from(this.$refs.items.querySelectorAll(":scope > li"))
25
+ ? Array.from(this.$refs.items.querySelectorAll(":scope > li > div"))
26
26
  : [];
27
27
  },
28
28
  setActive() {
@@ -1,5 +1,21 @@
1
1
  import createSocket from "../lib/socket";
2
2
 
3
+ const morphOpts = {
4
+ key(el) {
5
+ return el.getAttribute("key") ? el.getAttribute("key") : el.id;
6
+ },
7
+ updating(el, toEl, childrenOnly, skip) {
8
+ if (
9
+ el.getAttribute &&
10
+ el.getAttribute("data-morph-strategy") === "replace"
11
+ ) {
12
+ el.innerHTML = toEl.innerHTML;
13
+ return skip();
14
+ }
15
+ },
16
+ lookahead: true,
17
+ };
18
+
3
19
  export default function page() {
4
20
  return {
5
21
  init() {
@@ -22,11 +38,7 @@ export default function page() {
22
38
  },
23
39
  morph(dom) {
24
40
  const pageHtml = dom.getElementById(this.$root.id).outerHTML;
25
- Alpine.morph(this.$root, pageHtml, {
26
- key(el) {
27
- return el.getAttribute("key") ? el.getAttribute("key") : el.id;
28
- },
29
- });
41
+ Alpine.morph(this.$root, pageHtml, morphOpts);
30
42
  this.$dispatch("page:morphed");
31
43
  },
32
44
  };
@@ -1,18 +1,34 @@
1
- export default function param() {
1
+ import debounce from "debounce";
2
+
3
+ export default function param(name, value, opts = {}) {
2
4
  return {
3
- setFocus() {
4
- if (this.$refs.input) {
5
- setTimeout(() => this.$refs.input.focus(), 0);
5
+ name,
6
+ value,
7
+ updating: false,
8
+ init() {
9
+ if (opts.debounce) {
10
+ this.$watch(
11
+ "value",
12
+ debounce(() => this.updateIfValid(), opts.debounce)
13
+ );
14
+ } else {
15
+ this.$watch("value", () => this.updateIfValid());
6
16
  }
7
17
  },
8
- update(name, value) {
18
+ setFocus() {
19
+ setTimeout(() => this.$root.focus(), 0);
20
+ },
21
+ updateIfValid() {
22
+ if (this.validate()) this.update();
23
+ },
24
+ update() {
9
25
  const searchParams = new URLSearchParams(window.location.search);
10
- searchParams.set(name, value);
26
+ searchParams.set(this.name, this.value);
11
27
  const path = location.href.replace(location.search, "");
12
28
  this.setLocation(`${path}?${searchParams.toString()}`);
13
29
  },
14
30
  validate() {
15
- return this.$el.reportValidity ? this.$el.reportValidity() : true;
31
+ return this.$root.reportValidity ? this.$root.reportValidity() : true;
16
32
  },
17
33
  };
18
34
  }
@@ -1,37 +1,106 @@
1
1
  export default function preview() {
2
2
  return {
3
- onResize(e) {
4
- const size =
5
- this.resizeStartSize - (this.resizeStartPosition - e.pageX) * 2;
6
- const parentSize = this.$root.parentElement.clientWidth;
7
- const percentSize = (Math.round(size) / parentSize) * 100;
8
- const minWidth = (300 / parentSize) * 100;
9
- this.$store.inspector.preview.width = `${Math.min(
10
- Math.max(percentSize, minWidth),
11
- 100
12
- )}%`;
3
+ get store() {
4
+ return this.$store.inspector.preview;
13
5
  },
14
- onResizeStart(e) {
6
+ get maxWidth() {
7
+ return this.store.width === "100%" ? "100%" : `${this.store.width}px`;
8
+ },
9
+ get maxHeight() {
10
+ return this.store.height === "100%" ? "100%" : `${this.store.height}px`;
11
+ },
12
+ get parentWidth() {
13
+ return Math.round(this.$root.parentElement.clientWidth);
14
+ },
15
+ get parentHeight() {
16
+ return Math.round(this.$root.parentElement.clientHeight);
17
+ },
18
+ start() {
15
19
  this.$store.layout.reflowing = true;
16
- this.onResize = this.onResize.bind(this);
17
- this.onResizeEnd = this.onResizeEnd.bind(this);
18
- this.resizeStartPosition = e.pageX;
19
- this.resizeStartSize = this.$root.clientWidth;
20
- window.addEventListener("pointermove", this.onResize);
21
- window.addEventListener("pointerup", this.onResizeEnd);
22
- },
23
- onResizeEnd() {
24
- window.removeEventListener("pointermove", this.onResize);
25
- window.removeEventListener("pointerup", this.onResizeEnd);
20
+ this.store.resizing = true;
21
+ },
22
+ end() {
26
23
  this.$store.layout.reflowing = false;
24
+ this.store.resizing = false;
25
+ },
26
+ onResizeStart(e) {
27
+ this.onResizeWidthStart(e);
28
+ this.onResizeHeightStart(e);
29
+ },
30
+ toggleFullSize() {
31
+ const { height, width } = this.store;
32
+ if (height === "100%" && width === "100%") {
33
+ this.toggleFullHeight();
34
+ this.toggleFullWidth();
35
+ } else {
36
+ if (height !== "100%") this.toggleFullHeight();
37
+ if (width !== "100%") this.toggleFullWidth();
38
+ }
39
+ },
40
+ onResizeWidth(e) {
41
+ const width =
42
+ this.resizeStartWidth - (this.resizeStartPositionX - e.pageX) * 2;
43
+ const boundedWidth = Math.min(
44
+ Math.max(Math.round(width), 200),
45
+ this.parentWidth
46
+ );
47
+ this.store.width =
48
+ boundedWidth === this.parentWidth ? "100%" : boundedWidth;
49
+ },
50
+ onResizeWidthStart(e) {
51
+ this.start();
52
+ this.onResizeWidth = this.onResizeWidth.bind(this);
53
+ this.onResizeWidthEnd = this.onResizeWidthEnd.bind(this);
54
+ this.resizeStartPositionX = e.pageX;
55
+ this.resizeStartWidth = this.$root.clientWidth;
56
+ window.addEventListener("pointermove", this.onResizeWidth);
57
+ window.addEventListener("pointerup", this.onResizeWidthEnd);
58
+ },
59
+ onResizeWidthEnd() {
60
+ window.removeEventListener("pointermove", this.onResizeWidth);
61
+ window.removeEventListener("pointerup", this.onResizeWidthEnd);
62
+ this.end();
27
63
  },
28
64
  toggleFullWidth() {
29
- const preview = this.$store.inspector.preview;
30
- if (preview.width === "100%" && preview.lastWidth) {
31
- preview.width = preview.lastWidth;
65
+ const { width, lastWidth } = this.store;
66
+ if (width === "100%" && lastWidth) {
67
+ this.store.width = lastWidth;
68
+ } else {
69
+ this.store.lastWidth = width;
70
+ this.store.width = "100%";
71
+ }
72
+ },
73
+ onResizeHeight(e) {
74
+ const height =
75
+ this.resizeStartHeight - (this.resizeStartPositionY - e.pageY);
76
+ const boundedHeight = Math.min(
77
+ Math.max(Math.round(height), 200),
78
+ this.parentHeight
79
+ );
80
+ this.$store.inspector.preview.height =
81
+ boundedHeight === this.parentHeight ? "100%" : boundedHeight;
82
+ },
83
+ onResizeHeightStart(e) {
84
+ this.start();
85
+ this.onResizeHeight = this.onResizeHeight.bind(this);
86
+ this.onResizeHeightEnd = this.onResizeHeightEnd.bind(this);
87
+ this.resizeStartPositionY = e.pageY;
88
+ this.resizeStartHeight = this.$root.clientHeight;
89
+ window.addEventListener("pointermove", this.onResizeHeight);
90
+ window.addEventListener("pointerup", this.onResizeHeightEnd);
91
+ },
92
+ onResizeHeightEnd() {
93
+ window.removeEventListener("pointermove", this.onResizeHeight);
94
+ window.removeEventListener("pointerup", this.onResizeHeightEnd);
95
+ this.end();
96
+ },
97
+ toggleFullHeight() {
98
+ const { height, lastHeight } = this.store;
99
+ if (height === "100%" && lastHeight) {
100
+ this.store.height = lastHeight;
32
101
  } else {
33
- preview.lastWidth = preview.width;
34
- preview.width = "100%";
102
+ this.store.lastHeight = height;
103
+ this.store.height = "100%";
35
104
  }
36
105
  },
37
106
  };
@@ -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,18 @@ 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",
9
+ drawer: {
10
+ orientation: "horizontal",
11
+ defaultPanel: "source",
11
12
  defaultHeight: 200,
13
+ defaultWidth: 500,
14
+ minWidth: 350,
15
+ },
16
+ preview: {
17
+ view: "preview",
12
18
  },
13
19
  },
14
20
  };
@@ -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,24 @@
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
+ active: Alpine.$persist(drawer.defaultPanel).as("drawer-active"),
10
+ height: Alpine.$persist(drawer.defaultHeight).as("drawer-height"),
11
+ width: Alpine.$persist(drawer.defaultWidth).as("drawer-width"),
12
+ minWidth: drawer.minWidth,
13
+ visibleTabCount: Infinity,
9
14
  },
10
15
  preview: {
11
16
  width: Alpine.$persist("100%").as("preview-width"),
12
17
  height: Alpine.$persist("100%").as("preview-height"),
13
- source: Alpine.$persist(false).as("preview-source"),
18
+ view: Alpine.$persist(preview.view).as("preview-view"),
14
19
  lastWidth: null,
20
+ lastHeight: null,
21
+ resizing: false,
15
22
  },
16
23
  };
17
24
  }
@@ -27,7 +27,9 @@ module Lookbook
27
27
  begin
28
28
  set_params
29
29
  @examples = examples_data
30
- @preview_srcdoc = render_examples(examples_data).gsub("\"", "&quot;")
30
+ @preview_srcdoc = if Lookbook.config.preview_srcdoc
31
+ render_examples(examples_data).gsub("\"", "&quot;")
32
+ end
31
33
  @panels = panels.filter { |name, panel| panel[:show] }
32
34
  rescue *EXCEPTIONS
33
35
  render "error"
@@ -13,7 +13,7 @@
13
13
  window.SOCKET_PATH = "<%= Lookbook::Engine.websocket_mount_path %>";
14
14
  </script>
15
15
  <% end %>
16
- <script src="/lookbook-assets/js/app.js?v=<%= Lookbook::VERSION %>" defer></script>
16
+ <script src="/lookbook-assets/js/app.js?v=<%= Lookbook.version %>" defer></script>
17
17
 
18
18
  <title><%= [@example&.label, @preview&.label, "Lookbook"].compact.join(" :: ") %></title>
19
19
  </head>
@@ -44,9 +44,13 @@
44
44
  x-effect="$store.sidebar.width = Math.min(splits[0] || $store.sidebar.width, $store.sidebar.maxWidth)"
45
45
  x-cloak
46
46
  >
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>
47
+ <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
48
  </div>
49
- <main id="main" class="h-full overflow-hidden w-full" x-show="$store.layout.desktop || !$store.sidebar.open" x-cloak>
49
+ <main
50
+ id="main"
51
+ class="h-full overflow-hidden w-full"
52
+ x-show="$store.layout.desktop || !$store.sidebar.open" x-cloak
53
+ >
50
54
  <%= yield %>
51
55
  </main>
52
56
  </div>