lookbook 0.4.1 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6556e23e693e27f706b4989f25c13c75a453ce5582e835e6c380a226840feff8
4
- data.tar.gz: c28f4d88136a02522fb23adc826ec1066c0800ef6a223451149b4516448d318c
3
+ metadata.gz: 676b8a78237532971ec7d22d7265b3da7d3b896aa8020d7ab1c514edc3859b45
4
+ data.tar.gz: dfb016bf927d7492afdeacd3b10a2425563e227b5cbd4ebd4d3f7a5bfa97609f
5
5
  SHA512:
6
- metadata.gz: 92076777e5a0e2ca449eec799a4702965bc78c12ae1d48f0a7de9276832f02b320f25ecc5384d56aeb5138a953b26a8251ad44756d6a36a58c7fbe84255078e4
7
- data.tar.gz: e840534cd52b19c9db2b39316739b7ee8df820c96837f59cfd03e96fe99d90778eddc5518ffc5ac0167f61e27f74a2fafaa83642eb7697fd433d34cd09f28550
6
+ metadata.gz: 2e5aeb26c2db3322e7aa127e176b691744d5af3cfbd207159f65d0e9dee1362382f418b07bda2fa7b75d7f85b35f522efe106707e665c6e12f9ab5e4ca1f4cd1
7
+ data.tar.gz: be2908ed45f9645dd9387adf6547c8c45ba262310325438197de22d6db9909705fa3e45de0912914004d516c4546618ccc73fd96fea3fbdc86f2affa5e7ebb3d
data/README.md CHANGED
@@ -28,6 +28,7 @@ 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
 
@@ -76,7 +77,7 @@ Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/do
76
77
 
77
78
  ```ruby
78
79
  # @label Basic Button
79
- # @display bg_color "#fff"
80
+ # @display bg_color #fff
80
81
  class ButtonComponentPreview < ViewComponent::Preview
81
82
 
82
83
  # Primary button
@@ -90,11 +91,24 @@ class ButtonComponentPreview < ViewComponent::Preview
90
91
  end
91
92
  end
92
93
 
94
+ # Button with icon
95
+ # ----------------
96
+ # This example uses dynamic preview parameters
97
+ # which can be edited live in the Lookbook UI
98
+ #
99
+ # @param text
100
+ # @param icon select [heart, cog, alert]
101
+ def icon(text: "Spread the love", icon: "heart")
102
+ render ButtonComponent.new(icon: icon) do
103
+ text
104
+ end
105
+ end
106
+
93
107
  # Inverted button
94
108
  # ---------------
95
109
  # For light-on-dark screens
96
110
  #
97
- # @display bg_color "#000"
111
+ # @display bg_color #000
98
112
  def secondary
99
113
  render ButtonComponent.new(style: :inverted) do
100
114
  "Click me"
@@ -141,54 +155,41 @@ end
141
155
 
142
156
  The following Lookbook-specific tags are available for use:
143
157
 
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)
158
+ * [`@label`](#label-tag)
159
+ * [`@display`](#display-tag)
160
+ * [`@!group ... @!endgroup`](#group-tag)
161
+ * [`@hidden`](#hidden-tag)
162
+ * [`@param`](#param-tag) [⚠️ **experimental!** - requires [feature opt-in](#experimental-features) ⚠️]
148
163
 
149
- ### 🔖 `@label <text>`
164
+ <h3 id="label-tag">🏷 @label</h3>
150
165
 
151
166
  Used to replace the auto-generated navigation label for the item with `<text>`.
152
167
 
153
- > Available for preview classes & example methods.
154
-
155
168
  ```ruby
156
- # @label Preview Label
157
- class FooComponentPreview < ViewComponent::Preview
158
-
159
- # @label Example Label
160
- def default
161
- end
162
- end
169
+ @label <text>
163
170
  ```
164
171
 
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.
172
+ > Available for preview classes & example methods.
172
173
 
173
174
  ```ruby
174
- # @hidden
175
+ # @label Preview Label
175
176
  class FooComponentPreview < ViewComponent::Preview
176
177
 
177
- # @hidden
178
+ # @label Example Label
178
179
  def default
179
180
  end
180
181
  end
181
182
  ```
182
183
 
183
- ### 🔖 `@display <key> <value>`
184
+ <h3 id="display-tag">🏷 @display</h3>
184
185
 
185
186
  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
 
187
188
  ```ruby
188
- # @display bg_color "#eee"
189
+ # @display bg_color #eee
189
190
  class FooComponentPreview < ViewComponent::Preview
190
191
 
191
- # @display max_width "500px"
192
+ # @display max_width 500px
192
193
  # @display wrapper true
193
194
  def default
194
195
  end
@@ -198,13 +199,11 @@ end
198
199
  The `@display` tag can be applied at the preview (class) or at the example (method) level, and takes the following format:
199
200
 
200
201
  ```ruby
201
- # @display <key> <value>
202
+ @display <key> <value>
202
203
  ```
203
204
 
204
205
  - `<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.
206
+ - `<value>` will be parsed using the [Ruby YAML parser](https://yaml.org/YAML_for_ruby.html) to resolve the value
208
207
 
209
208
  These display parameters can then be accessed via the `params` hash in your preview layout using `params[:lookbook][:display][<key>]`:
210
209
 
@@ -244,27 +243,7 @@ config.lookbook.preview_display_params = {
244
243
 
245
244
  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
245
 
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`
246
+ <h3 id="group-tag">🔖 `@!group ... @!endgroup`</h3>
268
247
 
269
248
  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
249
 
@@ -310,6 +289,167 @@ The example above would display the `Sizes` examples grouped together on a singl
310
289
 
311
290
  You can have as many groups as you like within a single preview class, but each example can only belong to one group.
312
291
 
292
+ <h3 id="hidden-tag">🏷 `@hidden`</h3>
293
+
294
+ Used to temporarily exclude an item from the Lookbook navigation. The item will still be accessible via it's URL.
295
+
296
+ 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.
297
+
298
+ > Available for both preview classes & example methods.
299
+
300
+ ```ruby
301
+ # @hidden
302
+ class FooComponentPreview < ViewComponent::Preview
303
+
304
+ # @hidden
305
+ def default
306
+ end
307
+ end
308
+ ```
309
+
310
+ <h3 id="param-tag"> 🚧 @param (experimental)</h3>
311
+
312
+ > ⚠️ 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.
313
+
314
+ 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.
315
+
316
+ 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.
317
+
318
+ The `@param` tag takes the following format:
319
+
320
+ ```ruby
321
+ @param <name> <input_type> <opts?>
322
+ ```
323
+
324
+ - `<name>` - name of the dynamic preview param
325
+ - `<input_type>` - input field type to generate in the UI
326
+ - `<opts?>` - YAML-encoded field options, used for some field types
327
+
328
+ #### Input types
329
+
330
+ The following **input field types** are available for use:
331
+
332
+ 📝 **Text-style inputs** - Single line fields, useful for short strings of text or numbers.
333
+
334
+ ```ruby
335
+ @param <name> text
336
+ @param <name> email
337
+ @param <name> number
338
+ @param <name> url
339
+ @param <name> tel
340
+ ```
341
+
342
+ > The above types only differ in the validation constraints they impose on the input field.
343
+
344
+ 📝 **Textarea** - Multi-line textarea field for longer-form content.
345
+
346
+ ```ruby
347
+ @param <name> textarea
348
+ ```
349
+
350
+ 📝 **Select box** - Dropdown select field for selecting from a list of known options.
351
+
352
+ ```ruby
353
+ @param <name> select <options>
354
+ ```
355
+
356
+ `<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:
357
+
358
+ ```ruby
359
+ # Basic options:
360
+ # @param theme select [primary, secondary, danger]
361
+
362
+ # With custom labels (each item itself an array of [label, value]):
363
+ # @param theme select [[Primary theme, primary], [Secondary theme, secondary], [Danger theme, danger]]
364
+
365
+ # With empty option (`~` in YAML)
366
+ # @param theme select [~, primary, secondary, danger]
367
+ ```
368
+
369
+ > **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.
370
+
371
+ 📝 **Toggle** - On/off switch for toggling boolean values.
372
+
373
+ ```ruby
374
+ @param <name> toggle
375
+ ```
376
+
377
+ #### Default values
378
+
379
+ Default values are specified as part of the preview example method parameters in the usual Ruby way:
380
+
381
+ ```ruby
382
+ def button(content: "Click me", theme: "primary", arrow: false)
383
+ # ...
384
+ end
385
+ ```
386
+
387
+ These will be used as the default values for the param fields.
388
+
389
+ > 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.
390
+
391
+ #### Type casting values
392
+
393
+ Most dynamic param values are passed to the example method as strings, with the following exceptions:
394
+
395
+ - `toggle` input - values are cast to booleans
396
+ - `number` input - values are cast to integers
397
+
398
+ 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.
399
+
400
+ To help with this, a `type` option can be specified in the `@param` definition to automatically cast the dynamic value to a different type:
401
+
402
+ ```ruby
403
+ # @param <name> [<type>] <input_type> <opts?>
404
+ ```
405
+
406
+ 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.
407
+
408
+ ```ruby
409
+ # @param theme [Symbol] select [primary, secondary, danger]
410
+ def default(theme: :primary)
411
+ render Elements::ButtonComponent.new(theme: theme) do
412
+ "Click me"
413
+ end
414
+ end
415
+ ```
416
+
417
+ The supported types to cast to are:
418
+
419
+ - `String` - *default for all except `toggle` inputs*
420
+ - `Boolean` - *default for `toggle` inputs*
421
+ - `Symbol`
422
+ - `Date`
423
+ - `DateTime`
424
+ - `Integer`
425
+ - `Float`
426
+
427
+ The following structured types are also available but should be considered **experimental** - you may run into bugs!
428
+
429
+ - `Hash` - *value string converted to Hash using the Ruby YAML parser*
430
+ - `Array` - *value string converted to Array using the Ruby YAML parser*
431
+
432
+ #### Full example:
433
+
434
+ ```ruby
435
+ class ButtonComponentPreview < ViewComponent::Preview
436
+
437
+ # The params defined below will be editable in the UI:
438
+ #
439
+ # @param content text
440
+ # @param theme select [primary, secondary, danger]
441
+ # @param arrow toggle
442
+ def default(content: "Click me", theme: "primary", arrow: true)
443
+ render Elements::ButtonComponent.new(theme: theme, arrow: arrow) do
444
+ content
445
+ end
446
+ end
447
+
448
+ end
449
+ ```
450
+
451
+ <img src=".github/assets/dynamic_params.png">
452
+
313
453
  ### Adding notes
314
454
 
315
455
  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 +492,33 @@ If you wish to add additional paths to listen for changes in, you can use the `l
352
492
  config.lookbook.listen_paths << Rails.root.join('app/other/directory')
353
493
  ```
354
494
 
495
+ <h3 id="experimental-features">Experimental features opt-in</h3>
496
+
497
+ 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.
498
+
499
+ > ⚠️ **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'. ⚠️
500
+
501
+ #### Opting into specific features (recommended)
502
+
503
+ To opt into individual experimental features, include the name of the feature in the `experimental_features` config option:
504
+
505
+ ```ruby
506
+ config.lookbook.experimental_features = ["feature_name"]
507
+ ```
508
+
509
+ The current experimental features that can be opted into are:
510
+
511
+ - `params`: Live-editable, dynamic preview parameters ([read more](#param-tag)). Include `"params"` in the `experimental_features` config option to opt in.
512
+
513
+ #### Opting into all experimental features (not recommended!)
514
+
515
+ If you want to live life on the bleeding-edge you can opt-in to all current **and future** experimental features (usual caveats apply):
516
+
517
+ ```ruby
518
+ config.lookbook.experimental_features = true
519
+ ```
520
+
521
+
355
522
  ## Keyboard shortcuts
356
523
 
357
524
  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]
@@ -96,8 +96,7 @@ module Lookbook
96
96
  }
97
97
  end
98
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)
99
+ preview_controller.render_in_layout_to_string("lookbook/preview/group", {examples: examples}, @preview.lookbook_layout)
101
100
  else
102
101
  set_params(@example)
103
102
  preview_controller.params[:path] = "#{@preview.preview_name}/#{@example.name}".chomp("/")
@@ -106,6 +105,15 @@ module Lookbook
106
105
  end
107
106
 
108
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
109
117
  example_params = example.nil? ? @preview.display_params : example.display_params
110
118
  preview_controller.params.merge!({
111
119
  lookbook: {
@@ -114,10 +122,6 @@ module Lookbook
114
122
  })
115
123
  end
116
124
 
117
- def current_layout
118
- preview_controller.send :_layout, preview_controller.lookup_context, [:html]
119
- end
120
-
121
125
  def assign_inspector
122
126
  @inspector = {
123
127
  panes: {
@@ -144,6 +148,15 @@ module Lookbook
144
148
  }
145
149
  }
146
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
147
160
  end
148
161
 
149
162
  def assign_nav
@@ -179,5 +192,9 @@ module Lookbook
179
192
  controller.response = response
180
193
  @preview_controller ||= controller
181
194
  end
195
+
196
+ def enabled?(feature)
197
+ Lookbook::Features.enabled?(feature)
198
+ end
182
199
  end
183
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" ? "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>