lookbook 0.3.4 → 0.4.1

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: 64f02e35ce90b1e27249752c3ea536ead68b7f6dba3a6bb0991dff8f29f63d92
4
- data.tar.gz: effca06a618749eb6be51880929f417d0de03fe180d60e3df026065a7d0718ee
3
+ metadata.gz: 6556e23e693e27f706b4989f25c13c75a453ce5582e835e6c380a226840feff8
4
+ data.tar.gz: c28f4d88136a02522fb23adc826ec1066c0800ef6a223451149b4516448d318c
5
5
  SHA512:
6
- metadata.gz: 3323004e8ea85985c687adfe184b0519b3b168676df1640cdb5ada335ffef304a1266e6ad239e62968261e2c043c98b23589dd28ed3913e22c620500fc76cdde
7
- data.tar.gz: a60ed3e4b295875fcac660f560d75dc4ac9ecff3cf26a410c30837326c84e26e8e2cea4ad183879ee94049836ab68fb81b3d59c0aab6c081d239744184cfc374
6
+ metadata.gz: 92076777e5a0e2ca449eec799a4702965bc78c12ae1d48f0a7de9276832f02b320f25ecc5384d56aeb5138a953b26a8251ad44756d6a36a58c7fbe84255078e4
7
+ data.tar.gz: e840534cd52b19c9db2b39316739b7ee8df820c96837f59cfd03e96fe99d90778eddc5518ffc5ac0167f61e27f74a2fafaa83642eb7697fd433d34cd09f28550
data/README.md CHANGED
@@ -16,19 +16,17 @@
16
16
 
17
17
  It uses (and extends) the native [ViewComponent preview functionality](https://viewcomponent.org/guide/previews.html), so you don't need to learn a new DSL or create any extra files to get up and running.
18
18
 
19
- Lookbook uses [RDoc/Yard-style comment tags](https://rubydoc.info/gems/yard/file/docs/Tags.md) to extend the capabilities of ViewComponent's previews whilst maintaining compatability with the standard preview class format, so you can add or remove Lookbook at any time without having to rework your code.
19
+ Lookbook uses [RDoc/Yard-style comment tags](#annotating-preview-files) to extend the capabilities of ViewComponent's previews whilst maintaining compatability with the standard preview class format, so you can add or remove Lookbook at any time without having to rework your code.
20
20
 
21
21
  ![Lookbook UI](.github/assets/lookbook_screenshot.png)
22
22
 
23
23
  ### Features
24
24
 
25
- - Tree-style navigation menu
26
- - Live nav search/filter
25
+ - Tree-style navigation menu with live search/filter
27
26
  - Resizable preview window for responsive testing
28
27
  - Highlighted preview source code and HTML output
29
- - Add notes via comments in the preview file (markdown supported)
30
28
  - Auto-updating UI when component or preview files are updated _(Rails v6.0+ only)_
31
- - Hide, group and rename preview examples using comment tags
29
+ - Use comment tag annotations for granular customisation of the preview experience
32
30
  - Fully compatible with standard the ViewComponent preview system
33
31
 
34
32
  ## Lookbook demo
@@ -39,8 +37,6 @@ The [demo app repo](https://github.com/allmarkedup/lookbook-demo) contains instr
39
37
 
40
38
  ## Installing
41
39
 
42
- > ⚠️ **Please note:** Lookbook is still in the early stages of development and has not yet been well tested across a wide range of Rails/ViewComponent versions and setups. If you run into any problems please [open an issue](issues) with as much detail as possible. Thanks! ⚠️
43
-
44
40
  ### 1. Add as a dependency
45
41
 
46
42
  Add Lookbook to your `Gemfile` somewhere **after** the ViewComponent gem. For example:
@@ -74,12 +70,13 @@ You don't need to do anything special to see your ViewComponent previews and exa
74
70
 
75
71
  > If you are new to ViewComponent development, checkout the ViewComponent [documentation](https://viewcomponent.org/guide/) on how to get started developing your components and [creating previews](https://viewcomponent.org/guide/previews.html).
76
72
 
77
- ### Annotating preview files
73
+ ## Annotating preview files
78
74
 
79
75
  Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/docs/Tags.md) in your preview classes to customise and extend the standard ViewComponent preview experience:
80
76
 
81
77
  ```ruby
82
78
  # @label Basic Button
79
+ # @display bg_color "#fff"
83
80
  class ButtonComponentPreview < ViewComponent::Preview
84
81
 
85
82
  # Primary button
@@ -93,11 +90,13 @@ class ButtonComponentPreview < ViewComponent::Preview
93
90
  end
94
91
  end
95
92
 
96
- # Secondary button
93
+ # Inverted button
97
94
  # ---------------
98
- # This should be used for less important actions.
95
+ # For light-on-dark screens
96
+ #
97
+ # @display bg_color "#000"
99
98
  def secondary
100
- render ButtonComponent.new(style: :secondary) do
99
+ render ButtonComponent.new(style: :inverted) do
101
100
  "Click me"
102
101
  end
103
102
  end
@@ -142,7 +141,12 @@ end
142
141
 
143
142
  The following Lookbook-specific tags are available for use:
144
143
 
145
- #### `@label <text>`
144
+ * `@label <label>` -[Customise navigation labels](#-label-text)
145
+ * `@hidden` - [Prevent items displaying in the navigation](#-hidden)
146
+ * `@display <key> <value>` - [Specify params to pass into the preview template](#-display-key-value)
147
+ * `@!group <name> ... @!endgroup` - [Render examples in a group on the same page](#-group-name--endgroup)
148
+
149
+ ### 🔖 `@label <text>`
146
150
 
147
151
  Used to replace the auto-generated navigation label for the item with `<text>`.
148
152
 
@@ -158,7 +162,7 @@ class FooComponentPreview < ViewComponent::Preview
158
162
  end
159
163
  ```
160
164
 
161
- #### `@hidden`
165
+ ### 🔖 `@hidden`
162
166
 
163
167
  Used to temporarily exclude an item from the Lookbook navigation. The item will still be accessible via it's URL.
164
168
 
@@ -176,7 +180,91 @@ class FooComponentPreview < ViewComponent::Preview
176
180
  end
177
181
  ```
178
182
 
179
- #### `@!group <name> ... @!endgroup`
183
+ ### 🔖 `@display <key> <value>`
184
+
185
+ 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.
186
+
187
+ ```ruby
188
+ # @display bg_color "#eee"
189
+ class FooComponentPreview < ViewComponent::Preview
190
+
191
+ # @display max_width "500px"
192
+ # @display wrapper true
193
+ def default
194
+ end
195
+ end
196
+ ```
197
+
198
+ The `@display` tag can be applied at the preview (class) or at the example (method) level, and takes the following format:
199
+
200
+ ```ruby
201
+ # @display <key> <value>
202
+ ```
203
+
204
+ - `<key>` must be a valid Ruby hash key name, without quotes or spaces
205
+ - `<value>` must be a valid JSON-parsable value. It can be a string (surrounded by **double** quotes), a boolean or an integer.
206
+
207
+ > [See below for some examples](#some-display-value-examples) of valid and invalid `@display` values.
208
+
209
+ These display parameters can then be accessed via the `params` hash in your preview layout using `params[:lookbook][:display][<key>]`:
210
+
211
+ ```html
212
+ <!DOCTYPE html>
213
+ <html style="background-color: <%= params[:lookbook][:display][:bg_color] %>">
214
+ <head>
215
+ <title>Preview Layout</title>
216
+ </head>
217
+ <body>
218
+ <div style="max-width: <%= params[:lookbook][:display][:max_width] || '100%' %>">
219
+ <% if params[:lookbook][:display][:wrapper] == true %>
220
+ <div class="wrapper"><%= yield %></div>
221
+ <% else %>
222
+ <%= yield %>
223
+ <% end %>
224
+ </div>
225
+ </body>
226
+ </html>
227
+ ```
228
+
229
+ > By default ViewComponent will use your default application layout for displaying the rendered example. However it's often better to create a seperate layout that you can customise and use specifically for previewing your components. See the ViewComponent [preview docs](https://viewcomponent.org/guide/previews.html) for instructions on how to set that up.
230
+
231
+ Any `@display` params set at the preview (class) level with be merged with those set on individual example methods.
232
+
233
+ #### Global display params
234
+
235
+ Global (fallback) display params can be defined via a configuration option:
236
+
237
+ ```ruby
238
+ # config/application.rb
239
+ config.lookbook.preview_display_params = {
240
+ bg_color: "#fff",
241
+ max_width: "100%"
242
+ }
243
+ ```
244
+
245
+ Globally defined display params will be available to all previews. Any preview or example-level `@display` values with the same name will take precedence and override a globally-set one.
246
+
247
+ #### Some `@display` value examples:
248
+
249
+ Valid:
250
+
251
+ ```ruby
252
+ # @display body_classes "bg-red border border-4 border-green"
253
+ # @display wrap_in_container true
254
+ # @display emojis_to_show 4
255
+ # @display page_title "Special example title"
256
+ ```
257
+
258
+ Invalid:
259
+
260
+ ```ruby
261
+ # @display body_classes 'bg-red border border-4 border-green' [❌ single quotes]
262
+ # @display wrap_in_container should_wrap [❌ unquoted string, perhaps trying to call a method]
263
+ # @display page title "Special example title" [❌ space in key]
264
+ # @display bg_color #fff [❌ colors need quotes around them, it's not CSS!]
265
+ ```
266
+
267
+ ### 🔖 `@!group <name> ... @!endgroup`
180
268
 
181
269
  For smaller components, it can often make sense to render a set of preview examples in a single window, rather than representing them as individual items in the navigation which can start to look a bit cluttered.
182
270
 
@@ -222,7 +310,7 @@ The example above would display the `Sizes` examples grouped together on a singl
222
310
 
223
311
  You can have as many groups as you like within a single preview class, but each example can only belong to one group.
224
312
 
225
- #### Adding notes
313
+ ### Adding notes
226
314
 
227
315
  All comment text other than tags will be treated as markdown and rendered in the **Notes** panel for that example in the Lookbook UI.
228
316
 
@@ -14,6 +14,17 @@ export default function navNode() {
14
14
  ? Array.from(this.$refs.items.querySelectorAll(":scope > li"))
15
15
  : [];
16
16
  },
17
+ navigateToFirstChild() {
18
+ if (this.open()) {
19
+ const child = this.firstVisibleChild();
20
+ if (child) {
21
+ const link = child.querySelector(":scope > a.nav-link");
22
+ if (link) {
23
+ this.navigate(link.getAttribute("href"));
24
+ }
25
+ }
26
+ }
27
+ },
17
28
  filter() {
18
29
  this.hidden = true;
19
30
  this.getChildren().forEach((child) => {
@@ -27,5 +38,12 @@ export default function navNode() {
27
38
  toggle() {
28
39
  this.$store.nav.open[this.id] = !this.$store.nav.open[this.id];
29
40
  },
41
+ firstVisibleChild() {
42
+ return this.getChildren().find((child) => {
43
+ return child._x_dataStack
44
+ ? child._x_dataStack[0].hidden === false
45
+ : false;
46
+ });
47
+ },
30
48
  };
31
49
  }
@@ -50,7 +50,7 @@ module Lookbook
50
50
  def find_preview
51
51
  candidates = []
52
52
  params[:path].to_s.scan(%r{/|$}) { candidates << $` }
53
- match = candidates.detect { |candidate| Lookbook::Preview.exists?(candidate) }
53
+ match = candidates.reverse.detect { |candidate| Lookbook::Preview.exists?(candidate) }
54
54
  @preview = match ? Lookbook::Preview.find(match) : nil
55
55
  end
56
56
 
@@ -95,14 +95,29 @@ module Lookbook
95
95
  html: preview_controller.render_example_to_string(@preview, example.name)
96
96
  }
97
97
  end
98
- joined = render_to_string "lookbook/preview_group", locals: {examples: examples}, layout: nil
99
- preview_controller.render_in_layout_to_string(joined, @preview.lookbook_layout)
98
+ set_params
99
+ layout = @preview.lookbook_layout || Rails.configuration.view_component.default_preview_layout || current_layout
100
+ preview_controller.render_in_layout_to_string("lookbook/preview/group", {examples: examples}, layout)
100
101
  else
101
- preview_controller.request.params[:path] = "#{@preview.preview_name}/#{@example.name}".chomp("/")
102
+ set_params(@example)
103
+ preview_controller.params[:path] = "#{@preview.preview_name}/#{@example.name}".chomp("/")
102
104
  preview_controller.process(:previews)
103
105
  end
104
106
  end
105
107
 
108
+ def set_params(example = nil)
109
+ example_params = example.nil? ? @preview.display_params : example.display_params
110
+ preview_controller.params.merge!({
111
+ lookbook: {
112
+ display: example_params
113
+ }
114
+ })
115
+ end
116
+
117
+ def current_layout
118
+ preview_controller.send :_layout, preview_controller.lookup_context, [:html]
119
+ end
120
+
106
121
  def assign_inspector
107
122
  @inspector = {
108
123
  panes: {
@@ -5,7 +5,7 @@ label ||= leaf.label
5
5
  %>
6
6
  <li x-data="navLeaf" :class="{hidden}" x-init="matchers = <%= leaf.matchers.to_json %>; path = '<%= path %>'; setActive()" @popstate.window="setActive">
7
7
  <a href="<%= path %>"
8
- class="pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
8
+ class="nav-link pr-3 py-[5px] flex items-center w-full group transition hover:bg-gray-200 hover:bg-opacity-50"
9
9
  style="<%= nav_padding_style(depth) %>"
10
10
  :class="{'!bg-indigo-100':active}"
11
11
  @click.stop.prevent="navigate"
@@ -8,7 +8,7 @@
8
8
  <svg class="feather h-3.5 w-3.5 mr-1.5 flex-none text-indigo-500">
9
9
  <use xlink:href="/lookbook-assets/feather-sprite.svg#<%= node.type == :preview ? 'layers' : 'folder' %>" />
10
10
  </svg>
11
- <div class="truncate w-full whitespace-nowrap text-left <%= "font-bold" if node.type == :preview %>" @click.stop="toggle(); if (open()) { <%= "navigate('#{path}')" if defined?(path) %>}">
11
+ <div class="truncate w-full whitespace-nowrap text-left <%= "font-bold" if node.type == :preview %>" @click.stop="toggle(); navigateToFirstChild();">
12
12
  <%= node.label %>
13
13
  </div>
14
14
  </div>
@@ -1,4 +1,4 @@
1
- <% examples = node.get_examples %>
1
+ <% examples = node.get_examples.reject(&:hidden?) %>
2
2
  <% if examples.many? %>
3
3
  <%= render "nav/node", node: node, path: show_path(examples.first.path) do %>
4
4
  <% examples.each do |example| %>
@@ -3,6 +3,7 @@
3
3
  <nav class="-mb-px flex space-x-8 cursor-auto">
4
4
  <% panes.each do |key, props| %>
5
5
  <a
6
+ id="inspector-tab-<%= key %>"
6
7
  href="#<%= key %>"
7
8
  class="whitespace-nowrap py-2 px-1 border-b-2 cursor-pointer <%= "!text-gray-300" if props[:disabled] %>"
8
9
  :class="{
@@ -23,7 +24,7 @@
23
24
  <% if props[:clipboard].present? %>
24
25
  <%= render "shared/clipboard" do %><%= h props[:clipboard].strip %><% end %>
25
26
  <% end %>
26
- <div class="flex flex-col h-full overflow-auto">
27
+ <div id="inspector-content-<%= key %>" class="flex flex-col h-full overflow-auto">
27
28
  <%= render "workbench/inspector/#{props[:template]}", key: key, **props %>
28
29
  </div>
29
30
  </div>
@@ -30,6 +30,7 @@ module Lookbook
30
30
 
31
31
  options.preview_controller = vc_options.preview_controller if options.preview_controller.nil?
32
32
  options.preview_srcdoc = true if options.preview_srcdoc.nil?
33
+ options.preview_display_params ||= {}.with_indifferent_access
33
34
 
34
35
  options.listen_paths = options.listen_paths.map(&:to_s)
35
36
  options.listen_paths += options.preview_paths
@@ -27,6 +27,7 @@ module Lookbook
27
27
  def define_tags
28
28
  YARD::Tags::Library.define_tag("Hidden status", :hidden)
29
29
  YARD::Tags::Library.define_tag("Label", :label)
30
+ YARD::Tags::Library.define_tag("Display", :display)
30
31
  end
31
32
  end
32
33
  end
@@ -23,8 +23,8 @@ module Lookbook
23
23
  return @lookbook_examples if @lookbook_examples.present?
24
24
  public_methods = public_instance_methods(false)
25
25
  public_method_objects = code_object.meths.filter { |m| public_methods.include?(m.name) }
26
- visible = public_method_objects.map { |m| PreviewExample.new(m.name.to_s, self) }.reject(&:hidden?)
27
- sorted = Lookbook.config.sort_examples ? visible.sort_by(&:label) : visible
26
+ examples = public_method_objects.map { |m| PreviewExample.new(m.name.to_s, self) }
27
+ sorted = Lookbook.config.sort_examples ? examples.sort_by(&:label) : examples
28
28
  @lookbook_examples = []
29
29
  if code_object.groups.any?
30
30
  sorted.group_by { |m| m.group }.each do |name, examples|
@@ -78,6 +78,10 @@ module Lookbook
78
78
  defined?(@layout) ? @layout : nil
79
79
  end
80
80
 
81
+ def display_params
82
+ Lookbook.config.preview_display_params.deep_merge(lookbook_display_params)
83
+ end
84
+
81
85
  class << self
82
86
  def all
83
87
  ViewComponent::Preview.all.sort_by(&:label)
@@ -14,9 +14,10 @@ module Lookbook
14
14
  render_to_string template, opts
15
15
  end
16
16
 
17
- def render_in_layout_to_string(content, layout_override)
17
+ def render_in_layout_to_string(template, locals, layout_override = nil)
18
+ append_view_path Lookbook::Engine.root.join("app/views")
18
19
  layout = determine_layout(layout_override, prepend_views: false)[:layout]
19
- render_to_string html: content, layout: layout
20
+ render_to_string template, locals: locals, layout: layout
20
21
  end
21
22
  end
22
23
  end
@@ -21,6 +21,10 @@ module Lookbook
21
21
  lookbook_label.presence || name.titleize
22
22
  end
23
23
 
24
+ def display_params
25
+ @preview.display_params.merge(lookbook_display_params)
26
+ end
27
+
24
28
  def method_source
25
29
  code_object.source.split("\n")[1..-2].join("\n").strip_heredoc
26
30
  end
@@ -26,6 +26,10 @@ module Lookbook
26
26
  :group
27
27
  end
28
28
 
29
+ def hidden?
30
+ false
31
+ end
32
+
29
33
  def matchers
30
34
  [@preview.label, label].map { |m| m.gsub(/\s/, "").downcase }
31
35
  end
@@ -20,6 +20,23 @@ module Lookbook
20
20
  code_object&.group
21
21
  end
22
22
 
23
+ def lookbook_display_params
24
+ display_params = {}.with_indifferent_access
25
+ if code_object&.tags(:display).present?
26
+ code_object.tags(:display).each do |tag|
27
+ parts = tag.text.strip.match(/^([^\s]*)\s?(.*)$/)
28
+ if parts.present?
29
+ begin
30
+ display_params[parts[1]] = JSON.parse parts[2]
31
+ rescue JSON::ParserError => err
32
+ Rails.logger.error("\n👀 [Lookbook] Invalid JSON in @display tag.\n👀 [Lookbook] (#{err})\n")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ display_params
38
+ end
39
+
23
40
  # private
24
41
 
25
42
  def code_object
@@ -1,3 +1,3 @@
1
1
  module Lookbook
2
- VERSION = "0.3.4"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -11,5 +11,10 @@ namespace :lookbook do
11
11
  contents = file.read
12
12
  File.write(filename, contents.gsub(current_version, new_version))
13
13
  end
14
+
15
+ desc "Build Gem and push to RubyGems"
16
+ task :build_and_push do
17
+ sh("rake build && gem push pkg/lookbook-#{Lookbook::VERSION}.gem")
18
+ end
14
19
  end
15
20
  end
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v2.2.7 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v2.2.17 | MIT License | https://tailwindcss.com */
2
2
 
3
3
  /*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
4
4
 
@@ -475,6 +475,18 @@ button,
475
475
  cursor: pointer;
476
476
  }
477
477
 
478
+ /**
479
+ * Override legacy focus reset from Normalize with modern Firefox focus styles.
480
+ *
481
+ * This is actually an improvement over the new defaults in Firefox in our testing,
482
+ * as it triggers the better focus styles even for links, which still use a dotted
483
+ * outline in Firefox by default.
484
+ */
485
+
486
+ :-moz-focusring {
487
+ outline: auto;
488
+ }
489
+
478
490
  table {
479
491
  border-collapse: collapse;
480
492
  }
@@ -627,6 +639,7 @@ video {
627
639
  padding-left: 0.75rem;
628
640
  font-size: 1rem;
629
641
  line-height: 1.5rem;
642
+ --tw-shadow: 0 0 #0000;
630
643
  }
631
644
 
632
645
  [type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
@@ -638,7 +651,7 @@ video {
638
651
  --tw-ring-color: #2563eb;
639
652
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
640
653
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
641
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
654
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
642
655
  border-color: #2563eb;
643
656
  }
644
657
 
@@ -706,6 +719,7 @@ select {
706
719
  background-color: #fff;
707
720
  border-color: #6b7280;
708
721
  border-width: 1px;
722
+ --tw-shadow: 0 0 #0000;
709
723
  }
710
724
 
711
725
  [type='checkbox'] {
@@ -725,7 +739,7 @@ select {
725
739
  --tw-ring-color: #2563eb;
726
740
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
727
741
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
728
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
742
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
729
743
  }
730
744
 
731
745
  [type='checkbox']:checked,[type='radio']:checked {
@@ -2023,6 +2037,7 @@ pre[class*="language-"] {
2023
2037
  border-radius:4px;
2024
2038
  font-size:14px;
2025
2039
  line-height:1.4;
2040
+ white-space:normal;
2026
2041
  outline:0;
2027
2042
  transition-property:transform,visibility,opacity
2028
2043
  }