lookbook 0.5.0.beta.2 → 0.6.0

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: 7838a877b435b1952e1eed9163cad4f21fa44b2578d9114430bfc1d262b15c23
4
- data.tar.gz: 4f8512d21d07f2b93164706569f0a28483f6b43e60446f19857411b4e66ac6ee
3
+ metadata.gz: 77ec54d7371b46e26f850afe422f94a008bd40a11dd861db81763bf16b4a3776
4
+ data.tar.gz: 0755cfd84d1be9b1979a528619a69a127c376268b8015d7f6ee2e55c8d6b4dac
5
5
  SHA512:
6
- metadata.gz: bf6f6045ce9ea8d5bca9932bfb3538e0e5c307b856cdbc0a209520be5112bed281f623cc879af59e51216f1d284aa2de08ed6ef1b483f794ea74c434a150bb13
7
- data.tar.gz: 4b2d545082ea784bc1122a51c947b0d5391cc829e895adfe224107591acdb68fabb23e8d21fe8be1f1d6acf405e774130dad1e1ba9676470a5bea9240f82cd34
6
+ metadata.gz: ee0dabd03d84efd6598bab86a4e2a7298126f4ade488aaf535dc12de7b2fd7334224f40b45c5482afba4a3e4196713d165efc56ea3f859e085b2b216ccb1e3f7
7
+ data.tar.gz: 97d9d33c6cad98c8c9c4224cb5ecfbf4676902a2b7980ef7122f1547ac626c6b4727204772090d9d0702fbb368032b1b5f496d3e6f4f5e9ba22d93be28675f01
data/README.md CHANGED
@@ -28,7 +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
+ - In-browser live-editable preview parameters (similar to basic Storybook Controls/Knobs)
32
32
 
33
33
  ## Lookbook demo
34
34
 
@@ -63,10 +63,16 @@ end
63
63
 
64
64
  The `at` property determines the root URL that the Lookbook UI will be served at.
65
65
 
66
- > If you would like to expose the Lookbook UI in production as well as in development, just remove the `if Rails.env.development?` condition from around the mount statement.
67
-
68
66
  Then you can start your app as normal and navigate to `http://localhost:3000/lookbook` (or whatever mount path you specified) to view your component previews in the Lookbook UI.
69
67
 
68
+ #### Mounting in Production
69
+
70
+ If you would like to expose the Lookbook UI in production as well as in development
71
+
72
+ 1. Remove the `if Rails.env.development?` condition from around the mount statement in `routes.rb`
73
+ 2. Add `config.view_component.show_previews = true` to `config/environments/production.rb`
74
+
75
+
70
76
  ## Usage
71
77
 
72
78
  You don't need to do anything special to see your ViewComponent previews and examples in Lookbook - just create them as normal and they'll automatically appear in the Lookbook UI. Preview templates, custom layouts and even bespoke [preview controllers](https://viewcomponent.org/guide/previews.html#configuring-preview-controller) should all work as you would expect.
@@ -96,7 +102,7 @@ class ButtonComponentPreview < ViewComponent::Preview
96
102
  # Button with icon
97
103
  # ----------------
98
104
  # This example uses dynamic preview parameters
99
- # which can be edited live in the Lookbook UI
105
+ # which can be edited live in the Lookbook UI
100
106
  #
101
107
  # @param text
102
108
  # @param icon select [heart, cog, alert]
@@ -161,7 +167,7 @@ The following Lookbook-specific tags are available for use:
161
167
  * [`@display`](#display-tag)
162
168
  * [`@!group ... @!endgroup`](#group-tag)
163
169
  * [`@hidden`](#hidden-tag)
164
- * [`@param`](#param-tag) [⚠️ **experimental!** - requires [feature opt-in](#experimental-features) ⚠️]
170
+ * [`@param`](#param-tag)
165
171
 
166
172
  <h3 id="label-tag">🏷 @label</h3>
167
173
 
@@ -311,9 +317,7 @@ class FooComponentPreview < ViewComponent::Preview
311
317
  end
312
318
  ```
313
319
 
314
- <h3 id="param-tag"> 🚧 @param (experimental)</h3>
315
-
316
- > ⚠️ 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.
320
+ <h3 id="param-tag">@param</h3>
317
321
 
318
322
  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.
319
323
 
@@ -326,7 +330,7 @@ The `@param` tag takes the following format:
326
330
  ```
327
331
 
328
332
  - `<name>` - name of the dynamic preview param
329
- - `<input_type>` - input field type to generate in the UI
333
+ - `<input_type>` - input field type to generate in the UI
330
334
  - `<opts?>` - YAML-encoded field options, used for some field types
331
335
 
332
336
  #### Input types
@@ -357,7 +361,7 @@ The following **input field types** are available for use:
357
361
  @param <name> select <options>
358
362
  ```
359
363
 
360
- `<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:
364
+ `<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:
361
365
 
362
366
  ```ruby
363
367
  # Basic options:
@@ -437,7 +441,7 @@ The following structured types are also available but should be considered **exp
437
441
 
438
442
  ```ruby
439
443
  class ButtonComponentPreview < ViewComponent::Preview
440
-
444
+
441
445
  # The params defined below will be editable in the UI:
442
446
  #
443
447
  # @param content text
@@ -470,8 +474,6 @@ class ProfileCardComponentPreview < ViewComponent::Preview
470
474
  end
471
475
  ```
472
476
 
473
- <img src=".github/assets/preview_example_notes.png" width="400">
474
-
475
477
  ## Configuration
476
478
 
477
479
  Lookbook will use the ViewComponent [configuration](https://viewcomponent.org/api.html#configuration) for your project to find and render your previews so you generally you won't need to configure anything separately.
@@ -496,6 +498,17 @@ If you wish to add additional paths to listen for changes in, you can use the `l
496
498
  config.lookbook.listen_paths << Rails.root.join('app/other/directory')
497
499
  ```
498
500
 
501
+ ### Custom favicon
502
+
503
+ If you want to change the favicon used by the Lookbook UI, you can provide a path to your own (or a data-uri string) using the `ui_favicon` option:
504
+
505
+ ```ruby
506
+ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
507
+ ```
508
+
509
+ > To disable the favicon entirely, set the value to `false`.
510
+
511
+
499
512
  <h3 id="experimental-features">Experimental features opt-in</h3>
500
513
 
501
514
  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.
@@ -510,10 +523,6 @@ To opt into individual experimental features, include the name of the feature in
510
523
  config.lookbook.experimental_features = ["feature_name"]
511
524
  ```
512
525
 
513
- The current experimental features that can be opted into are:
514
-
515
- - `params`: Live-editable, dynamic preview parameters ([read more](#param-tag)). Include `"params"` in the `experimental_features` config option to opt in.
516
-
517
526
  #### Opting into all experimental features (not recommended!)
518
527
 
519
528
  If you want to live life on the bleeding-edge you can opt-in to all current **and future** experimental features (usual caveats apply):
@@ -529,24 +538,13 @@ Lookbook provides a few keyboard shortcuts to help you quickly move around the U
529
538
 
530
539
  - `f` - move focus to the nav filter box
531
540
  - `Esc` [when focus is in nav filter box] - Clear contents if text is present, or return focus to the UI if the box is already empty
532
- - `s` - Switch to Source tab in the inspector
533
- - `o` - Switch to Output tab in the inspector
534
- - `n` - Switch to Notes tab in the inspector
541
+ - `s` - Switch to Source tab in the drawer
542
+ - `n` - Switch to Notes tab in the drawer
543
+ - `v` - Switch to the rendered preview
544
+ - `o` - Switch to the code preview
535
545
  - `r` - Refresh the preview (useful if using something like Faker to generate randomised data for the preview)
536
546
  - `w` - Open the standalone rendered preview in a new window
537
547
 
538
- ## Troubleshooting
539
-
540
- #### Blank preview window
541
-
542
- Certain setups (for example when using `Rack::LiveReload`) can cause an issue with the way that the preview iframe displays the rendered component preview (i.e. using the `srcdoc` attribute to avoid extra requests).
543
-
544
- If you are seeing a blank preview window, but the source and output tabs are both displaying code as expected, you can disable the use of the `srcdoc` attribute using the following configuration option:
545
-
546
- ```ruby
547
- config.lookbook.preview_srcdoc = false
548
- ```
549
-
550
548
  ## Contributing
551
549
 
552
550
  Lookbook is very much a small hobby/side project at the moment. I'd love to hear from anyone who is interested in contributing but I'm terrible at replying to emails or messages, so don't be surprised if I take forever to get back to you. It's not personal 😜
@@ -1,8 +1,12 @@
1
- export default function copy(id) {
1
+ export default function copy() {
2
2
  return {
3
3
  get content() {
4
- const target = document.getElementById(id);
5
- return (target ? target.innerHTML : "").trim();
4
+ const target = document.getElementById(
5
+ this.$root.getAttribute("data-target")
6
+ );
7
+ const decoder = document.createElement("textarea");
8
+ decoder.innerHTML = target ? target.innerHTML : "";
9
+ return decoder.value.trim();
6
10
  },
7
11
  done: false,
8
12
  async save() {
@@ -32,15 +32,23 @@ export default function inspector() {
32
32
  get drawerHidden() {
33
33
  return this.$store.inspector.drawer.hidden;
34
34
  },
35
- isActivePanel(panel) {
36
- return this.$store.inspector.drawer.active == panel;
35
+ get maxDrawerHeight() {
36
+ return Math.round(this.height * 0.7);
37
37
  },
38
- switchPanel(panel) {
39
- this.$store.inspector.drawer.active = panel;
38
+ get maxDrawerWidth() {
39
+ return Math.round(this.width * 0.7);
40
40
  },
41
- toggleView() {
42
- this.$store.inspector.preview.view =
43
- this.view === "html" ? "preview" : "html";
41
+ isActiveDrawerPanel(panel) {
42
+ return this.$store.inspector.drawer.panel === panel;
43
+ },
44
+ switchDrawerPanel(panel) {
45
+ this.$store.inspector.drawer.panel = panel;
46
+ },
47
+ isActivePreviewPanel(panel) {
48
+ return this.$store.inspector.preview.panel === panel;
49
+ },
50
+ switchPreviewPanel(panel) {
51
+ this.$store.inspector.preview.panel = panel;
44
52
  },
45
53
  toggleOrientation() {
46
54
  this.$store.inspector.drawer.orientation =
@@ -26,7 +26,9 @@ export default function page() {
26
26
  const response = await fetch(window.document.location);
27
27
  if (!response.ok) return window.location.reload();
28
28
  const html = await response.text();
29
- this.morph(new DOMParser().parseFromString(html, "text/html"));
29
+ const newDoc = new DOMParser().parseFromString(html, "text/html");
30
+ this.morph(newDoc);
31
+ document.title = newDoc.title;
30
32
  },
31
33
  setLocation(loc) {
32
34
  const path = loc instanceof Event ? loc.currentTarget.href : loc;
@@ -9,12 +9,13 @@ export default {
9
9
  drawer: {
10
10
  orientation: "horizontal",
11
11
  defaultPanel: "source",
12
- defaultHeight: 200,
12
+ defaultHeight: 300,
13
13
  defaultWidth: 500,
14
14
  minWidth: 350,
15
+ minHeight: 200,
15
16
  },
16
17
  preview: {
17
- view: "preview",
18
+ defaultPanel: "preview",
18
19
  },
19
20
  },
20
21
  };
@@ -6,16 +6,17 @@ export default function createInspectorStore(Alpine) {
6
6
  drawer: {
7
7
  hidden: Alpine.$persist(false).as("drawer-hidden"),
8
8
  orientation: Alpine.$persist(drawer.orientation).as("drawer-orientation"),
9
- active: Alpine.$persist(drawer.defaultPanel).as("drawer-active"),
9
+ panel: Alpine.$persist(drawer.defaultPanel).as("drawer-panel"),
10
10
  height: Alpine.$persist(drawer.defaultHeight).as("drawer-height"),
11
11
  width: Alpine.$persist(drawer.defaultWidth).as("drawer-width"),
12
12
  minWidth: drawer.minWidth,
13
+ minHeight: drawer.minHeight,
13
14
  visibleTabCount: Infinity,
14
15
  },
15
16
  preview: {
16
17
  width: Alpine.$persist("100%").as("preview-width"),
17
18
  height: Alpine.$persist("100%").as("preview-height"),
18
- view: Alpine.$persist(preview.view).as("preview-view"),
19
+ panel: Alpine.$persist(preview.defaultPanel).as("preview-panel"),
19
20
  lastWidth: null,
20
21
  lastHeight: null,
21
22
  resizing: false,
@@ -27,10 +27,8 @@ module Lookbook
27
27
  begin
28
28
  set_params
29
29
  @examples = examples_data
30
- @preview_srcdoc = if Lookbook.config.preview_srcdoc
31
- render_examples(examples_data).gsub("\"", "&quot;")
32
- end
33
- @panels = panels.filter { |name, panel| panel[:show] }
30
+ @drawer_panels = drawer_panels.filter { |name, panel| panel[:show] }
31
+ @preview_panels = preview_panels.filter { |name, panel| panel[:show] }
34
32
  rescue *EXCEPTIONS
35
33
  render "error"
36
34
  end
@@ -119,23 +117,37 @@ module Lookbook
119
117
  @nav
120
118
  end
121
119
 
122
- def panels
120
+ def preview_panels
123
121
  {
124
- source: {
125
- label: "Source",
126
- template: "lookbook/panels/source",
127
- hotkey: "s",
122
+ preview: {
123
+ label: "Preview",
124
+ template: "lookbook/panels/preview",
125
+ srcdoc: Lookbook.config.preview_srcdoc ? render_examples(examples_data).gsub("\"", "&quot;") : nil,
126
+ hotkey: "v",
128
127
  show: true,
129
128
  disabled: false,
130
- copy: true
129
+ copy: false
131
130
  },
132
131
  output: {
133
- label: "Output",
132
+ label: "HTML",
134
133
  template: "lookbook/panels/output",
135
134
  hotkey: "o",
136
135
  show: true,
137
136
  disabled: false,
138
137
  copy: true
138
+ }
139
+ }
140
+ end
141
+
142
+ def drawer_panels
143
+ {
144
+ source: {
145
+ label: "Source",
146
+ template: "lookbook/panels/source",
147
+ hotkey: "s",
148
+ show: true,
149
+ disabled: false,
150
+ copy: true
139
151
  },
140
152
  notes: {
141
153
  label: "Notes",
@@ -6,7 +6,9 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
7
7
 
8
8
  <link href="/lookbook-assets/css/app.css?v=<%= Lookbook::VERSION %>" rel="stylesheet">
9
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👀</text></svg>">
9
+ <% if config.ui_favicon != false %>
10
+ <link rel="icon" href="<%= config.ui_favicon || "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👀</text></svg>" %>">
11
+ <% end %>
10
12
 
11
13
  <% if config.auto_refresh %>
12
14
  <script>
@@ -1,10 +1,10 @@
1
1
  <div id="drawer" class="bg-white w-full h-full flex flex-col min-w-0">
2
- <div class="px-4 border-b border-gray-200 select-none flex-none" x-show="!drawerHidden">
3
- <div class="-mb-px flex cursor-auto relative">
2
+ <div class="pl-4 border-b border-gray-300 select-none flex-none" x-show="!drawerHidden">
3
+ <div class="flex cursor-auto relative">
4
4
  <div
5
5
  id="inspector-tabs-<%= example.id %>"
6
6
  x-data="tabs"
7
- class="min-w-0"
7
+ class="min-w-0 -mb-px"
8
8
  x-effect="$store.inspector.drawer.visibleTabCount = visibleTabCount"
9
9
  >
10
10
  <nav x-ref="tabs" class="flex h-10 space-x-8 flex-grow pr-8">
@@ -14,11 +14,11 @@
14
14
  href="#inspector-panel-<%= key %>"
15
15
  class="whitespace-nowrap pt-2.5 pb-1.5 px-1 border-b-2 cursor-pointer <%= "!text-gray-300" if props[:disabled] %>"
16
16
  :class="{
17
- 'border-indigo-400': isActivePanel('<%= key %>'),
18
- 'border-transparent text-gray-500 hover:text-gray-700': !isActivePanel('<%= key %>'),
17
+ 'border-indigo-400': isActiveDrawerPanel('<%= key %>'),
18
+ 'border-transparent text-gray-500 hover:text-gray-700': !isActiveDrawerPanel('<%= key %>'),
19
19
  'invisible': (<%= i %> > visibleTabCount)
20
20
  }"
21
- @click.stop.prevent="switchPanel('<%= key %>')"
21
+ @click.stop.prevent="switchDrawerPanel('<%= key %>')"
22
22
  <% if props[:hotkey] %>data-hotkey="<%= props[:hotkey] %>"<% end %>
23
23
  >
24
24
  <%== props[:label] %>
@@ -41,14 +41,13 @@
41
41
  <template id="inspector-dropdown-tab-<%= key %>-<%= example.id %>" x-if="<%= i %> > $store.inspector.drawer.visibleTabCount">
42
42
  <div :class="{'border-t border-gray-300': (<%= i %> > $store.inspector.drawer.visibleTabCount + 1)}">
43
43
  <a
44
-
45
44
  href="#inspector-panel-<%= key %>"
46
45
  class="block whitespace-nowrap py-2 px-4 border-l-2 cursor-pointer <%= "!text-gray-300" if props[:disabled] %>"
47
46
  :class="{
48
- 'border-indigo-400': $store.inspector.drawer.active === '<%= key %>',
49
- 'border-transparent text-gray-500 hover:text-gray-700': $store.inspector.drawer.active !== '<%= key %>',
47
+ 'border-indigo-400': $store.inspector.drawer.panel === '<%= key %>',
48
+ 'border-transparent text-gray-500 hover:text-gray-700': $store.inspector.drawer.panel !== '<%= key %>',
50
49
  }"
51
- @click.stop.prevent=" hideDropdown(); switchPanel('<%= key %>')"
50
+ @click.stop.prevent=" hideDropdown(); switchDrawerPanel('<%= key %>')"
52
51
  >
53
52
  <%== props[:label] %>
54
53
  </a>
@@ -58,31 +57,35 @@
58
57
  </div>
59
58
  </nav>
60
59
  </div>
61
- <div class="flex items-center ml-auto pl-8 space-x-3">
62
- <% panels.each do |key, props| %>
63
- <div
64
- ref="<%= "inspector-panel-#{example.id}-#{key}-copy" %>"
65
- class="flex items-center"
66
- :class="{'pointer-events-none opacity-30': <%= !props[:copy].present? %>}"
67
- x-show="isActivePanel('<%= key %>')"
68
- >
69
- <button
70
- class="text-gray-400 transition"
71
- x-data="copy('<%= "inspector-panel-#{example.id}-#{key}-clipboard" %>')"
72
- x-tooltip.theme.lookbook="done ? 'copied!' : 'copy to clipboard'"
73
- @click="save"
74
- :class="{
75
- '!text-green-600 hover:text-green-600': done,
76
- 'hover:text-indigo-500': !done}"
60
+ <div class="flex items-center bg-white border-l border-gray-200 ml-auto space-x-3 px-3">
61
+ <div>
62
+ <% panels.each do |key, props| %>
63
+ <div
64
+ ref="<%= "inspector-panel-#{example.id}-#{key}-copy" %>"
65
+ class="flex items-center"
66
+ :class="{'pointer-events-none opacity-30': <%= !props[:copy].present? %>}"
67
+ x-show="isActiveDrawerPanel('<%= key %>')"
77
68
  x-cloak
78
- <% unless props[:copy].present? %>disabled<% end %>
79
69
  >
80
- <%= icon "${done ? 'check' : 'clipboard'}", size: 4 %>
81
- </button>
82
- </div>
83
- <% end %>
70
+ <button
71
+ data-target="<%= "inspector-panel-#{example.id}-#{key}-clipboard" %>"
72
+ class="text-gray-400 transition"
73
+ x-data="copy"
74
+ x-tooltip.theme.lookbook="done ? 'copied!' : 'copy to clipboard'"
75
+ @click="save"
76
+ :class="{
77
+ '!text-green-600 hover:text-green-600': done,
78
+ 'hover:text-indigo-500': !done}"
79
+ x-cloak
80
+ <% unless props[:copy].present? %>disabled<% end %>
81
+ >
82
+ <%= icon "${done ? 'check' : 'clipboard'}", size: 4 %>
83
+ </button>
84
+ </div>
85
+ <% end %>
86
+ </div>
84
87
  <button
85
- x-tooltip.theme.lookbook="`switch orientation`"
88
+ x-tooltip.theme.lookbook="`${horizontal ? 'pin drawer on right' : 'pin drawer on bottom'}`"
86
89
  @click="toggleOrientation"
87
90
  :class="{'pointer-events-none opacity-30': !canBeVertical}"
88
91
  >
@@ -91,10 +94,10 @@
91
94
  class: "scale-[-1] text-gray-400 hover:text-indigo-800" %>
92
95
  </button>
93
96
  <button
94
- x-tooltip.theme.lookbook="`close drawer`"
97
+ x-tooltip.theme.lookbook="`hide drawer`"
95
98
  @click="toggleDrawer"
96
99
  >
97
- <%= icon "x",
100
+ <%= icon "x-circle",
98
101
  size: 4,
99
102
  class: "text-gray-400 hover:text-indigo-800" %>
100
103
  </button>
@@ -105,7 +108,7 @@
105
108
  <% panels.each do |key, props| %>
106
109
  <div
107
110
  class="h-full w-full absolute inset-0"
108
- x-show="$store.inspector.drawer.active === '<%= key %>'"
111
+ x-show="isActiveDrawerPanel('<%= key %>')"
109
112
  x-cloak
110
113
  >
111
114
  <div id="inspector-panel-<%= example.id %>-<%= key %>" class="h-full">
@@ -1,5 +1,5 @@
1
- <header class="py-2 px-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10 select-none min-w-0">
2
- <button class="flex-none mr-3" x-show="!$store.layout.desktop" @click="$store.sidebar.toggle">
1
+ <header class="pl-4 w-full flex-none bg-white border-b border-gray-300 flex items-center h-10 select-none min-w-0">
2
+ <button class="flex-none mr-6" x-show="!$store.layout.desktop" @click="$store.sidebar.toggle">
3
3
  <svg class="feather w-5 h-5 hover:text-indigo-500 transition">
4
4
  <use xlink:href="/lookbook-assets/feather-sprite.svg#menu" />
5
5
  </svg>
@@ -6,6 +6,8 @@
6
6
  <% end %>
7
7
  <% end %>
8
8
  <% else %>
9
- <% example = examples.first %>
10
- <%= component "nav_item", item: example, depth: example.hierarchy_depth, label: node.label, display: :node %>
9
+ <% if examples.any? %>
10
+ <% example = examples.first %>
11
+ <%= component "nav_item", item: example, depth: example.hierarchy_depth, label: node.label, display: :node %>
12
+ <% end %>
11
13
  <% end %>
@@ -1,52 +1,71 @@
1
- <div id="preview" class="h-auto min-h-0 w-full checked-bg">
2
- <div
3
- class="mx-auto h-full w-full"
4
- :style="`max-width: ${maxWidth}; max-height: ${maxHeight};`"
5
- x-data="previewWindow"
6
- >
7
- <div class="grid bg-white relative grid-cols-[1fr_17px] grid-rows-[1fr_17px] -inset-px" style="width: calc(100% + 2px); height: calc(100% + 2px)">
8
-
9
- <iframe seamless
10
- class="h-full w-full border border-gray-300"
11
- :class="{ 'pointer-events-none': $store.layout.reflowing }"
12
- src="<%= lookbook.preview_path(request.query_parameters) %>"
13
- <% if config.preview_srcdoc %>srcdoc="<%== srcdoc %>"<% end %>
14
- frameborder="0"
15
- x-data="sizes"
16
- x-effect="preview.width = width; preview.height = height;"
17
- ></iframe>
18
-
1
+ <div id="preview" class="grid grid-rows-[40px_1fr]">
2
+ <%= component "header" do %>
3
+ <div
4
+ id="preview-tabs-<%= @example.id %>" class="min-w-0 -mb-px">
5
+ <nav class="flex h-10 space-x-8 flex-grow pr-8">
6
+ <% panels.each do |key, props| %>
7
+ <a
8
+ id="preview-tab-<%= key %>-<%= @example.id %>"
9
+ href="#preview-panel-<%= key %>"
10
+ class="whitespace-nowrap pt-2.5 pb-1.5 px-1 border-b-2 cursor-pointer"
11
+ :class="{
12
+ 'border-indigo-400': isActivePreviewPanel('<%= key %>'),
13
+ 'border-transparent text-gray-500 hover:text-gray-700': !isActivePreviewPanel('<%= key %>'),
14
+ }"
15
+ @click.stop.prevent="switchPreviewPanel('<%= key %>')"
16
+ <% if props[:hotkey] %>data-hotkey="<%= props[:hotkey] %>"<% end %>
17
+ >
18
+ <%== props[:label] %>
19
+ </a>
20
+ <% end %>
21
+ </nav>
22
+ </div>
23
+ <div class="flex items-stretch h-full ml-auto space-x-3">
19
24
  <div
20
- class="resize-handle border-r border-t cursor-[col-resize]"
21
- @pointerdown="onResizeWidthStart"
22
- @dblclick="toggleFullWidth"
23
- >
24
- <svg class="h-4 w-4 pointer-events-none" fill="currentColor" viewBox="0 0 24 24">
25
- <path d="M8 5h2v14H8zM14 5h2v14h-2z"></path>
26
- </svg>
25
+ class="flex items-center text-xs font-monospace text-gray-700 space-x-1 opacity-50 hover:opacity-100 transition"
26
+ :class="{'opacity-100': $store.inspector.preview.resizing}"
27
+ x-show="isActivePreviewPanel('preview')">
28
+ <span x-text="`${preview.width}px`"></span>
29
+ <span class="text-gray-500">x</span>
30
+ <span x-text="`${preview.height}px`"></span>
27
31
  </div>
28
-
29
- <div
30
- class="resize-handle border-b border-l cursor-[col-resize]"
31
- @pointerdown="onResizeHeightStart"
32
- @dblclick="toggleFullHeight"
33
- >
34
- <svg class="h-4 w-4 pointer-events-none rotate-90" fill="currentColor" viewBox="0 0 24 24" >
35
- <path d="M8 5h2v14H8zM14 5h2v14h-2z"></path>
36
- </svg>
32
+ <div class="flex items-center bg-white border-l border-gray-200 space-x-3 text-gray-400 divide-x divide-gray-300 px-3">
33
+ <div class="flex items-center space-x-3">
34
+ <button
35
+ x-tooltip.theme.lookbook="`Refresh preview`"
36
+ @click.prevent.stop="refresh"
37
+ data-hotkey="r"
38
+ >
39
+ <%= icon "refresh-cw", size: 4, class: "hover:text-indigo-800" %>
40
+ </button>
41
+ <a
42
+ href="<%= preview_path %>"
43
+ target="_blank"
44
+ x-tooltip.theme.lookbook="`Open in new window`"
45
+ data-hotkey="w"
46
+ >
47
+ <%= icon "external-link", size: 4, class: "hover:text-indigo-800" %>
48
+ </a>
49
+ <button
50
+ x-tooltip.theme.lookbook="`${drawerHidden ? 'show' : 'hide'} drawer`"
51
+ @click="toggleDrawer"
52
+ x-show="drawerHidden"
53
+ data-hotkey="i"
54
+ >
55
+ <%= icon "${horizontal ? 'credit-card' : 'sidebar'}", size: 4, class: "hover:text-indigo-800 scale-[-1]" %>
56
+ </button>
57
+ </div>
58
+
37
59
  </div>
38
-
39
- <div
40
- class="resize-handle border-r border-b cursor-[nwse-resize]"
41
- @pointerdown="onResizeStart"
42
- @dblclick="toggleFullSize"
43
- >
44
- <svg class="h-3.5 w-3.5 pointer-events-none rotate-45 relative -top-px -left-px" fill="currentColor" viewBox="0 0 24 24" >
45
- <path d="M8 5h2v14H8zM14 5h2v14h-2z"></path>
46
- </svg>
47
- </div>
48
-
49
60
  </div>
50
-
61
+ <% end %>
62
+ <div class="bg-white relative flex-grow">
63
+ <% panels.each do |key, props| %>
64
+ <div class="h-full w-full absolute inset-0" x-show="isActivePreviewPanel('<%= key %>')" x-cloak>
65
+ <div id="preview-panel-<%= example.id %>-<%= key %>" class="h-full">
66
+ <%= render props[:template], key: key, examples: examples, **props %>
67
+ </div>
68
+ </div>
69
+ <% end %>
51
70
  </div>
52
71
  </div>
@@ -1,4 +1,4 @@
1
- <div class="p-4 h-full bg-gray-50 overflow-auto" data-morph-strategy="replace">
1
+ <div class="p-4 h-full bg-white overflow-auto" data-morph-strategy="replace">
2
2
  <%= component "code", wrap: "vertical" do -%>
3
3
  <% if examples.many? %>
4
4
  <% examples.each do |example| -%>