lookbook 0.4.5 → 0.5.0.beta.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -4
  3. data/app/assets/lookbook/css/app.css +63 -5
  4. data/app/assets/lookbook/css/tooltip_theme.css +28 -0
  5. data/app/assets/lookbook/js/app.js +40 -54
  6. data/app/assets/lookbook/js/components/copy.js +16 -0
  7. data/app/assets/lookbook/js/components/filter.js +24 -0
  8. data/app/assets/lookbook/js/components/inspector.js +58 -0
  9. data/app/assets/lookbook/js/{nav/node.js → components/nav-group.js} +17 -16
  10. data/app/assets/lookbook/js/components/nav-item.js +27 -0
  11. data/app/assets/lookbook/js/components/nav.js +35 -0
  12. data/app/assets/lookbook/js/components/page.js +45 -0
  13. data/app/assets/lookbook/js/components/param.js +32 -0
  14. data/app/assets/lookbook/js/components/preview-window.js +107 -0
  15. data/app/assets/lookbook/js/components/sidebar.js +3 -0
  16. data/app/assets/lookbook/js/components/sizes.js +16 -0
  17. data/app/assets/lookbook/js/components/splitter.js +25 -0
  18. data/app/assets/lookbook/js/components/tabs.js +50 -0
  19. data/app/assets/lookbook/js/config.js +20 -0
  20. data/app/assets/lookbook/js/{utils/reloader.js → lib/socket.js} +7 -12
  21. data/app/assets/lookbook/js/lib/split.js +21 -0
  22. data/app/assets/lookbook/js/lib/utils.js +3 -0
  23. data/app/assets/lookbook/js/stores/filter.js +11 -0
  24. data/app/assets/lookbook/js/stores/inspector.js +24 -0
  25. data/app/assets/lookbook/js/stores/layout.js +12 -0
  26. data/app/assets/lookbook/js/stores/nav.js +21 -0
  27. data/app/assets/lookbook/js/stores/sidebar.js +14 -0
  28. data/app/controllers/lookbook/app_controller.rb +71 -96
  29. data/app/helpers/lookbook/application_helper.rb +48 -4
  30. data/app/views/layouts/lookbook/app.html.erb +58 -0
  31. data/app/views/layouts/lookbook/preview.html.erb +12 -0
  32. data/app/views/lookbook/components/_code.html.erb +8 -0
  33. data/app/views/lookbook/components/_copy.html.erb +14 -0
  34. data/app/views/lookbook/components/_drawer.html.erb +121 -0
  35. data/app/views/lookbook/components/_filter.html.erb +15 -0
  36. data/app/views/lookbook/{shared → components}/_header.html.erb +3 -3
  37. data/app/views/lookbook/components/_icon.html.erb +5 -0
  38. data/app/views/lookbook/components/_nav.html.erb +17 -0
  39. data/app/views/lookbook/components/_nav_collection.html.erb +5 -0
  40. data/app/views/lookbook/components/_nav_group.html.erb +14 -0
  41. data/app/views/lookbook/components/_nav_item.html.erb +23 -0
  42. data/app/views/lookbook/components/_nav_preview.html.erb +11 -0
  43. data/app/views/lookbook/components/_param.html.erb +24 -0
  44. data/app/views/lookbook/components/_preview.html.erb +52 -0
  45. data/app/views/lookbook/{app/error.html.erb → error.html.erb} +0 -0
  46. data/app/views/lookbook/index.html.erb +9 -0
  47. data/app/views/lookbook/{workbench/inspector/params → inputs}/_select.html.erb +2 -2
  48. data/app/views/lookbook/{workbench/inspector/params → inputs}/_text.html.erb +3 -2
  49. data/app/views/lookbook/inputs/_textarea.html.erb +9 -0
  50. data/app/views/lookbook/{workbench/inspector/params → inputs}/_toggle.html.erb +5 -5
  51. data/app/views/lookbook/{app/not_found.html.erb → not_found.html.erb} +2 -4
  52. data/app/views/lookbook/panels/_notes.html.erb +25 -0
  53. data/app/views/lookbook/panels/_output.html.erb +18 -0
  54. data/app/views/lookbook/panels/_params.html.erb +17 -0
  55. data/app/views/lookbook/panels/_source.html.erb +20 -0
  56. data/app/views/lookbook/show.html.erb +73 -0
  57. data/lib/lookbook/code_formatter.rb +20 -0
  58. data/lib/lookbook/engine.rb +8 -1
  59. data/lib/lookbook/lang.rb +10 -5
  60. data/lib/lookbook/preview.rb +1 -1
  61. data/lib/lookbook/preview_controller.rb +1 -1
  62. data/lib/lookbook/preview_group.rb +5 -1
  63. data/lib/lookbook/version.rb +1 -1
  64. data/lib/lookbook.rb +2 -0
  65. data/public/lookbook-assets/css/app.css +4 -0
  66. data/public/lookbook-assets/css/app.css.map +1 -0
  67. data/public/lookbook-assets/js/app.js +2 -0
  68. data/public/lookbook-assets/js/app.js.map +1 -0
  69. metadata +58 -45
  70. data/app/assets/lookbook/js/nav/leaf.js +0 -20
  71. data/app/assets/lookbook/js/nav.js +0 -38
  72. data/app/assets/lookbook/js/page.js +0 -38
  73. data/app/assets/lookbook/js/utils/clipboard.js +0 -13
  74. data/app/assets/lookbook/js/utils/morph.js +0 -19
  75. data/app/assets/lookbook/js/utils/screen.js +0 -44
  76. data/app/assets/lookbook/js/utils/size_observer.js +0 -16
  77. data/app/assets/lookbook/js/utils/split.js +0 -26
  78. data/app/assets/lookbook/js/workbench/inspector.js +0 -11
  79. data/app/assets/lookbook/js/workbench/param.js +0 -19
  80. data/app/assets/lookbook/js/workbench/preview.js +0 -39
  81. data/app/assets/lookbook/js/workbench.js +0 -14
  82. data/app/views/lookbook/app/index.html.erb +0 -11
  83. data/app/views/lookbook/app/show.html.erb +0 -1
  84. data/app/views/lookbook/layouts/app.html.erb +0 -41
  85. data/app/views/lookbook/nav/_collection.html.erb +0 -5
  86. data/app/views/lookbook/nav/_leaf.html.erb +0 -22
  87. data/app/views/lookbook/nav/_node.html.erb +0 -19
  88. data/app/views/lookbook/nav/_preview.html.erb +0 -11
  89. data/app/views/lookbook/preview/group.html.erb +0 -8
  90. data/app/views/lookbook/shared/_clipboard.html.erb +0 -11
  91. data/app/views/lookbook/shared/_sidebar.html.erb +0 -45
  92. data/app/views/lookbook/shared/_workbench.html.erb +0 -12
  93. data/app/views/lookbook/workbench/_header.html.erb +0 -39
  94. data/app/views/lookbook/workbench/_inspector.html.erb +0 -38
  95. data/app/views/lookbook/workbench/_preview.html.erb +0 -24
  96. data/app/views/lookbook/workbench/inspector/_code.html.erb +0 -3
  97. data/app/views/lookbook/workbench/inspector/_notes.html.erb +0 -24
  98. data/app/views/lookbook/workbench/inspector/_params.html.erb +0 -28
  99. data/app/views/lookbook/workbench/inspector/_plain.html.erb +0 -3
  100. data/app/views/lookbook/workbench/inspector/params/_textarea.html.erb +0 -8
  101. data/public/lookbook-assets/app.css +0 -2626
  102. data/public/lookbook-assets/app.js +0 -8718
@@ -0,0 +1,107 @@
1
+ export default function preview() {
2
+ return {
3
+ get store() {
4
+ return this.$store.inspector.preview;
5
+ },
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() {
19
+ this.$store.layout.reflowing = true;
20
+ this.store.resizing = true;
21
+ },
22
+ end() {
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();
63
+ },
64
+ toggleFullWidth() {
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;
101
+ } else {
102
+ this.store.lastHeight = height;
103
+ this.store.height = "100%";
104
+ }
105
+ },
106
+ };
107
+ }
@@ -0,0 +1,3 @@
1
+ export default function sidebar() {
2
+ return {};
3
+ }
@@ -0,0 +1,16 @@
1
+ export default function sizeObserver() {
2
+ return {
3
+ width: 0,
4
+ height: 0,
5
+ init() {
6
+ const ro = new ResizeObserver((entries) => {
7
+ const rect = entries[0].contentRect;
8
+ this.width = Math.round(rect.width);
9
+ this.height = Math.round(rect.height);
10
+ });
11
+ ro.observe(this.$el);
12
+ this.width = Math.round(this.$el.clientWidth);
13
+ this.height = Math.round(this.$el.clientHeight);
14
+ },
15
+ };
16
+ }
@@ -0,0 +1,25 @@
1
+ import Split from "split-grid";
2
+
3
+ export default function splitter(direction, props = {}) {
4
+ return {
5
+ splits: [],
6
+ init() {
7
+ const type = `${direction === "vertical" ? "column" : "row"}Gutters`;
8
+ const element = this.$el;
9
+ Split({
10
+ [type]: [{ track: 1, element }],
11
+ minSize: props.minSize || 0,
12
+ writeStyle() {},
13
+ onDrag: (dir, track, style) => {
14
+ this.splits = style.split(" ").map((num) => parseInt(num));
15
+ },
16
+ onDragStart: () => {
17
+ this.$store.layout.reflowing = true;
18
+ },
19
+ onDragEnd: () => {
20
+ this.$store.layout.reflowing = false;
21
+ },
22
+ });
23
+ },
24
+ };
25
+ }
@@ -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
+ }
@@ -0,0 +1,20 @@
1
+ export default {
2
+ desktopWidth: 768,
3
+ sidebar: {
4
+ defaultWidth: 280,
5
+ minWidth: 200,
6
+ maxWidth: 350,
7
+ },
8
+ inspector: {
9
+ drawer: {
10
+ orientation: "horizontal",
11
+ defaultPanel: "source",
12
+ defaultHeight: 200,
13
+ defaultWidth: 500,
14
+ minWidth: 350,
15
+ },
16
+ preview: {
17
+ view: "preview",
18
+ },
19
+ },
20
+ };
@@ -1,21 +1,16 @@
1
1
  import { createConsumer } from "@rails/actioncable";
2
2
  import debounce from "debounce";
3
3
 
4
- export default function (endpoint) {
4
+ export default function socket(endpoint) {
5
5
  const uid = (Date.now() + ((Math.random() * 100) | 0)).toString();
6
6
  const consumer = createConsumer(`${endpoint}?uid=${uid}`);
7
-
8
7
  return {
9
- uid,
10
- consumer,
11
- start() {
12
- const received = debounce(() => {
13
- console.log("Lookbook files changed");
14
- document.dispatchEvent(new CustomEvent("refresh"));
15
- }, 300);
16
-
17
- consumer.subscriptions.create("Lookbook::ReloadChannel", {
18
- received,
8
+ addListener(channel, callback) {
9
+ consumer.subscriptions.create(channel, {
10
+ received: debounce((data) => {
11
+ console.log("Lookbook files changed");
12
+ callback(data);
13
+ }, 300),
19
14
  connected() {
20
15
  console.log("Lookbook websocket connected");
21
16
  },
@@ -0,0 +1,21 @@
1
+ import Split from "split-grid";
2
+
3
+ export default function (element, props) {
4
+ Split({
5
+ [`${props.direction === "vertical" ? "row" : "column"}Gutters`]: [
6
+ { track: 1, element },
7
+ ],
8
+ minSize: props.minSize,
9
+ writeStyle() {},
10
+ onDrag(dir, track, style) {
11
+ const splits = style.split(" ").map((num) => parseInt(num));
12
+ props.onDrag(splits);
13
+ },
14
+ // onDragStart() {
15
+ // this.reflowing = true;
16
+ // },
17
+ // onDragEnd() {
18
+ // this.reflowing = false;
19
+ // },
20
+ });
21
+ }
@@ -0,0 +1,3 @@
1
+ export function getAlpineData(el) {
2
+ return el._x_dataStack[0];
3
+ }
@@ -0,0 +1,11 @@
1
+ export default function createFilterStore(Alpine) {
2
+ return {
3
+ raw: Alpine.$persist("").as("filter-text"),
4
+ get text() {
5
+ return this.raw.replace(/\s/g, "").toLowerCase();
6
+ },
7
+ get active() {
8
+ return this.text.length > 0;
9
+ },
10
+ };
11
+ }
@@ -0,0 +1,24 @@
1
+ import config from "../config";
2
+
3
+ export default function createInspectorStore(Alpine) {
4
+ const { drawer, preview } = config.inspector;
5
+ return {
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,
14
+ },
15
+ preview: {
16
+ width: Alpine.$persist("100%").as("preview-width"),
17
+ height: Alpine.$persist("100%").as("preview-height"),
18
+ view: Alpine.$persist(preview.view).as("preview-view"),
19
+ lastWidth: null,
20
+ lastHeight: null,
21
+ resizing: false,
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,12 @@
1
+ import config from "../config";
2
+
3
+ export default function createLayoutStore() {
4
+ return {
5
+ init() {
6
+ this.desktop = window.innerWidth >= config.desktopWidth;
7
+ },
8
+ reflowing: false,
9
+ desktop: true,
10
+ desktopWidth: config.desktopWidth,
11
+ };
12
+ }
@@ -0,0 +1,21 @@
1
+ export default function createNavStore(Alpine) {
2
+ return {
3
+ open: Alpine.$persist([]).as("nav-open"),
4
+ active: Alpine.$persist(null).as("nav-active"),
5
+ isOpen(id) {
6
+ return this.open.includes(id);
7
+ },
8
+ setOpen(id) {
9
+ this.open.push(id);
10
+ },
11
+ setClosed(id) {
12
+ const index = this.open.indexOf(id);
13
+ if (index > -1) {
14
+ this.open.splice(index, 1);
15
+ }
16
+ },
17
+ toggle(id) {
18
+ this.isOpen(id) ? this.setClosed(id) : this.setOpen(id);
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,14 @@
1
+ import config from "../config";
2
+
3
+ export default function createSidebarStore(Alpine) {
4
+ const { defaultWidth, minWidth, maxWidth } = config.sidebar;
5
+ return {
6
+ open: Alpine.$persist(false).as("sidebar-open"),
7
+ width: Alpine.$persist(defaultWidth).as("sidebar-width"),
8
+ minWidth,
9
+ maxWidth,
10
+ toggle() {
11
+ Alpine.store("sidebar").open = !Alpine.store("sidebar").open;
12
+ },
13
+ };
14
+ }
@@ -1,52 +1,46 @@
1
- require "htmlbeautifier"
2
-
3
1
  module Lookbook
4
2
  class AppController < ActionController::Base
5
3
  EXCEPTIONS = [ViewComponent::PreviewTemplateError, ViewComponent::ComponentError, ViewComponent::TemplateError, ActionView::Template::Error]
6
4
 
7
5
  protect_from_forgery with: :exception
8
- prepend_view_path File.expand_path("../../views/lookbook", __dir__)
9
-
10
- layout "layouts/app"
11
6
  helper Lookbook::ApplicationHelper
12
7
 
13
8
  before_action :find_preview, only: [:preview, :show]
14
9
  before_action :find_example, only: [:preview, :show]
15
- before_action :assign_nav, only: [:index, :show]
16
- before_action :initialize_inspector, only: [:show]
10
+ before_action :build_nav
11
+
12
+ def self.controller_path
13
+ "lookbook"
14
+ end
17
15
 
18
16
  def preview
19
17
  if @example
20
- render html: rendered_example
18
+ set_params
19
+ render html: render_examples(examples_data)
21
20
  else
22
- render "app/not_found"
21
+ render "not_found"
23
22
  end
24
23
  end
25
24
 
26
25
  def show
27
26
  if @example
28
27
  begin
29
- @rendered_example = rendered_example.gsub("\"", "&quot;")
30
- (@example.type == :group ? @example.examples : [@example]).each do |example|
31
- include_example_data(example)
28
+ set_params
29
+ @examples = examples_data
30
+ @preview_srcdoc = if Lookbook.config.preview_srcdoc
31
+ render_examples(examples_data).gsub("\"", "&quot;")
32
32
  end
33
- assign_inspector
33
+ @panels = panels.filter { |name, panel| panel[:show] }
34
34
  rescue *EXCEPTIONS
35
- render "app/error"
35
+ render "error"
36
36
  end
37
37
  else
38
- render "app/not_found"
38
+ render "not_found"
39
39
  end
40
40
  end
41
41
 
42
42
  private
43
43
 
44
- def initialize_inspector
45
- @source = []
46
- @output = []
47
- @notes = []
48
- end
49
-
50
44
  def find_preview
51
45
  candidates = []
52
46
  params[:path].to_s.scan(%r{/|$}) { candidates << $` }
@@ -65,101 +59,47 @@ module Lookbook
65
59
  end
66
60
  end
67
61
 
68
- def include_example_data(example)
69
- content = HtmlBeautifier.beautify(preview_controller.render_example_to_string(@preview, example.name))
70
- @output << {
71
- label: "<!-- #{example.label} -->",
72
- content: content,
73
- lang: Lookbook::Lang.find(:html)
74
- }
62
+ def examples_data
63
+ @examples_data ||= (@example.type == :group ? @example.examples : [@example]).map do |example|
64
+ example_data(example)
65
+ end
66
+ end
67
+
68
+ def example_data(example)
75
69
  render_args = @preview.render_args(example.name, params: preview_controller.params.permit!)
76
70
  has_template = render_args[:template] != "view_components/preview"
77
- @source << {
78
- label: has_template ? "<!-- #{example.label} -->" : "\# #{example.label}",
79
- content: has_template ? example.template_source(render_args[:template]) : example.method_source,
80
- lang: has_template ? example.template_lang(render_args[:template]) : example.source_lang
71
+ {
72
+ label: example.label,
73
+ notes: example.notes,
74
+ html: preview_controller.render_example_to_string(@preview, example.name),
75
+ source: has_template ? example.template_source(render_args[:template]) : example.method_source,
76
+ source_lang: has_template ? example.template_lang(render_args[:template]) : example.source_lang,
77
+ params: enabled?(:params) ? example.params : []
81
78
  }
82
- if example.notes.present?
83
- @notes << {
84
- label: example.label,
85
- content: example.notes
86
- }
87
- end
88
79
  end
89
80
 
90
- def rendered_example
91
- if @example.type == :group
92
- examples = @example.examples.map do |example|
93
- {
94
- label: example.label,
95
- html: preview_controller.render_example_to_string(@preview, example.name)
96
- }
97
- end
98
- set_params
99
- preview_controller.render_in_layout_to_string("lookbook/preview/group", {examples: examples}, @preview.lookbook_layout)
100
- else
101
- set_params(@example)
102
- preview_controller.params[:path] = "#{@preview.preview_name}/#{@example.name}".chomp("/")
103
- preview_controller.process(:previews)
104
- end
81
+ def render_examples(examples)
82
+ preview_controller.render_in_layout_to_string("layouts/lookbook/preview", {examples: examples}, @preview.lookbook_layout)
105
83
  end
106
84
 
107
- def set_params(example = nil)
108
- if example.present? && enabled?(:params)
85
+ def set_params
86
+ if enabled?(:params)
109
87
  # cast known params to type
110
- example.params.each do |param|
88
+ @example.params.each do |param|
111
89
  if preview_controller.params.key?(param[:name])
112
90
  preview_controller.params[param[:name]] = Lookbook::Params.cast(preview_controller.params[param[:name]], param[:type])
113
91
  end
114
92
  end
115
93
  end
116
94
  # set display params
117
- example_params = example.nil? ? @preview.display_params : example.display_params
118
95
  preview_controller.params.merge!({
119
96
  lookbook: {
120
- display: example_params
97
+ display: @example.display_params
121
98
  }
122
99
  })
123
100
  end
124
101
 
125
- def assign_inspector
126
- @inspector = {
127
- panes: {
128
- source: {
129
- label: "Source",
130
- template: "code",
131
- hotkey: "s",
132
- items: @source,
133
- clipboard: @source.map { |s| @source.many? ? "#{s[:label]}\n#{s[:content]}" : s[:content] }.join("\n\n")
134
- },
135
- output: {
136
- label: "Output",
137
- template: "code",
138
- hotkey: "o",
139
- items: @output,
140
- clipboard: @output.map { |o| @output.many? ? "#{o[:label]}\n#{o[:content]}" : o[:content] }.join("\n\n")
141
- },
142
- notes: {
143
- label: "Notes",
144
- template: "notes",
145
- hotkey: "n",
146
- items: @notes,
147
- disabled: @notes.none?
148
- }
149
- }
150
- }
151
- if enabled?(:params)
152
- @inspector[:panes][:params] = {
153
- label: "Params",
154
- template: "params",
155
- hotkey: "p",
156
- items: @source.many? ? [] : @example.params,
157
- disabled: @source.many? || @example.params.none?
158
- }
159
- end
160
- end
161
-
162
- def assign_nav
102
+ def build_nav
163
103
  @nav = Collection.new
164
104
  previews.reject { |p| p.hidden? }.each do |preview|
165
105
  current = @nav
@@ -179,6 +119,41 @@ module Lookbook
179
119
  @nav
180
120
  end
181
121
 
122
+ def panels
123
+ {
124
+ source: {
125
+ label: "Source",
126
+ template: "lookbook/panels/source",
127
+ hotkey: "s",
128
+ show: true,
129
+ disabled: false,
130
+ copy: true
131
+ },
132
+ output: {
133
+ label: "Output",
134
+ template: "lookbook/panels/output",
135
+ hotkey: "o",
136
+ show: true,
137
+ disabled: false,
138
+ copy: true
139
+ },
140
+ notes: {
141
+ label: "Notes",
142
+ template: "lookbook/panels/notes",
143
+ hotkey: "n",
144
+ show: true,
145
+ disabled: @examples.filter { |e| e[:notes].present? }.none?
146
+ },
147
+ params: {
148
+ label: "Params",
149
+ template: "lookbook/panels/params",
150
+ hotkey: "p",
151
+ show: enabled?(:params),
152
+ disabled: @example.type == :group || @example.params.none?
153
+ }
154
+ }
155
+ end
156
+
182
157
  def previews
183
158
  Lookbook::Preview.all.sort_by(&:label)
184
159
  end
@@ -1,5 +1,6 @@
1
1
  require "redcarpet"
2
2
  require "rouge"
3
+ require "htmlbeautifier"
3
4
 
4
5
  module Lookbook
5
6
  module ApplicationHelper
@@ -16,14 +17,57 @@ module Lookbook
16
17
  markdown.render(text).html_safe
17
18
  end
18
19
 
19
- def highlight(source, language)
20
- formatter = Rouge::Formatters::HTML.new(css_class: "highlight")
20
+ def highlight(source, language, opts = {})
21
+ formatter = Lookbook::CodeFormatter.new(opts)
21
22
  lexer = Rouge::Lexer.find(language)
22
23
  formatter.format(lexer.lex(source)).html_safe
23
24
  end
24
25
 
25
- def nav_padding_style(depth)
26
- "padding-left: calc((#{depth - 1} * 12px) + 0.5rem);"
26
+ def beautify(source, language = "html")
27
+ source = source.strip
28
+ result = language.downcase == "html" ? HtmlBeautifier.beautify(source) : source
29
+ result.strip.html_safe
30
+ end
31
+
32
+ def icon(name = nil, size: 4, **attrs)
33
+ render "lookbook/components/icon",
34
+ name: name,
35
+ size: size,
36
+ classes: class_names(attrs[:class]),
37
+ **attrs.except(:class)
38
+ end
39
+
40
+ def component(name, **attrs, &block)
41
+ render "lookbook/components/#{name}",
42
+ classes: class_names(attrs[:class]),
43
+ **attrs.except(:class),
44
+ &block
45
+ end
46
+
47
+ if Rails.version.to_f < 6.1
48
+ def class_names(*args)
49
+ tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
50
+ safe_join(tokens, " ")
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def build_tag_values(*args)
57
+ tag_values = []
58
+ args.each do |tag_value|
59
+ case tag_value
60
+ when Hash
61
+ tag_value.each do |key, val|
62
+ tag_values << key.to_s if val && key.present?
63
+ end
64
+ when Array
65
+ tag_values.concat build_tag_values(*tag_value)
66
+ else
67
+ tag_values << tag_value.to_s if tag_value.present?
68
+ end
69
+ end
70
+ tag_values
27
71
  end
28
72
  end
29
73
  end