lookbook 0.4.6 → 0.5.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/app/assets/lookbook/css/app.css +66 -5
  4. data/app/assets/lookbook/css/tooltip_theme.css +28 -0
  5. data/app/assets/lookbook/js/app.js +41 -53
  6. data/app/assets/lookbook/js/components/code.js +5 -0
  7. data/app/assets/lookbook/js/components/copy.js +16 -0
  8. data/app/assets/lookbook/js/components/filter.js +24 -0
  9. data/app/assets/lookbook/js/components/inspector.js +58 -0
  10. data/app/assets/lookbook/js/{nav/node.js → components/nav-group.js} +17 -16
  11. data/app/assets/lookbook/js/components/nav-item.js +27 -0
  12. data/app/assets/lookbook/js/components/nav.js +35 -0
  13. data/app/assets/lookbook/js/components/page.js +45 -0
  14. data/app/assets/lookbook/js/components/param.js +34 -0
  15. data/app/assets/lookbook/js/components/preview-window.js +107 -0
  16. data/app/assets/lookbook/js/components/sidebar.js +3 -0
  17. data/app/assets/lookbook/js/components/sizes.js +16 -0
  18. data/app/assets/lookbook/js/components/splitter.js +25 -0
  19. data/app/assets/lookbook/js/components/tabs.js +50 -0
  20. data/app/assets/lookbook/js/config.js +20 -0
  21. data/app/assets/lookbook/js/{utils/reloader.js → lib/socket.js} +7 -12
  22. data/app/assets/lookbook/js/lib/split.js +21 -0
  23. data/app/assets/lookbook/js/lib/utils.js +3 -0
  24. data/app/assets/lookbook/js/stores/filter.js +11 -0
  25. data/app/assets/lookbook/js/stores/inspector.js +24 -0
  26. data/app/assets/lookbook/js/stores/layout.js +12 -0
  27. data/app/assets/lookbook/js/stores/nav.js +21 -0
  28. data/app/assets/lookbook/js/stores/sidebar.js +14 -0
  29. data/app/controllers/lookbook/app_controller.rb +71 -96
  30. data/app/helpers/lookbook/application_helper.rb +48 -4
  31. data/app/views/layouts/lookbook/app.html.erb +58 -0
  32. data/app/views/layouts/lookbook/preview.html.erb +12 -0
  33. data/app/views/lookbook/components/_code.html.erb +13 -0
  34. data/app/views/lookbook/components/_copy.html.erb +14 -0
  35. data/app/views/lookbook/components/_drawer.html.erb +121 -0
  36. data/app/views/lookbook/components/_filter.html.erb +15 -0
  37. data/app/views/lookbook/{shared → components}/_header.html.erb +3 -3
  38. data/app/views/lookbook/components/_icon.html.erb +5 -0
  39. data/app/views/lookbook/components/_nav.html.erb +17 -0
  40. data/app/views/lookbook/components/_nav_collection.html.erb +5 -0
  41. data/app/views/lookbook/components/_nav_group.html.erb +14 -0
  42. data/app/views/lookbook/components/_nav_item.html.erb +23 -0
  43. data/app/views/lookbook/components/_nav_preview.html.erb +11 -0
  44. data/app/views/lookbook/components/_param.html.erb +21 -0
  45. data/app/views/lookbook/components/_preview.html.erb +52 -0
  46. data/app/views/lookbook/{app/error.html.erb → error.html.erb} +0 -0
  47. data/app/views/lookbook/index.html.erb +9 -0
  48. data/app/views/lookbook/{workbench/inspector/params → inputs}/_select.html.erb +2 -3
  49. data/app/views/lookbook/inputs/_text.html.erb +8 -0
  50. data/app/views/lookbook/inputs/_textarea.html.erb +8 -0
  51. data/app/views/lookbook/inputs/_toggle.html.erb +13 -0
  52. data/app/views/lookbook/{app/not_found.html.erb → not_found.html.erb} +2 -4
  53. data/app/views/lookbook/panels/_notes.html.erb +25 -0
  54. data/app/views/lookbook/panels/_output.html.erb +18 -0
  55. data/app/views/lookbook/panels/_params.html.erb +17 -0
  56. data/app/views/lookbook/panels/_source.html.erb +20 -0
  57. data/app/views/lookbook/show.html.erb +73 -0
  58. data/lib/lookbook/code_formatter.rb +20 -0
  59. data/lib/lookbook/engine.rb +8 -1
  60. data/lib/lookbook/lang.rb +10 -5
  61. data/lib/lookbook/preview.rb +1 -1
  62. data/lib/lookbook/preview_controller.rb +1 -1
  63. data/lib/lookbook/preview_group.rb +5 -1
  64. data/lib/lookbook/version.rb +1 -1
  65. data/lib/lookbook.rb +2 -0
  66. data/public/lookbook-assets/css/app.css +4 -0
  67. data/public/lookbook-assets/css/app.css.map +1 -0
  68. data/public/lookbook-assets/js/app.js +2 -0
  69. data/public/lookbook-assets/js/app.js.map +1 -0
  70. metadata +59 -45
  71. data/app/assets/lookbook/js/nav/leaf.js +0 -20
  72. data/app/assets/lookbook/js/nav.js +0 -38
  73. data/app/assets/lookbook/js/page.js +0 -38
  74. data/app/assets/lookbook/js/utils/clipboard.js +0 -13
  75. data/app/assets/lookbook/js/utils/morph.js +0 -19
  76. data/app/assets/lookbook/js/utils/screen.js +0 -44
  77. data/app/assets/lookbook/js/utils/size_observer.js +0 -16
  78. data/app/assets/lookbook/js/utils/split.js +0 -26
  79. data/app/assets/lookbook/js/workbench/inspector.js +0 -11
  80. data/app/assets/lookbook/js/workbench/param.js +0 -19
  81. data/app/assets/lookbook/js/workbench/preview.js +0 -39
  82. data/app/assets/lookbook/js/workbench.js +0 -14
  83. data/app/views/lookbook/app/index.html.erb +0 -11
  84. data/app/views/lookbook/app/show.html.erb +0 -1
  85. data/app/views/lookbook/layouts/app.html.erb +0 -41
  86. data/app/views/lookbook/nav/_collection.html.erb +0 -5
  87. data/app/views/lookbook/nav/_leaf.html.erb +0 -22
  88. data/app/views/lookbook/nav/_node.html.erb +0 -19
  89. data/app/views/lookbook/nav/_preview.html.erb +0 -11
  90. data/app/views/lookbook/preview/group.html.erb +0 -8
  91. data/app/views/lookbook/shared/_clipboard.html.erb +0 -11
  92. data/app/views/lookbook/shared/_sidebar.html.erb +0 -45
  93. data/app/views/lookbook/shared/_workbench.html.erb +0 -12
  94. data/app/views/lookbook/workbench/_header.html.erb +0 -39
  95. data/app/views/lookbook/workbench/_inspector.html.erb +0 -38
  96. data/app/views/lookbook/workbench/_preview.html.erb +0 -24
  97. data/app/views/lookbook/workbench/inspector/_code.html.erb +0 -3
  98. data/app/views/lookbook/workbench/inspector/_notes.html.erb +0 -24
  99. data/app/views/lookbook/workbench/inspector/_params.html.erb +0 -28
  100. data/app/views/lookbook/workbench/inspector/_plain.html.erb +0 -3
  101. data/app/views/lookbook/workbench/inspector/params/_text.html.erb +0 -8
  102. data/app/views/lookbook/workbench/inspector/params/_textarea.html.erb +0 -8
  103. data/app/views/lookbook/workbench/inspector/params/_toggle.html.erb +0 -13
  104. data/public/lookbook-assets/app.css +0 -2626
  105. 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
+ }, 200),
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