lookbook 0.4.2 → 0.4.6

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: ea3c9193679ce30a8af8657a1c02c78f7651a54aeffcb67f1a64585f7ad6b6cc
4
- data.tar.gz: df6c3467fb9d9414efabb6b8939df401629c3540ba77640f23177303f7e2a615
3
+ metadata.gz: 93eda55ac8a014e5027a7a1176653a8ecbf9f1b5a95b26820c68ef93f2402269
4
+ data.tar.gz: c5a4dde94d4ce0c84298dfe5d8592d3ccaf887fe56fa21a9cb0bbc78b797604f
5
5
  SHA512:
6
- metadata.gz: c2df05db3f8a2f88fd3099e49ec6c2b510b16008f6c45f13e82c1bf6f2959e18ab68a0fdde304743ec5da00e6eb5c00de64de78f8c0e36bc410ab5577676076a
7
- data.tar.gz: 4c6814008e6e8360cdb6e6592e099e26fc74df31840bb9fba2569984c0a019284b0c7f4a3f0c27e186697ef0425d94f7d6940dee5a00461a6009dc49c2f43ab6
6
+ metadata.gz: 704265eb0b3c0b15417b47446ac900f14ca3bf746f9bbbabbbeb563eefe672f907d01ebbe5ab70d77633235d228ca509738826d674035528b12a0eb96dfcfa17
7
+ data.tar.gz: 562c86882d1a2da3eb06b660006dc3d37223c80273d9aaaacafb9a85ee8a009081eecd1acfd8d451e850d34b4055400b9d9e8e5d452f7dcc66c2428a3928db7d
data/README.md CHANGED
@@ -28,12 +28,15 @@ Lookbook uses [RDoc/Yard-style comment tags](#annotating-preview-files) to exten
28
28
  - Auto-updating UI when component or preview files are updated _(Rails v6.0+ only)_
29
29
  - Use comment tag annotations for granular customisation of the preview experience
30
30
  - Fully compatible with standard the ViewComponent preview system
31
+ - [**Experimental**] In-browser live editable preview parameters (similar to Storybook Controls/Knobs)
31
32
 
32
33
  ## Lookbook demo
33
34
 
34
35
  If you want to have a quick play with Lookbook, the easiest way is to [give the demo app](https://github.com/allmarkedup/lookbook-demo) a spin. It's a basic Rails/ViewComponent app with a few test components included to tinker with.
35
36
 
36
- The [demo app repo](https://github.com/allmarkedup/lookbook-demo) contains instructions on how to get it up and running.
37
+ **Online demo: https://lookbook-demo-app.herokuapp.com/lookbook**
38
+
39
+ If you'd rather dig in a bit more and run the demo app locally, the [demo repo](https://github.com/allmarkedup/lookbook-demo) contains instructions on how to get it up and running.
37
40
 
38
41
  ## Installing
39
42
 
@@ -76,7 +79,7 @@ Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/do
76
79
 
77
80
  ```ruby
78
81
  # @label Basic Button
79
- # @display bg_color "#fff"
82
+ # @display bg_color #fff
80
83
  class ButtonComponentPreview < ViewComponent::Preview
81
84
 
82
85
  # Primary button
@@ -90,11 +93,24 @@ class ButtonComponentPreview < ViewComponent::Preview
90
93
  end
91
94
  end
92
95
 
96
+ # Button with icon
97
+ # ----------------
98
+ # This example uses dynamic preview parameters
99
+ # which can be edited live in the Lookbook UI
100
+ #
101
+ # @param text
102
+ # @param icon select [heart, cog, alert]
103
+ def icon(text: "Spread the love", icon: "heart")
104
+ render ButtonComponent.new(icon: icon) do
105
+ text
106
+ end
107
+ end
108
+
93
109
  # Inverted button
94
110
  # ---------------
95
111
  # For light-on-dark screens
96
112
  #
97
- # @display bg_color "#000"
113
+ # @display bg_color #000
98
114
  def secondary
99
115
  render ButtonComponent.new(style: :inverted) do
100
116
  "Click me"
@@ -141,54 +157,41 @@ end
141
157
 
142
158
  The following Lookbook-specific tags are available for use:
143
159
 
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)
160
+ * [`@label`](#label-tag)
161
+ * [`@display`](#display-tag)
162
+ * [`@!group ... @!endgroup`](#group-tag)
163
+ * [`@hidden`](#hidden-tag)
164
+ * [`@param`](#param-tag) [⚠️ **experimental!** - requires [feature opt-in](#experimental-features) ⚠️]
148
165
 
149
- ### 🔖 `@label <text>`
166
+ <h3 id="label-tag">🏷 @label</h3>
150
167
 
151
168
  Used to replace the auto-generated navigation label for the item with `<text>`.
152
169
 
153
- > Available for preview classes & example methods.
154
-
155
170
  ```ruby
156
- # @label Preview Label
157
- class FooComponentPreview < ViewComponent::Preview
158
-
159
- # @label Example Label
160
- def default
161
- end
162
- end
171
+ @label <text>
163
172
  ```
164
173
 
165
- ### 🔖 `@hidden`
166
-
167
- Used to temporarily exclude an item from the Lookbook navigation. The item will still be accessible via it's URL.
168
-
169
- Can be useful when a component (or a variant of a component) is still in development and is not ready to be shared with the wider team.
170
-
171
- > Available for both preview classes & example methods.
174
+ > Available for preview classes & example methods.
172
175
 
173
176
  ```ruby
174
- # @hidden
177
+ # @label Preview Label
175
178
  class FooComponentPreview < ViewComponent::Preview
176
179
 
177
- # @hidden
180
+ # @label Example Label
178
181
  def default
179
182
  end
180
183
  end
181
184
  ```
182
185
 
183
- ### 🔖 `@display <key> <value>`
186
+ <h3 id="display-tag">🏷 @display</h3>
184
187
 
185
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.
186
189
 
187
190
  ```ruby
188
- # @display bg_color "#eee"
191
+ # @display bg_color #eee
189
192
  class FooComponentPreview < ViewComponent::Preview
190
193
 
191
- # @display max_width "500px"
194
+ # @display max_width 500px
192
195
  # @display wrapper true
193
196
  def default
194
197
  end
@@ -198,13 +201,11 @@ end
198
201
  The `@display` tag can be applied at the preview (class) or at the example (method) level, and takes the following format:
199
202
 
200
203
  ```ruby
201
- # @display <key> <value>
204
+ @display <key> <value>
202
205
  ```
203
206
 
204
207
  - `<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
+ - `<value>` will be parsed using the [Ruby YAML parser](https://yaml.org/YAML_for_ruby.html) to resolve the value
208
209
 
209
210
  These display parameters can then be accessed via the `params` hash in your preview layout using `params[:lookbook][:display][<key>]`:
210
211
 
@@ -244,27 +245,7 @@ config.lookbook.preview_display_params = {
244
245
 
245
246
  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
 
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`
248
+ <h3 id="group-tag">🔖 `@!group ... @!endgroup`</h3>
268
249
 
269
250
  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.
270
251
 
@@ -310,6 +291,167 @@ The example above would display the `Sizes` examples grouped together on a singl
310
291
 
311
292
  You can have as many groups as you like within a single preview class, but each example can only belong to one group.
312
293
 
294
+ <h3 id="hidden-tag">🏷 `@hidden`</h3>
295
+
296
+ Used to temporarily exclude an item from the Lookbook navigation. The item will still be accessible via it's URL.
297
+
298
+ Can be useful when a component (or a variant of a component) is still in development and is not ready to be shared with the wider team.
299
+
300
+ > Available for both preview classes & example methods.
301
+
302
+ ```ruby
303
+ # @hidden
304
+ class FooComponentPreview < ViewComponent::Preview
305
+
306
+ # @hidden
307
+ def default
308
+ end
309
+ end
310
+ ```
311
+
312
+ <h3 id="param-tag"> 🚧 @param (experimental)</h3>
313
+
314
+ > ⚠️ This feature is currently flagged as an **experimental** feature which requires [feature opt-in](#experimental-features) to use. Its API and implementation may change in the future.
315
+
316
+ The `@param` tag provides the ability to specify **editable preview parameters** which can be changed in the Lookbook UI in order to customise the rendered output on the fly, much like the [Controls (knobs) addon](https://storybook.js.org/addons/@storybook/addon-controls) for Storybook.
317
+
318
+ Each `@param` will have an associated form field generated for it. The values for each field will be handled as [dynamic preview params](https://viewcomponent.org/guide/previews.html#:~:text=It%E2%80%99s%20also%20possible%20to%20set%20dynamic%20values%20from%20the%20params%20by%20setting%20them%20as%20arguments%3A) when rendering the example.
319
+
320
+ The `@param` tag takes the following format:
321
+
322
+ ```ruby
323
+ @param <name> <input_type> <opts?>
324
+ ```
325
+
326
+ - `<name>` - name of the dynamic preview param
327
+ - `<input_type>` - input field type to generate in the UI
328
+ - `<opts?>` - YAML-encoded field options, used for some field types
329
+
330
+ #### Input types
331
+
332
+ The following **input field types** are available for use:
333
+
334
+ 📝 **Text-style inputs** - Single line fields, useful for short strings of text or numbers.
335
+
336
+ ```ruby
337
+ @param <name> text
338
+ @param <name> email
339
+ @param <name> number
340
+ @param <name> url
341
+ @param <name> tel
342
+ ```
343
+
344
+ > The above types only differ in the validation constraints they impose on the input field.
345
+
346
+ 📝 **Textarea** - Multi-line textarea field for longer-form content.
347
+
348
+ ```ruby
349
+ @param <name> textarea
350
+ ```
351
+
352
+ 📝 **Select box** - Dropdown select field for selecting from a list of known options.
353
+
354
+ ```ruby
355
+ @param <name> select <options>
356
+ ```
357
+
358
+ `<options>` should be a [YAML array](https://yaml.org/YAML_for_ruby.html#simple_inline_array) of options which must be formatted in the same style as the input for Rails' [`options_for_select`](https://apidock.com/rails/v6.0.0/ActionView/Helpers/FormOptionsHelper/options_for_select) helper:
359
+
360
+ ```ruby
361
+ # Basic options:
362
+ # @param theme select [primary, secondary, danger]
363
+
364
+ # With custom labels (each item itself an array of [label, value]):
365
+ # @param theme select [[Primary theme, primary], [Secondary theme, secondary], [Danger theme, danger]]
366
+
367
+ # With empty option (`~` in YAML)
368
+ # @param theme select [~, primary, secondary, danger]
369
+ ```
370
+
371
+ > **Note**: In most cases YAML does not require quoting of strings, however if you are running into issues check out the [Ruby YAML docs](https://yaml.org/YAML_for_ruby.html) for a complete syntax reference.
372
+
373
+ 📝 **Toggle** - On/off switch for toggling boolean values.
374
+
375
+ ```ruby
376
+ @param <name> toggle
377
+ ```
378
+
379
+ #### Default values
380
+
381
+ Default values are specified as part of the preview example method parameters in the usual Ruby way:
382
+
383
+ ```ruby
384
+ def button(content: "Click me", theme: "primary", arrow: false)
385
+ # ...
386
+ end
387
+ ```
388
+
389
+ These will be used as the default values for the param fields.
390
+
391
+ > Note that the default values are **not** evaluated at runtime, so you cannot use method calls to generate the defaults. Only static default values are supported.
392
+
393
+ #### Type casting values
394
+
395
+ Most dynamic param values are passed to the example method as strings, with the following exceptions:
396
+
397
+ - `toggle` input - values are cast to booleans
398
+ - `number` input - values are cast to integers
399
+
400
+ In some cases, you may want to type cast the parameter value to something else (for example a `Symbol`) before using it when initializing the component.
401
+
402
+ To help with this, a `type` option can be specified in the `@param` definition to automatically cast the dynamic value to a different type:
403
+
404
+ ```ruby
405
+ # @param <name> [<type>] <input_type> <opts?>
406
+ ```
407
+
408
+ In the example below, the value of the `theme` param (by default a string) will be automatically cast to a Symbol, ready for use in instantiating the component.
409
+
410
+ ```ruby
411
+ # @param theme [Symbol] select [primary, secondary, danger]
412
+ def default(theme: :primary)
413
+ render Elements::ButtonComponent.new(theme: theme) do
414
+ "Click me"
415
+ end
416
+ end
417
+ ```
418
+
419
+ The supported types to cast to are:
420
+
421
+ - `String` - *default for all except `toggle` inputs*
422
+ - `Boolean` - *default for `toggle` inputs*
423
+ - `Symbol`
424
+ - `Date`
425
+ - `DateTime`
426
+ - `Integer`
427
+ - `Float`
428
+
429
+ The following structured types are also available but should be considered **experimental** - you may run into bugs!
430
+
431
+ - `Hash` - *value string converted to Hash using the Ruby YAML parser*
432
+ - `Array` - *value string converted to Array using the Ruby YAML parser*
433
+
434
+ #### Full example:
435
+
436
+ ```ruby
437
+ class ButtonComponentPreview < ViewComponent::Preview
438
+
439
+ # The params defined below will be editable in the UI:
440
+ #
441
+ # @param content text
442
+ # @param theme select [primary, secondary, danger]
443
+ # @param arrow toggle
444
+ def default(content: "Click me", theme: "primary", arrow: true)
445
+ render Elements::ButtonComponent.new(theme: theme, arrow: arrow) do
446
+ content
447
+ end
448
+ end
449
+
450
+ end
451
+ ```
452
+
453
+ <img src=".github/assets/dynamic_params.png">
454
+
313
455
  ### Adding notes
314
456
 
315
457
  All comment text other than tags will be treated as markdown and rendered in the **Notes** panel for that example in the Lookbook UI.
@@ -352,6 +494,33 @@ If you wish to add additional paths to listen for changes in, you can use the `l
352
494
  config.lookbook.listen_paths << Rails.root.join('app/other/directory')
353
495
  ```
354
496
 
497
+ <h3 id="experimental-features">Experimental features opt-in</h3>
498
+
499
+ Some features may occasionally be released behind a 'experimental' feature flag while they are being tested and refined, to allow people to try them out and provide feedback.
500
+
501
+ > ⚠️ **Please note:** Experimental features should be considered to be **subject to extensive change** and breaking changes to them may be made within point releases - these features are **not** considered to be covered by [semver](https://semver.org/) whilst flagged as 'experimental'. ⚠️
502
+
503
+ #### Opting into specific features (recommended)
504
+
505
+ To opt into individual experimental features, include the name of the feature in the `experimental_features` config option:
506
+
507
+ ```ruby
508
+ config.lookbook.experimental_features = ["feature_name"]
509
+ ```
510
+
511
+ The current experimental features that can be opted into are:
512
+
513
+ - `params`: Live-editable, dynamic preview parameters ([read more](#param-tag)). Include `"params"` in the `experimental_features` config option to opt in.
514
+
515
+ #### Opting into all experimental features (not recommended!)
516
+
517
+ If you want to live life on the bleeding-edge you can opt-in to all current **and future** experimental features (usual caveats apply):
518
+
519
+ ```ruby
520
+ config.lookbook.experimental_features = true
521
+ ```
522
+
523
+
355
524
  ## Keyboard shortcuts
356
525
 
357
526
  Lookbook provides a few keyboard shortcuts to help you quickly move around the UI.
@@ -35,14 +35,6 @@
35
35
  fill: none;
36
36
  }
37
37
 
38
- .h-fill {
39
- height: fill-available;
40
- }
41
-
42
- .min-h-fill {
43
- min-height: fill-available;
44
- }
45
-
46
38
  ::-webkit-scrollbar {
47
39
  width: 8px;
48
40
  height: 8px;
@@ -63,3 +55,17 @@
63
55
  @apply bg-gray-400;
64
56
  }
65
57
  }
58
+
59
+ @layer utilities {
60
+ .h-fill {
61
+ height: fill-available;
62
+ }
63
+
64
+ .min-h-fill {
65
+ min-height: fill-available;
66
+ }
67
+
68
+ .form-input {
69
+ @apply border-gray-300 text-gray-700 focus:ring-indigo-300 focus:border-indigo-300 rounded-sm text-sm w-full;
70
+ }
71
+ }
@@ -9,6 +9,7 @@ import page from "./page";
9
9
  import workbench from "./workbench";
10
10
  import preview from "./workbench/preview";
11
11
  import inspector from "./workbench/inspector";
12
+ import param from "./workbench/param";
12
13
  import nav from "./nav";
13
14
  import navNode from "./nav/node";
14
15
  import navLeaf from "./nav/leaf";
@@ -56,6 +57,7 @@ Alpine.data("navLeaf", navLeaf);
56
57
  Alpine.data("workbench", workbench);
57
58
  Alpine.data("preview", preview);
58
59
  Alpine.data("inspector", inspector);
60
+ Alpine.data("param", param);
59
61
  Alpine.data("clipboard", clipboard);
60
62
  Alpine.data("sizeObserver", sizeObserver);
61
63
  Alpine.data("split", split);
@@ -22,13 +22,12 @@ export default function () {
22
22
  });
23
23
  },
24
24
  navigate(path) {
25
- if (path instanceof Event) {
26
- path = path.currentTarget.href;
27
- }
28
- history.pushState({}, null, path);
29
- this.$dispatch("popstate");
25
+ this.navigateTo(path instanceof Event ? path.currentTarget.href : path);
30
26
  },
31
- focusFilter() {
27
+ focusFilter($event) {
28
+ if ($event.target.tagName === "INPUT") {
29
+ return;
30
+ }
32
31
  this.currentFocus = this.$refs.filter;
33
32
  setTimeout(() => this.$refs.filter.focus(), 0);
34
33
  },
@@ -27,7 +27,12 @@ export default function page() {
27
27
  render() {
28
28
  if (this.ready) {
29
29
  morph(this.$el, store.doc.getElementById(this.$el.id));
30
+ this.$dispatch("document:patched");
30
31
  }
31
32
  },
33
+ navigateTo(path) {
34
+ history.pushState({}, null, path);
35
+ this.$dispatch("popstate");
36
+ },
32
37
  };
33
38
  }
@@ -9,6 +9,9 @@ export default function (from, to, opts = {}) {
9
9
  if (fromEl.isEqualNode(toEl)) {
10
10
  return false;
11
11
  }
12
+ if (fromEl.hasAttribute("skip-morph")) {
13
+ return false;
14
+ }
12
15
  return true;
13
16
  },
14
17
  ...opts,
@@ -0,0 +1,19 @@
1
+ export default function param() {
2
+ return {
3
+ focused: false,
4
+ setFocus() {
5
+ if (this.focused && this.$el.focus) {
6
+ this.$el.focus();
7
+ }
8
+ },
9
+ update(name, value) {
10
+ const searchParams = new URLSearchParams(window.location.search);
11
+ searchParams.set(name, value);
12
+ const path = location.href.replace(location.search, "");
13
+ this.navigateTo(`${path}?${searchParams.toString()}`);
14
+ },
15
+ validate() {
16
+ return this.$el.reportValidity ? this.$el.reportValidity() : true;
17
+ },
18
+ };
19
+ }
@@ -8,7 +8,7 @@ module Lookbook
8
8
  prepend_view_path File.expand_path("../../views/lookbook", __dir__)
9
9
 
10
10
  layout "layouts/app"
11
- helper Lookbook::Engine.helpers
11
+ helper Lookbook::ApplicationHelper
12
12
 
13
13
  before_action :find_preview, only: [:preview, :show]
14
14
  before_action :find_example, only: [:preview, :show]
@@ -105,6 +105,15 @@ module Lookbook
105
105
  end
106
106
 
107
107
  def set_params(example = nil)
108
+ if example.present? && enabled?(:params)
109
+ # cast known params to type
110
+ example.params.each do |param|
111
+ if preview_controller.params.key?(param[:name])
112
+ preview_controller.params[param[:name]] = Lookbook::Params.cast(preview_controller.params[param[:name]], param[:type])
113
+ end
114
+ end
115
+ end
116
+ # set display params
108
117
  example_params = example.nil? ? @preview.display_params : example.display_params
109
118
  preview_controller.params.merge!({
110
119
  lookbook: {
@@ -139,6 +148,15 @@ module Lookbook
139
148
  }
140
149
  }
141
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
142
160
  end
143
161
 
144
162
  def assign_nav
@@ -174,5 +192,9 @@ module Lookbook
174
192
  controller.response = response
175
193
  @preview_controller ||= controller
176
194
  end
195
+
196
+ def enabled?(feature)
197
+ Lookbook::Features.enabled?(feature)
198
+ end
177
199
  end
178
200
  end
@@ -8,7 +8,12 @@ module Lookbook
8
8
  end
9
9
 
10
10
  def markdown(text)
11
- Markdown.new(text).to_html.html_safe
11
+ markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, {
12
+ tables: true,
13
+ fenced_code_blocks: true,
14
+ disable_indented_code_blocks: true
15
+ })
16
+ markdown.render(text).html_safe
12
17
  end
13
18
 
14
19
  def highlight(source, language)
@@ -0,0 +1,7 @@
1
+ module Lookbook
2
+ module PreviewHelper
3
+ def lookbook_display(key, fallback = nil)
4
+ params[:lookbook][:display][key.to_sym] || fallback
5
+ end
6
+ end
7
+ end
@@ -20,7 +20,12 @@
20
20
  </div>
21
21
  <div class="flex-auto overflow-auto bg-gray-50">
22
22
  <% panes.each do |key, props| %>
23
- <div class="flex flex-col h-full relative" x-show="active('<%= key %>')" x-cloak>
23
+ <div class="flex flex-col h-full relative" x-show="active('<%= key %>')" x-effect="
24
+ if ($store.inspector.active === '<%= key %>') {
25
+ const input = $el.querySelector('[data-param-input]');
26
+ if (input) setTimeout(() => input.focus(), 0)
27
+ }
28
+ " x-cloak>
24
29
  <% if props[:clipboard].present? %>
25
30
  <%= render "shared/clipboard" do %><%= h props[:clipboard].strip %><% end %>
26
31
  <% end %>
@@ -0,0 +1,28 @@
1
+ <% if @example.type == :group %>
2
+ <div class="p-4 prose prose-sm">
3
+ <em class='opacity-50'>Params are not supported for grouped previews.</em>
4
+ </div>
5
+ <% elsif items.none? %>
6
+ <div class="p-4 prose prose-sm">
7
+ <em class='opacity-50'>No params configured.</em>
8
+ </div>
9
+ <% else %>
10
+ <div class="py-3">
11
+ <% items.each do |param| %>
12
+ <div class="px-4 py-3" x-data="param" @document:patched="setFocus">
13
+ <div class="flex items-start max-w-[800px]">
14
+ <div class="w-[200px] flex-none py-2">
15
+ <label for="param-<%= param[:name] %>" class="font-bold"><%= param[:name].titleize %></label>
16
+ </div>
17
+ <div class="flex-grow" @focus="focussed = true" @blur="focussed = false">
18
+ <%= render "workbench/inspector/params/#{param[:input]}",
19
+ **param,
20
+ value: params.key?(param[:name]) ? params[param[:name]] : param[:default],
21
+ id: "#{@example.id}-param-#{param[:name]}"
22
+ %>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ <% end %>
27
+ </div>
28
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <select
2
+ name="<%= name %>"
3
+ id="<%= id %>"
4
+ class="form-input"
5
+ @change.stop="update($el.name, $el.value)"
6
+ data-param-input>
7
+ <%= options_for_select(options, value) %>
8
+ </select>
@@ -0,0 +1,8 @@
1
+ <input
2
+ id="<%= id %>"
3
+ class="form-input"
4
+ type="<%= input_type %>"
5
+ name="<%= name %>"
6
+ value="<%= value %>"
7
+ @keyup.stop.debounce.400="if (validate()) update($el.name, $el.value)"
8
+ data-param-input>
@@ -0,0 +1,8 @@
1
+ <textarea
2
+ id="<%= id %>"
3
+ class="form-input"
4
+ name="<%= name %>"
5
+ rows="4"
6
+ @keyup.stop.debounce.300="update($el.name, $el.value)"
7
+ data-param-input
8
+ ><%= value %></textarea>
@@ -0,0 +1,13 @@
1
+ <div id="<%= id %>" x-init="checked = <%= value == true || value == "true" ? "true" : "false" %>" data-param-input>
2
+ <button type="button"
3
+ class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400"
4
+ :class="{'bg-indigo-500': checked, 'bg-gray-300': !checked}"
5
+ role="switch"
6
+ @click.stop="checked = !checked; update('<%= name %>', checked)">
7
+ <span
8
+ aria-hidden="true"
9
+ class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
10
+ :class="{'translate-x-5': checked, 'translate-x-0': !checked}"
11
+ ></span>
12
+ </button>
13
+ </div>
@@ -8,6 +8,10 @@ module Lookbook
8
8
  def config
9
9
  @config ||= Engine.config.lookbook
10
10
  end
11
+
12
+ def logger
13
+ @logger ||= config.debug == true ? Rails.logger : Lookbook::NullLogger.new
14
+ end
11
15
  end
12
16
 
13
17
  class Engine < Rails::Engine
@@ -35,6 +39,8 @@ module Lookbook
35
39
  options.listen_paths = options.listen_paths.map(&:to_s)
36
40
  options.listen_paths += options.preview_paths
37
41
  options.listen_paths << (vc_options.view_component_path || Rails.root.join("app/components"))
42
+
43
+ options.experimental_features = false unless options.experimental_features.present?
38
44
  end
39
45
 
40
46
  initializer "lookbook.cable.config" do |app|
@@ -0,0 +1,24 @@
1
+ module Lookbook
2
+ module Features
3
+ EXPERIMENTAL_FEATURES = [:params]
4
+
5
+ def self.experimental_feature?(name)
6
+ EXPERIMENTAL_FEATURES.include?(name.to_sym)
7
+ end
8
+
9
+ def self.enabled?(name)
10
+ return true unless experimental_feature?(name)
11
+ enabled.include?(name.to_sym)
12
+ end
13
+
14
+ def self.enabled
15
+ if Lookbook.config.experimental_features == true
16
+ EXPERIMENTAL_FEATURES
17
+ elsif Lookbook.config.experimental_features.blank?
18
+ []
19
+ else
20
+ Lookbook.config.experimental_features.map(&:to_sym)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,110 @@
1
+ module Lookbook
2
+ module Params
3
+ class << self
4
+ def build_param(param, default)
5
+ input, options_str = param.text.present? ? param.text.split(" ", 2) : [nil, ""]
6
+ type = param.types&.first
7
+ options = YAML.safe_load(options_str || "~")
8
+ input ||= guess_input(type, default)
9
+ type ||= guess_type(input, default)
10
+ {
11
+ name: param.name,
12
+ input: input_text?(input) ? "text" : input,
13
+ input_type: (input if input_text?(input)),
14
+ options: options,
15
+ type: type,
16
+ default: default
17
+ }
18
+ end
19
+
20
+ def parse_method_param_str(param_str)
21
+ return nil if param_str[0].nil? || param_str[1].nil?
22
+ name = param_str[0].chomp(":")
23
+ value = param_str[1]&.strip
24
+ value = case value
25
+ when "nil"
26
+ nil
27
+ else
28
+ if value&.first == ":"
29
+ value.delete_prefix(":").to_sym
30
+ else
31
+ YAML.safe_load(value)
32
+ end
33
+ end
34
+ [name, value]
35
+ end
36
+
37
+ def cast(value, type = "String")
38
+ case type.downcase
39
+ when "symbol"
40
+ value.delete_prefix(":").to_sym
41
+ when "hash"
42
+ result = safe_parse_yaml(value, {})
43
+ unless result.is_a? Hash
44
+ Lookbook.logger.debug "Failed to parse '#{value}' into a Hash"
45
+ result = {}
46
+ end
47
+ result
48
+ when "array"
49
+ result = safe_parse_yaml(value, [])
50
+ unless result.is_a? Array
51
+ Lookbook.logger.debug "Failed to parse '#{value}' into an Array"
52
+ result = []
53
+ end
54
+ result
55
+ else
56
+ begin
57
+ type_class = "ActiveModel::Type::#{type}".constantize
58
+ type_class.new.cast(value)
59
+ rescue NameError
60
+ raise ArgumentError, "'#{type}' is not a valid param type to cast to."
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def guess_input(type, default)
68
+ if type&.downcase == "boolean" || (type.blank? && boolean?(default))
69
+ "toggle"
70
+ else
71
+ "text"
72
+ end
73
+ end
74
+
75
+ def guess_type(input, default)
76
+ if input&.downcase == "toggle"
77
+ "Boolean"
78
+ elsif input&.downcase == "number"
79
+ "Integer"
80
+ elsif boolean?(default)
81
+ "Boolean"
82
+ elsif default.is_a? Symbol
83
+ "Symbol"
84
+ else
85
+ "String"
86
+ end
87
+ end
88
+
89
+ def input_text?(input)
90
+ [
91
+ "email",
92
+ "number",
93
+ "tel",
94
+ "text",
95
+ "url"
96
+ ].include? input
97
+ end
98
+
99
+ def safe_parse_yaml(value, fallback)
100
+ value.present? ? YAML.safe_load(value) : fallback
101
+ rescue Psych::SyntaxError
102
+ fallback
103
+ end
104
+
105
+ def boolean?(value)
106
+ value == true || value == false
107
+ end
108
+ end
109
+ end
110
+ end
@@ -5,7 +5,7 @@ module Lookbook
5
5
  YARDOC_FILE_PATH = Rails.root.join("tmp/storage/.yardoc").to_s
6
6
 
7
7
  def initialize(paths)
8
- @paths = paths.map { |p| "#{p}/**/*_preview.rb" }
8
+ @paths = paths.map { |p| "#{p}/**/*preview.rb" }
9
9
  YARD::Registry.yardoc_file = YARDOC_FILE_PATH
10
10
  end
11
11
 
@@ -71,7 +71,7 @@ module Lookbook
71
71
  end
72
72
 
73
73
  def lookbook_id
74
- lookbook_path.tr("_", "-")
74
+ lookbook_path.tr("/", "-").tr("_", "-")
75
75
  end
76
76
 
77
77
  def lookbook_layout
@@ -10,7 +10,7 @@ module Lookbook
10
10
  end
11
11
 
12
12
  def id
13
- path.underscore.tr("_", "-")
13
+ path.underscore.tr("/", "-").tr("_", "-")
14
14
  end
15
15
 
16
16
  def path
@@ -25,6 +25,12 @@ module Lookbook
25
25
  @preview.display_params.merge(lookbook_display_params)
26
26
  end
27
27
 
28
+ def params
29
+ @params || code_object&.tags("param")&.map do |param|
30
+ Lookbook::Params.build_param(param, parameter_defaults[param.name])
31
+ end
32
+ end
33
+
28
34
  def method_source
29
35
  code_object.source.split("\n")[1..-2].join("\n").strip_heredoc
30
36
  end
@@ -55,6 +61,12 @@ module Lookbook
55
61
 
56
62
  private
57
63
 
64
+ def parameter_defaults
65
+ @parameter_defaults || code_object&.parameters&.map do |param_str|
66
+ Lookbook::Params.parse_method_param_str(param_str)
67
+ end&.compact&.to_h
68
+ end
69
+
58
70
  def taggable_object_path
59
71
  "#{@preview.name}##{name}"
60
72
  end
@@ -11,7 +11,7 @@ module Lookbook
11
11
  end
12
12
 
13
13
  def id
14
- path.underscore.tr("_", "-")
14
+ path.underscore.tr("/", "-").tr("_", "-")
15
15
  end
16
16
 
17
17
  def path
@@ -26,6 +26,10 @@ module Lookbook
26
26
  :group
27
27
  end
28
28
 
29
+ def params
30
+ []
31
+ end
32
+
29
33
  def hidden?
30
34
  false
31
35
  end
@@ -27,8 +27,8 @@ module Lookbook
27
27
  parts = tag.text.strip.match(/^([^\s]*)\s?(.*)$/)
28
28
  if parts.present?
29
29
  begin
30
- display_params[parts[1]] = JSON.parse parts[2]
31
- rescue JSON::ParserError => err
30
+ display_params[parts[1]] = YAML.safe_load(parts[2] || "~")
31
+ rescue SyntaxError => err
32
32
  Rails.logger.error("\n👀 [Lookbook] Invalid JSON in @display tag.\n👀 [Lookbook] (#{err})\n")
33
33
  end
34
34
  end
@@ -1,3 +1,3 @@
1
1
  module Lookbook
2
- VERSION = "0.4.2"
2
+ VERSION = "0.4.6"
3
3
  end
data/lib/lookbook.rb CHANGED
@@ -5,6 +5,8 @@ module Lookbook
5
5
  extend ActiveSupport::Autoload
6
6
 
7
7
  autoload :Lang, "lookbook/lang"
8
+ autoload :Params, "lookbook/params"
9
+ autoload :Features, "lookbook/features"
8
10
  autoload :Collection, "lookbook/collection"
9
11
  autoload :Parser, "lookbook/parser"
10
12
  autoload :Preview, "lookbook/preview"
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v2.2.17 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com */
2
2
 
3
3
  /*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
4
4
 
@@ -606,6 +606,9 @@ video {
606
606
  --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
607
607
  --tw-border-opacity: 1;
608
608
  border-color: rgba(229, 231, 235, var(--tw-border-opacity));
609
+ --tw-ring-offset-shadow: 0 0 #0000;
610
+ --tw-ring-shadow: 0 0 #0000;
611
+ --tw-shadow: 0 0 #0000;
609
612
  --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
610
613
  --tw-ring-offset-width: 0px;
611
614
  --tw-ring-offset-color: #fff;
@@ -820,12 +823,6 @@ pre[class*="language-"] {
820
823
  fill: none;
821
824
  }
822
825
 
823
- .min-h-fill {
824
- min-height: -webkit-fill-available;
825
- min-height: -moz-available;
826
- min-height: fill-available;
827
- }
828
-
829
826
  ::-webkit-scrollbar {
830
827
  width: 8px;
831
828
  height: 8px;
@@ -1563,6 +1560,10 @@ pre[class*="language-"] {
1563
1560
  display: flex;
1564
1561
  }
1565
1562
 
1563
+ .inline-flex {
1564
+ display: inline-flex;
1565
+ }
1566
+
1566
1567
  .hidden {
1567
1568
  display: none;
1568
1569
  }
@@ -1599,6 +1600,10 @@ pre[class*="language-"] {
1599
1600
  height: 11px;
1600
1601
  }
1601
1602
 
1603
+ .h-6 {
1604
+ height: 1.5rem;
1605
+ }
1606
+
1602
1607
  .w-full {
1603
1608
  width: 100%;
1604
1609
  }
@@ -1631,10 +1636,22 @@ pre[class*="language-"] {
1631
1636
  width: 1.25rem;
1632
1637
  }
1633
1638
 
1639
+ .w-\[200px\] {
1640
+ width: 200px;
1641
+ }
1642
+
1643
+ .w-11 {
1644
+ width: 2.75rem;
1645
+ }
1646
+
1634
1647
  .max-w-xs {
1635
1648
  max-width: 20rem;
1636
1649
  }
1637
1650
 
1651
+ .max-w-\[800px\] {
1652
+ max-width: 800px;
1653
+ }
1654
+
1638
1655
  .flex-none {
1639
1656
  flex: none;
1640
1657
  }
@@ -1643,6 +1660,10 @@ pre[class*="language-"] {
1643
1660
  flex: 1 1 auto;
1644
1661
  }
1645
1662
 
1663
+ .flex-shrink-0 {
1664
+ flex-shrink: 0;
1665
+ }
1666
+
1646
1667
  .flex-grow {
1647
1668
  flex-grow: 1;
1648
1669
  }
@@ -1657,6 +1678,16 @@ pre[class*="language-"] {
1657
1678
  transform: var(--tw-transform);
1658
1679
  }
1659
1680
 
1681
+ .translate-x-5 {
1682
+ --tw-translate-x: 1.25rem;
1683
+ transform: var(--tw-transform);
1684
+ }
1685
+
1686
+ .translate-x-0 {
1687
+ --tw-translate-x: 0px;
1688
+ transform: var(--tw-transform);
1689
+ }
1690
+
1660
1691
  .transform {
1661
1692
  transform: var(--tw-transform);
1662
1693
  }
@@ -1692,6 +1723,10 @@ pre[class*="language-"] {
1692
1723
  flex-direction: column;
1693
1724
  }
1694
1725
 
1726
+ .items-start {
1727
+ align-items: flex-start;
1728
+ }
1729
+
1695
1730
  .items-center {
1696
1731
  align-items: center;
1697
1732
  }
@@ -1751,6 +1786,10 @@ pre[class*="language-"] {
1751
1786
  white-space: nowrap;
1752
1787
  }
1753
1788
 
1789
+ .rounded-full {
1790
+ border-radius: 9999px;
1791
+ }
1792
+
1754
1793
  .rounded-b {
1755
1794
  border-bottom-right-radius: 0.25rem;
1756
1795
  border-bottom-left-radius: 0.25rem;
@@ -1768,6 +1807,10 @@ pre[class*="language-"] {
1768
1807
  border-width: 1px;
1769
1808
  }
1770
1809
 
1810
+ .border-2 {
1811
+ border-width: 2px;
1812
+ }
1813
+
1771
1814
  .border-r {
1772
1815
  border-right-width: 1px;
1773
1816
  }
@@ -1835,6 +1878,16 @@ pre[class*="language-"] {
1835
1878
  background-color: rgba(249, 250, 251, var(--tw-bg-opacity));
1836
1879
  }
1837
1880
 
1881
+ .bg-indigo-500 {
1882
+ --tw-bg-opacity: 1;
1883
+ background-color: rgba(99, 102, 241, var(--tw-bg-opacity));
1884
+ }
1885
+
1886
+ .bg-gray-300 {
1887
+ --tw-bg-opacity: 1;
1888
+ background-color: rgba(209, 213, 219, var(--tw-bg-opacity));
1889
+ }
1890
+
1838
1891
  .p-4 {
1839
1892
  padding: 1rem;
1840
1893
  }
@@ -1882,6 +1935,11 @@ pre[class*="language-"] {
1882
1935
  padding-bottom: 0px;
1883
1936
  }
1884
1937
 
1938
+ .py-3 {
1939
+ padding-top: 0.75rem;
1940
+ padding-bottom: 0.75rem;
1941
+ }
1942
+
1885
1943
  .pr-3 {
1886
1944
  padding-right: 0.75rem;
1887
1945
  }
@@ -2000,11 +2058,22 @@ pre[class*="language-"] {
2000
2058
  opacity: 0.5;
2001
2059
  }
2002
2060
 
2061
+ .shadow {
2062
+ --tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
2063
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2064
+ }
2065
+
2003
2066
  .outline-none {
2004
2067
  outline: 2px solid transparent;
2005
2068
  outline-offset: 2px;
2006
2069
  }
2007
2070
 
2071
+ .ring-0 {
2072
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2073
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2074
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2075
+ }
2076
+
2008
2077
  .blur {
2009
2078
  --tw-blur: blur(8px);
2010
2079
  filter: var(--tw-filter);
@@ -2022,6 +2091,44 @@ pre[class*="language-"] {
2022
2091
  transition-duration: 150ms;
2023
2092
  }
2024
2093
 
2094
+ .transition-colors {
2095
+ transition-property: background-color, border-color, color, fill, stroke;
2096
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2097
+ transition-duration: 150ms;
2098
+ }
2099
+
2100
+ .duration-200 {
2101
+ transition-duration: 200ms;
2102
+ }
2103
+
2104
+ .ease-in-out {
2105
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2106
+ }
2107
+
2108
+ .min-h-fill {
2109
+ min-height: -webkit-fill-available;
2110
+ min-height: -moz-available;
2111
+ min-height: fill-available;
2112
+ }
2113
+
2114
+ .form-input {
2115
+ width: 100%;
2116
+ border-radius: 0.125rem;
2117
+ --tw-border-opacity: 1;
2118
+ border-color: rgba(209, 213, 219, var(--tw-border-opacity));
2119
+ font-size: 0.875rem;
2120
+ line-height: 1.25rem;
2121
+ --tw-text-opacity: 1;
2122
+ color: rgba(55, 65, 81, var(--tw-text-opacity));
2123
+ }
2124
+
2125
+ .form-input:focus {
2126
+ --tw-border-opacity: 1;
2127
+ border-color: rgba(165, 180, 252, var(--tw-border-opacity));
2128
+ --tw-ring-opacity: 1;
2129
+ --tw-ring-color: rgba(165, 180, 252, var(--tw-ring-opacity));
2130
+ }
2131
+
2025
2132
  .tippy-box[data-animation=fade][data-state=hidden]{
2026
2133
  opacity:0
2027
2134
  }
@@ -2463,6 +2570,21 @@ pre[class*="language-"] {
2463
2570
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2464
2571
  }
2465
2572
 
2573
+ .focus\:ring-2:focus {
2574
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2575
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2576
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2577
+ }
2578
+
2579
+ .focus\:ring-indigo-400:focus {
2580
+ --tw-ring-opacity: 1;
2581
+ --tw-ring-color: rgba(129, 140, 248, var(--tw-ring-opacity));
2582
+ }
2583
+
2584
+ .focus\:ring-offset-2:focus {
2585
+ --tw-ring-offset-width: 2px;
2586
+ }
2587
+
2466
2588
  .group:hover .group-hover\:text-indigo-800 {
2467
2589
  --tw-text-opacity: 1;
2468
2590
  color: rgba(55, 48, 163, var(--tw-text-opacity));
@@ -7333,7 +7333,9 @@ Expression: "${expression}"
7333
7333
  var module_default3 = src_default3;
7334
7334
 
7335
7335
  // node_modules/@ryangjchandler/alpine-clipboard/src/index.js
7336
- function src_default4(Alpine3) {
7336
+ var onCopy = () => {
7337
+ };
7338
+ function Clipboard(Alpine3) {
7337
7339
  Alpine3.magic("clipboard", () => {
7338
7340
  return function(target) {
7339
7341
  if (typeof target === "function") {
@@ -7342,10 +7344,17 @@ Expression: "${expression}"
7342
7344
  if (typeof target === "object") {
7343
7345
  target = JSON.stringify(target);
7344
7346
  }
7345
- return window.navigator.clipboard.writeText(target);
7347
+ return window.navigator.clipboard.writeText(target).then(onCopy);
7346
7348
  };
7347
7349
  });
7348
7350
  }
7351
+ Clipboard.configure = (config) => {
7352
+ if (config.hasOwnProperty("onCopy") && typeof config.onCopy === "function") {
7353
+ onCopy = config.onCopy;
7354
+ }
7355
+ return Clipboard;
7356
+ };
7357
+ var src_default4 = Clipboard;
7349
7358
 
7350
7359
  // app/assets/lookbook/js/utils/screen.js
7351
7360
  function screen_default(Alpine3) {
@@ -8359,6 +8368,9 @@ Expression: "${expression}"
8359
8368
  if (fromEl.isEqualNode(toEl)) {
8360
8369
  return false;
8361
8370
  }
8371
+ if (fromEl.hasAttribute("skip-morph")) {
8372
+ return false;
8373
+ }
8362
8374
  return true;
8363
8375
  }
8364
8376
  }, opts));
@@ -8395,7 +8407,12 @@ Expression: "${expression}"
8395
8407
  render() {
8396
8408
  if (this.ready) {
8397
8409
  morph_default(this.$el, store2.doc.getElementById(this.$el.id));
8410
+ this.$dispatch("document:patched");
8398
8411
  }
8412
+ },
8413
+ navigateTo(path2) {
8414
+ history.pushState({}, null, path2);
8415
+ this.$dispatch("popstate");
8399
8416
  }
8400
8417
  };
8401
8418
  }
@@ -8469,6 +8486,27 @@ Expression: "${expression}"
8469
8486
  };
8470
8487
  }
8471
8488
 
8489
+ // app/assets/lookbook/js/workbench/param.js
8490
+ function param() {
8491
+ return {
8492
+ focused: false,
8493
+ setFocus() {
8494
+ if (this.focused && this.$el.focus) {
8495
+ this.$el.focus();
8496
+ }
8497
+ },
8498
+ update(name, value) {
8499
+ const searchParams = new URLSearchParams(window.location.search);
8500
+ searchParams.set(name, value);
8501
+ const path2 = location.href.replace(location.search, "");
8502
+ this.navigateTo(`${path2}?${searchParams.toString()}`);
8503
+ },
8504
+ validate() {
8505
+ return this.$el.reportValidity ? this.$el.reportValidity() : true;
8506
+ }
8507
+ };
8508
+ }
8509
+
8472
8510
  // app/assets/lookbook/js/nav.js
8473
8511
  function nav_default() {
8474
8512
  return {
@@ -8492,13 +8530,12 @@ Expression: "${expression}"
8492
8530
  });
8493
8531
  },
8494
8532
  navigate(path2) {
8495
- if (path2 instanceof Event) {
8496
- path2 = path2.currentTarget.href;
8497
- }
8498
- history.pushState({}, null, path2);
8499
- this.$dispatch("popstate");
8533
+ this.navigateTo(path2 instanceof Event ? path2.currentTarget.href : path2);
8500
8534
  },
8501
- focusFilter() {
8535
+ focusFilter($event) {
8536
+ if ($event.target.tagName === "INPUT") {
8537
+ return;
8538
+ }
8502
8539
  this.currentFocus = this.$refs.filter;
8503
8540
  setTimeout(() => this.$refs.filter.focus(), 0);
8504
8541
  },
@@ -8666,6 +8703,7 @@ Expression: "${expression}"
8666
8703
  module_default.data("workbench", workbench);
8667
8704
  module_default.data("preview", preview);
8668
8705
  module_default.data("inspector", inspector);
8706
+ module_default.data("param", param);
8669
8707
  module_default.data("clipboard", clipboard);
8670
8708
  module_default.data("sizeObserver", sizeObserver);
8671
8709
  module_default.data("split", split_default);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lookbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Perkins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-22 00:00:00.000000000 Z
11
+ date: 2021-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -202,11 +202,13 @@ files:
202
202
  - app/assets/lookbook/js/utils/split.js
203
203
  - app/assets/lookbook/js/workbench.js
204
204
  - app/assets/lookbook/js/workbench/inspector.js
205
+ - app/assets/lookbook/js/workbench/param.js
205
206
  - app/assets/lookbook/js/workbench/preview.js
206
207
  - app/channels/lookbook/connection.rb
207
208
  - app/channels/lookbook/reload_channel.rb
208
209
  - app/controllers/lookbook/app_controller.rb
209
210
  - app/helpers/lookbook/application_helper.rb
211
+ - app/helpers/lookbook/preview_helper.rb
210
212
  - app/views/lookbook/app/error.html.erb
211
213
  - app/views/lookbook/app/index.html.erb
212
214
  - app/views/lookbook/app/not_found.html.erb
@@ -226,13 +228,20 @@ files:
226
228
  - app/views/lookbook/workbench/_preview.html.erb
227
229
  - app/views/lookbook/workbench/inspector/_code.html.erb
228
230
  - app/views/lookbook/workbench/inspector/_notes.html.erb
231
+ - app/views/lookbook/workbench/inspector/_params.html.erb
229
232
  - app/views/lookbook/workbench/inspector/_plain.html.erb
233
+ - app/views/lookbook/workbench/inspector/params/_select.html.erb
234
+ - app/views/lookbook/workbench/inspector/params/_text.html.erb
235
+ - app/views/lookbook/workbench/inspector/params/_textarea.html.erb
236
+ - app/views/lookbook/workbench/inspector/params/_toggle.html.erb
230
237
  - config/routes.rb
231
238
  - lib/lookbook.rb
232
239
  - lib/lookbook/collection.rb
233
240
  - lib/lookbook/engine.rb
241
+ - lib/lookbook/features.rb
234
242
  - lib/lookbook/lang.rb
235
243
  - lib/lookbook/null_logger.rb
244
+ - lib/lookbook/params.rb
236
245
  - lib/lookbook/parser.rb
237
246
  - lib/lookbook/preview.rb
238
247
  - lib/lookbook/preview_controller.rb
@@ -263,7 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
272
  - !ruby/object:Gem::Version
264
273
  version: '0'
265
274
  requirements: []
266
- rubygems_version: 3.1.2
275
+ rubygems_version: 3.2.22
267
276
  signing_key:
268
277
  specification_version: 4
269
278
  summary: A native development UI for ViewComponent