lookbook 0.4.1 → 0.4.5

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: 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>