lookbook 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da304532fa0dd06355ed74fa190827c04ed8f1dd07953e0db2e12469a61555dc
4
- data.tar.gz: ab65b3585a8eed51792f6fc1ec561a85f2f2a1b7884a949e65b4dddb2d6f8fa5
3
+ metadata.gz: 71adea21d969026f205c494f5ebdfd5b1546a8dce639733da69bf8eeaa7f371d
4
+ data.tar.gz: dbd8406a04e3b6be61a97fba2daf96ce1bb005b0c7c0f9bdfc85e7443301d7d1
5
5
  SHA512:
6
- metadata.gz: 374b7c82c964772437bede3b57659e9f39d8804c4325ce193ee8d9566c9547b1e041869b9c8efa3a826077449e00cf768a7280d17001d775526a6417f2bbb46e
7
- data.tar.gz: b1c6e3fc74365adbaf37f82cce0d5cf3caec5b9be1ca89a9870a9503b15ae43e2f7d8503dd63b43a4fd3d67eb50c1df137a92b06c7c8b0268c922f02afe2d31c
6
+ metadata.gz: 4913a862f29674edaa775ed2171b37d80f32a7d57b44efea92ee2901a7a122c2bfd941fe776cbc0338de72f0ed2f63c28f108a04d699e944cf93a03456b376c1
7
+ data.tar.gz: f6f914053d0d2db782537180a6193ce24e19641407c156d46ffe48179ca4d60a4d48609b01da58318e4bb8e727ccc86356269f5e96f7746e961516291c771a8e
data/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  ---
14
14
 
15
15
  <div align="center">
16
- <a href="#installing">Installing</a> • <a href="#previews">Previews</a> • <a href="#pages">Pages</a> • <a href="#deployment">Deployment</a> • <a href="#config">Config</a>
16
+ <a href="#installing">Installing</a> • <a href="#previews">Previews</a> • <a href="#pages">Pages</a> • <a href="#deployment">Deployment</a> • <a href="#config">Config</a>
17
17
  </div>
18
18
 
19
19
  ---
@@ -481,10 +481,10 @@ end
481
481
  If you need to add more long-form documentation to live alongside your component previews you can do so using Lookbook's markdown-powered `pages` system.
482
482
 
483
483
  > ⚠️ This feature is currently flagged as an **experimental** feature which requires [feature opt-in](#experimental-features) to use. Its API and implementation may change before it is released.
484
- >
484
+ >
485
485
  > To enable support for pages in your project, add `config.lookbook.experimental_features = [:pages]` into your application configuration file.
486
486
 
487
- ### Pages demo
487
+ ### Pages demo
488
488
 
489
489
  For an example of some pages in Lookbook, check out the [example pages](https://lookbook-demo-app.herokuapp.com/lookbook) in the Lookbook demo app and the associated [page files](https://github.com/allmarkedup/lookbook-demo/tree/main/test/components/docs) in the demo repo.
490
490
 
@@ -496,7 +496,7 @@ Pages must have either a `.html.erb` or a `.md.erb` file extension. All pages a
496
496
 
497
497
  Pages can optionally make use of a **YAML frontmatter block** to customise the behaviour and content of the page itself.
498
498
 
499
- An example page might look like this:
499
+ An example page might look like this:
500
500
 
501
501
  ```markdown
502
502
  ---
@@ -510,7 +510,7 @@ contents will be run through a Markdown parser/renderer before display.
510
510
  Fenced code blocks are fully supported and will be highlighted appropriately.
511
511
 
512
512
  ERB can be used in here.
513
- The template will be rendered **before** being parsed as Markdown.
513
+ The template will be rendered **before** being parsed as Markdown.
514
514
 
515
515
  You can can access data about the page using the `@page` variable.
516
516
  The title of this page is "<%= @page.title %>".
@@ -656,6 +656,35 @@ The default language is `ruby`. To highlight a different language you need to sp
656
656
 
657
657
  > Lookbook uses [Rouge](https://github.com/rouge-ruby/rouge) for syntax highlighting. You can find a [full list of supported languages here](https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md).
658
658
 
659
+ ### Tabs
660
+
661
+ You can break up your page's content into tabs. If your avatar page is named `01_avatar.md.erb`, by declaring a page named `01_avatar[web].md.erb` it will create a **web** tab on the page. Tabs like normal Pages can contain embedded previews and code examples.
662
+
663
+ ```
664
+ test/components/docs/
665
+ ├── 01_avatar.md.erb
666
+ ├── 01_avatar[design].md.erb
667
+ ├── 01_avatar[mobile].md.erb
668
+ ├── 01_avatar[web].md.erb
669
+ ```
670
+
671
+ By declaring the `label` frontmatter you can change the label shown on the tab:
672
+
673
+ ```
674
+ ---
675
+ label: Website
676
+ ---
677
+ ```
678
+
679
+ If you want the tabs in a different order, you can use the `position` frontmatter:
680
+
681
+ ```
682
+ ---
683
+ label: Web
684
+ position: 1
685
+ ---
686
+ ```
687
+
659
688
  ---
660
689
 
661
690
  ### Pages configuration
@@ -743,10 +772,10 @@ If for some reason you wish to enable file watching or runtime preview annotatio
743
772
  # config/environments/production.rb
744
773
 
745
774
  # enable file-change listening
746
- config.lookbook.listen = true
775
+ config.lookbook.listen = true
747
776
 
748
777
  # enable runtime preview parsing
749
- config.lookbook.runtime_parsing = true
778
+ config.lookbook.runtime_parsing = true
750
779
  ```
751
780
 
752
781
  <h2 id="config">General Configuration</h2>
@@ -778,7 +807,7 @@ config.lookbook.listen_paths << Rails.root.join('app/other/directory')
778
807
  If you want to change the favicon used by the Lookbook UI, you can provide a path to your own (or a data-uri string) using the `ui_favicon` option:
779
808
 
780
809
  ```ruby
781
- config.lookbook.ui_favicon = "/path/to/my/favicon.png"
810
+ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
782
811
  ```
783
812
 
784
813
  > To disable the favicon entirely, set the value to `false`.
@@ -788,7 +817,7 @@ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
788
817
  Specify a project name to display in the top left of the UI (instead of the default "Lookbook"):
789
818
 
790
819
  ```ruby
791
- config.lookbook.project_name = "My Project"
820
+ config.lookbook.project_name = "My Project"
792
821
  ```
793
822
 
794
823
  > If you don't want to display a project name at all, set the value to `false`.
@@ -58,7 +58,7 @@
58
58
  }
59
59
 
60
60
  @layer components {
61
- .unsectioned > #nav > ul > li > div {
61
+ .unsectioned > nav > ul > li > div {
62
62
  @apply py-1 border-b border-gray-300;
63
63
  }
64
64
 
@@ -20,6 +20,7 @@ import copy from "./components/copy";
20
20
  import code from "./components/code";
21
21
  import sizes from "./components/sizes";
22
22
  import embed from "./components/embed";
23
+ import pageTabs from "./components/page-tabs";
23
24
 
24
25
  import initFilterStore from "./stores/filter";
25
26
  import initLayoutStore from "./stores/layout";
@@ -61,6 +62,7 @@ Alpine.data("tabs", tabs);
61
62
  Alpine.data("navItem", navItem);
62
63
  Alpine.data("navGroup", navGroup);
63
64
  Alpine.data("embed", embed);
65
+ Alpine.data("pageTabs", pageTabs);
64
66
 
65
67
  // Init
66
68
 
@@ -0,0 +1,9 @@
1
+ export default function pageTabs() {
2
+ return {
3
+ active: "",
4
+
5
+ changeTab(tabName) {
6
+ this.active = tabName;
7
+ },
8
+ };
9
+ }
@@ -14,7 +14,7 @@ module Lookbook
14
14
  if feature_enabled? :pages
15
15
  landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
16
16
  if landing.present?
17
- redirect_to page_path(landing.lookup_path)
17
+ redirect_to lookbook_page_path(landing.lookup_path)
18
18
  end
19
19
  end
20
20
  end
@@ -1,5 +1,7 @@
1
1
  module Lookbook
2
2
  class PagesController < ApplicationController
3
+ helper_method :page_controller
4
+
3
5
  def self.controller_path
4
6
  "lookbook/pages"
5
7
  end
@@ -7,7 +9,7 @@ module Lookbook
7
9
  def index
8
10
  landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
9
11
  if landing.present?
10
- redirect_to page_path landing.lookup_path
12
+ redirect_to lookbook_page_path landing.lookup_path
11
13
  else
12
14
  @title = "Not found"
13
15
  render "not_found"
@@ -45,11 +45,11 @@ module Lookbook
45
45
  if @example
46
46
  @preview = @example.preview
47
47
  if params[:path] == @preview&.lookup_path
48
- redirect_to show_path "#{params[:path]}/#{@preview.default_example.name}"
48
+ redirect_to lookbook_inspect_path "#{params[:path]}/#{@preview.default_example.name}"
49
49
  end
50
50
  else
51
51
  first_example = Lookbook.previews.find(params[:path])&.examples&.first
52
- redirect_to show_path(first_example.lookup_path) if first_example
52
+ redirect_to lookbook_inspect_path(first_example.lookup_path) if first_example
53
53
  end
54
54
  end
55
55
 
@@ -13,7 +13,7 @@ module Lookbook
13
13
  if landing.present?
14
14
  page_path(landing.lookup_path)
15
15
  else
16
- home_path
16
+ lookbook_home_path
17
17
  end
18
18
  end
19
19
  end
@@ -4,7 +4,7 @@ module Lookbook
4
4
 
5
5
  def page_path(id)
6
6
  page = id.is_a?(Page) ? id : Lookbook.pages.find(id)
7
- lookbook.page_path page.lookup_path
7
+ lookbook_page_path page.lookup_path
8
8
  end
9
9
 
10
10
  def embed(*args, params: {}, type: :preview, **opts)
@@ -1,14 +1,14 @@
1
- <div id="<%= id %>" x-data="embed" class="not-prose embed border border-gray-300 rounded-md overflow-hidden" @page:morphed.window="recaclulateIframeHeight">
1
+ <div id="<%= id %>" x-data="embed" class="not-prose embed border border-gray-300 rounded-md overflow-hidden" @page:morphed.window="recaclulateIframeHeight" @tab-switch.window="recaclulateIframeHeight">
2
2
  <header class="px-3 py-2 flex items-center text-xs text-gray-400 bg-white border-b border-gray-300">
3
3
  <div class="text-gray-500">
4
4
  <%= example.preview.label %> (<%= example.label %>)
5
5
  </div>
6
6
  <div class="ml-auto flex items-center space-x-3">
7
- <a href="<%= lookbook.show_path(example.path, params) %>" class="" x-tooltip.theme.lookbook="`View in Inspector`">
7
+ <a href="<%= lookbook_inspect_path(example.path, params) %>" class="" x-tooltip.theme.lookbook="`View in Inspector`">
8
8
  <%= icon "eye", size: 4, class: "hover:text-indigo-800" %>
9
9
  </a>
10
10
  <a
11
- href="<%= lookbook.preview_path(example.path, params) %>"
11
+ href="<%= lookbook_preview_path(example.path, params) %>"
12
12
  target="_blank"
13
13
  class="-top-px relative"
14
14
  x-tooltip.theme.lookbook="`Open in new window`">
@@ -21,7 +21,7 @@
21
21
  <iframe seamless
22
22
  id="<%= id %>-iframe"
23
23
  x-ref="iframe"
24
- src="<%= lookbook.preview_path(example.path, params.merge(lookbook_embed: true)) %>"
24
+ src="<%= lookbook_preview_path(example.path, params.merge(lookbook_embed: true)) %>"
25
25
  class="min-w-full h-full w-px"
26
26
  :class="{ 'pointer-events-none': reflowing }"
27
27
  @load="recaclulateIframeHeight">
@@ -1,6 +1,6 @@
1
- <nav id="nav" x-data="nav(<%= filterable %>)">
1
+ <nav id="nav-<%= items.class.describe_as %>" x-data="nav(<%= filterable %>)">
2
2
  <% if items.any? %>
3
- <ul x-ref="items" id="nav-<%= items.class.describe_as %>">
3
+ <ul x-ref="items" id="nav-<%= items.class.describe_as %>-items">
4
4
  <% items.each do |item| %>
5
5
  <%= component "nav_#{item.type}", node: item %>
6
6
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <%
2
- path = show_path(item.lookup_path)
2
+ path = lookbook_inspect_path(item.lookup_path)
3
3
  display ||= :item
4
4
  label ||= item.label
5
5
  item_icon = display == :node ? "layers" : "eye"
@@ -1,5 +1,5 @@
1
1
  <%
2
- path = page_path(node.lookup_path)
2
+ path = lookbook_page_path(node.lookup_path)
3
3
  depth = node.hierarchy_depth + 1
4
4
  %>
5
5
  <li key="nav-page-<%= node.id %>-item">
@@ -1,6 +1,6 @@
1
1
  <% examples = node.examples.reject(&:hidden?) %>
2
2
  <% if examples.many? %>
3
- <%= component "nav_group", node: node, path: show_path(examples.first.path) do %>
3
+ <%= component "nav_group", node: node, path: lookbook_inspect_path(examples.first.path) do %>
4
4
  <% examples.each do |example| %>
5
5
  <%= component "nav_item", item: example, depth: example.hierarchy_depth + 1 %>
6
6
  <% end %>
@@ -47,7 +47,7 @@
47
47
  <%= icon "refresh-cw", size: 3.5, class: "hover:text-indigo-800" %>
48
48
  </button>
49
49
  <a
50
- href="<%= preview_path %>"
50
+ href="<%= lookbook_preview_path %>"
51
51
  target="_blank"
52
52
  x-tooltip.theme.lookbook="`Open in new window`"
53
53
  data-hotkey="w">
@@ -43,6 +43,32 @@
43
43
  <div id="page-main-<%= @page.id %>" data-morph-strategy="replace">
44
44
  <div class="prose max-w-none prose-a:text-indigo-900">
45
45
  <%= @page_content %>
46
+
47
+ <% if @page.tabs.any? %>
48
+ <div
49
+ id="page-tabs"
50
+ x-data="pageTabs"
51
+ x-init="active = '<%= @page.tabs.first.tab %>'"
52
+ class="">
53
+ <nav class="flex border-b border-gray-300">
54
+ <% @page.tabs.each do |tab| %>
55
+ <button
56
+ class="px-5 py-2 font-bold border-b-2 hover:border-gray-500"
57
+ :class="active === '<%= tab.tab %>' ? '!border-gray-500 !cursor-default' : 'border-gray-50'"
58
+ x-on:click="changeTab('<%= tab.tab %>');$dispatch('tab-switch')">
59
+ <%= tab.label %>
60
+ </button>
61
+ <% end %>
62
+ </nav>
63
+
64
+ <% @page.tabs.each do |tab| %>
65
+ <div id="tab-<%= tab.tab %>" x-show="active === '<%= tab.tab %>'">
66
+ <%= page_controller.render_page(tab) %>
67
+ </div>
68
+ <% end %>
69
+ </div>
70
+ <% end %>
71
+
46
72
  </div>
47
73
  </div>
48
74
  <% if @page.footer? %>
@@ -1 +1 @@
1
- <iframe id="error-iframe" src="<%= url_for lookbook.preview_path %>" frameborder="0" class=" h-screen w-full" seamless></iframe>
1
+ <iframe id="error-iframe" src="<%= url_for lookbook_preview_path %>" frameborder="0" class=" h-screen w-full" seamless></iframe>
@@ -9,7 +9,7 @@
9
9
  <iframe seamless
10
10
  class="h-full w-full border border-gray-300"
11
11
  :class="{ 'pointer-events-none': $store.layout.reflowing }"
12
- src="<%= lookbook.preview_path(request.query_parameters.merge(lookbook_timestamp: Time.now)) %>"
12
+ src="<%= lookbook_preview_path(request.query_parameters.merge(lookbook_timestamp: Time.now)) %>"
13
13
  <% if config.preview_srcdoc %>srcdoc="<%== srcdoc %>"<% end %>
14
14
  frameborder="0"
15
15
  x-data="sizes"
data/config/routes.rb CHANGED
@@ -3,13 +3,13 @@ Lookbook::Engine.routes.draw do
3
3
  mount Lookbook::Engine.websocket => Lookbook.config.cable_mount_path
4
4
  end
5
5
 
6
- root to: "application#index", as: :home
6
+ root to: "application#index", as: :lookbook_home
7
7
 
8
8
  if Lookbook::Features.enabled?(:pages)
9
- get "/#{Lookbook.config.page_route}", to: "pages#index", as: :page_index
10
- get "/#{Lookbook.config.page_route}/*path", to: "pages#show", as: :page
9
+ get "/#{Lookbook.config.page_route}", to: "pages#index", as: :lookbook_page_index
10
+ get "/#{Lookbook.config.page_route}/*path", to: "pages#show", as: :lookbook_page
11
11
  end
12
12
 
13
- get "/preview/*path", to: "previews#preview", as: :preview
14
- get "/*path", to: "previews#show", as: :show
13
+ get "/preview/*path", to: "previews#preview", as: :lookbook_preview
14
+ get "/*path", to: "previews#show", as: :lookbook_inspect
15
15
  end
data/lib/lookbook/page.rb CHANGED
@@ -16,17 +16,22 @@ module Lookbook
16
16
  ]
17
17
 
18
18
  attr_reader :errors
19
+ attr_accessor :tabs
19
20
 
20
21
  def initialize(path, base_path)
21
22
  @pathname = Pathname.new path
22
23
  @base_path = Pathname.new base_path
23
24
  @options = nil
24
25
  @errors = []
26
+ @tabs = []
25
27
  end
26
28
 
27
29
  def path
28
30
  rel_path = @pathname.relative_path_from(@base_path)
29
- (rel_path.dirname.to_s == "." ? name : "#{rel_path.dirname}/#{name}")
31
+
32
+ _path = (rel_path.dirname.to_s == "." ? name : "#{rel_path.dirname}/#{name}")
33
+ _path.gsub!("[#{tab}]", "") if tab?
34
+ _path
30
35
  end
31
36
 
32
37
  def lookup_path
@@ -78,7 +83,16 @@ module Lookbook
78
83
  end
79
84
 
80
85
  def type
81
- :page
86
+ tab? ? :tab : :page
87
+ end
88
+
89
+ def tab
90
+ matches = full_path.to_s.match(%r{\[(?<tab>\w+)\]})
91
+ matches ? remove_position_prefix(matches[:tab]) : nil
92
+ end
93
+
94
+ def tab?
95
+ tab.present?
82
96
  end
83
97
 
84
98
  def method_missing(method_name, *args, &block)
@@ -114,7 +128,7 @@ module Lookbook
114
128
  end
115
129
  @options = Lookbook.config.page_options.deep_merge(frontmatter).with_indifferent_access
116
130
  @options[:id] = @options[:id] ? generate_id(@options[:id]) : generate_id(lookup_path)
117
- @options[:label] ||= name.titleize
131
+ @options[:label] ||= (tab? ? tab : name).titleize
118
132
  @options[:title] ||= @options[:label]
119
133
  @options[:hidden] ||= false
120
134
  @options[:landing] ||= false
@@ -143,12 +157,24 @@ module Lookbook
143
157
  end
144
158
 
145
159
  def all
146
- pages = Array(page_paths).map do |dir|
147
- Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*"].sort.map do |page|
148
- Lookbook::Page.new(page, dir)
149
- end
160
+ pages, tabs =
161
+ Array(page_paths).flat_map do |dir|
162
+ Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*"].sort.map do |page|
163
+ page = Lookbook::Page.new(page, dir)
164
+ end
165
+ end.partition { |page| page.type == :page }
166
+
167
+ sorted_pages = pages
168
+ .uniq { |page| page.path }
169
+ .sort_by { |page| [page.position, page.label] }
170
+
171
+ page_dict = sorted_pages.index_by(&:path)
172
+ sorted_tabs = tabs.sort_by { |tab| [tab.position, tab.label] }
173
+
174
+ sorted_tabs.each do |tab|
175
+ page_dict[tab.path].tabs << tab
150
176
  end
151
- sorted_pages = pages.flatten.uniq { |p| p.path }.sort_by { |page| [page.position, page.label] }
177
+
152
178
  PageCollection.new(sorted_pages)
153
179
  end
154
180
 
@@ -6,7 +6,7 @@ module Lookbook
6
6
  protected
7
7
 
8
8
  def generate_id(*args)
9
- parts = args.map { |arg| arg.to_s.parameterize.underscore }
9
+ parts = args.map { |arg| arg.to_s.force_encoding("UTF-8").parameterize.underscore }
10
10
  parts.join("-").tr("/", "-").tr("_", "-").delete_prefix("-").delete_suffix("-").gsub("--", "-")
11
11
  end
12
12
 
@@ -1,3 +1,3 @@
1
1
  module Lookbook
2
- VERSION = "0.8.3"
2
+ VERSION = "0.9.0"
3
3
  end