lookbook 0.2.1 → 0.3.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +155 -77
  3. data/app/assets/lookbook/css/app.css +28 -0
  4. data/app/assets/lookbook/js/app.js +51 -24
  5. data/app/assets/lookbook/js/nav/leaf.js +20 -0
  6. data/app/assets/lookbook/js/nav/node.js +31 -0
  7. data/app/assets/lookbook/js/nav.js +36 -0
  8. data/app/assets/lookbook/js/page.js +33 -0
  9. data/app/assets/lookbook/js/utils/clipboard.js +13 -0
  10. data/app/assets/lookbook/js/utils/morph.js +16 -0
  11. data/app/assets/lookbook/js/{reloader.js → utils/reloader.js} +0 -0
  12. data/app/assets/lookbook/js/utils/screen.js +44 -0
  13. data/app/assets/lookbook/js/{size_observer.js → utils/size_observer.js} +1 -1
  14. data/app/assets/lookbook/js/{split.js → utils/split.js} +4 -4
  15. data/app/assets/lookbook/js/workbench/inspector.js +11 -0
  16. data/app/assets/lookbook/js/workbench/preview.js +39 -0
  17. data/app/assets/lookbook/js/workbench.js +14 -0
  18. data/app/controllers/lookbook/{browser_controller.rb → app_controller.rb} +58 -31
  19. data/app/helpers/lookbook/application_helper.rb +1 -1
  20. data/app/views/lookbook/_sidebar.html.erb +45 -0
  21. data/app/views/lookbook/_workbench.html.erb +12 -0
  22. data/app/views/lookbook/{browser → app}/error.html.erb +0 -0
  23. data/app/views/lookbook/app/index.html.erb +11 -0
  24. data/app/views/lookbook/{browser → app}/not_found.html.erb +1 -1
  25. data/app/views/lookbook/app/show.html.erb +1 -0
  26. data/app/views/lookbook/layouts/app.html.erb +22 -30
  27. data/app/views/lookbook/layouts/group.html.erb +6 -0
  28. data/app/views/lookbook/nav/_collection.html.erb +5 -0
  29. data/app/views/lookbook/nav/_node.html.erb +19 -0
  30. data/app/views/lookbook/nav/_preview.html.erb +29 -0
  31. data/app/views/lookbook/shared/_clipboard.html.erb +11 -0
  32. data/app/views/lookbook/shared/_header.html.erb +8 -0
  33. data/app/views/lookbook/workbench/_header.html.erb +37 -0
  34. data/app/views/lookbook/workbench/_inspector.html.erb +32 -0
  35. data/app/views/lookbook/workbench/_preview.html.erb +24 -0
  36. data/app/views/lookbook/workbench/inspector/_code.html.erb +3 -0
  37. data/app/views/lookbook/workbench/inspector/_notes.html.erb +24 -0
  38. data/app/views/lookbook/{partials → workbench}/inspector/_plain.html.erb +0 -0
  39. data/config/routes.rb +6 -4
  40. data/lib/lookbook/engine.rb +6 -4
  41. data/lib/lookbook/preview.rb +25 -3
  42. data/lib/lookbook/preview_controller.rb +6 -1
  43. data/lib/lookbook/preview_example.rb +3 -2
  44. data/lib/lookbook/preview_group.rb +37 -0
  45. data/lib/lookbook/taggable.rb +5 -1
  46. data/lib/lookbook/version.rb +1 -1
  47. data/lib/lookbook.rb +1 -0
  48. data/lib/tasks/lookbook_tasks.rake +1 -1
  49. data/public/lookbook-assets/app.css +267 -113
  50. data/public/lookbook-assets/app.js +1014 -116
  51. data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
  52. metadata +54 -27
  53. data/app/assets/lookbook/js/preview.js +0 -76
  54. data/app/views/lookbook/browser/index.html.erb +0 -8
  55. data/app/views/lookbook/browser/show.html.erb +0 -33
  56. data/app/views/lookbook/partials/_preview.html.erb +0 -18
  57. data/app/views/lookbook/partials/_sidebar.html.erb +0 -21
  58. data/app/views/lookbook/partials/inspector/_code.html.erb +0 -1
  59. data/app/views/lookbook/partials/inspector/_inspector.html.erb +0 -43
  60. data/app/views/lookbook/partials/inspector/_prose.html.erb +0 -3
  61. data/app/views/lookbook/partials/nav/_collection.html.erb +0 -17
  62. data/app/views/lookbook/partials/nav/_label.html.erb +0 -13
  63. data/app/views/lookbook/partials/nav/_nav.html.erb +0 -27
  64. data/app/views/lookbook/partials/nav/_preview.html.erb +0 -48
  65. data/config/lookbook_cable.yml +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f1b2263fb0949354da2aab14aeea2e2c0052810b6eb316249d9796718413614
4
- data.tar.gz: 7423d9845627d153a1c46f1adc3fa4a14ed708d97f80a664b67c0e9cc0018528
3
+ metadata.gz: 0eddf2109917e499486420e62609c84456017efee3429da0370057a0792953eb
4
+ data.tar.gz: f5713d64d58704467646bebf3bdf979ad905c8e70df08cebb20401ed279b2a31
5
5
  SHA512:
6
- metadata.gz: b821396d726636b7c633535543c1c4e718181b65809b85ae5b4ffee857914cb6910f554d28c0cced233d0e8419b60545b4115bda573d6b15b473fdf6c9d0fe24
7
- data.tar.gz: 902e85067b97aa9371e9708aca1c14c2f73d928603755f63357e1644e7b078e515a2e979cdedfef0e37b206f930e9ac21f7ab15aeb138d2f8e3adc86f0dd1441
6
+ metadata.gz: 41238edb770517284df464e2a70682abc239dc9efb0212c4f467f596d013ea40ffbbcf391061eba724e2ffb168f9fc4ae3ef5684fe348f1574ea930746b8d796
7
+ data.tar.gz: c9c78b2399fae7a00185441af62a75568afc3a732ca46889e9314234aabc520f62aa9bbc89c84ba8f4af40d4e64d56d5152b96f48994b6a60d53a06bb9a5de7f
data/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  <div align="center">
2
- <h2>Lookbook</h2>
2
+ <h1>👀 Lookbook 👀</h1>
3
3
 
4
- 👀 A native development UI for [ViewComponent](http://viewcomponent.org/) 👀
4
+ <p>A native development UI for <a href="http://viewcomponent.org/">ViewComponent</a></p>
5
5
 
6
+ <div>
7
+ <a href="https://rubygems.org/gems/lookbook"><img src="https://badge.fury.io/rb/lookbook.svg" alt="Gem version">
8
+ <a href="https://github.com/testdouble/standard"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Ruby Style Guide">
9
+ <a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg" alt="Code style: Prettier">
10
+ </div>
6
11
  </div>
7
12
 
8
13
  ---
9
14
 
10
- Lookbook gives [ViewComponent](http://viewcomponent.org/)-based projects a _ready-to-go_ development UI for navigating, inspecting and interacting with component previews.
15
+ **Lookbook gives [ViewComponent](http://viewcomponent.org/)-based projects a _ready-to-go_ development UI for navigating, inspecting and interacting with component previews.**
11
16
 
12
17
  It uses (and extends) the native [ViewComponent preview functionality](https://viewcomponent.org/guide/previews.html), so you don't need to learn a new DSL or create any extra files to get up and running.
13
18
 
@@ -38,18 +43,13 @@ The [demo app repo](https://github.com/allmarkedup/lookbook-demo) contains instr
38
43
 
39
44
  ### 1. Add as a dependency
40
45
 
41
- In your `Gemfile` add:
46
+ Add Lookbook to your `Gemfile` somewhere **after** the ViewComponent gem. For example:
42
47
 
43
48
  ```ruby
49
+ gem "view_component", require: "view_component/engine"
44
50
  gem "lookbook"
45
51
  ```
46
52
 
47
- or
48
-
49
- ```bash
50
- gem install lookbook
51
- ```
52
-
53
53
  ### 2. Mount the Lookbook engine
54
54
 
55
55
  You then need to mount the Lookbook engine (at a path of your choosing) in your `routes.rb` file:
@@ -70,125 +70,179 @@ Then you can start your app as normal and navigate to `http://localhost:3000/loo
70
70
 
71
71
  ## Usage
72
72
 
73
- You don't need to do anything special to create ViewComponent previews for Lookbook.
73
+ 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.
74
74
 
75
- Lookbook will use the [ViewComponent configuration options](https://viewcomponent.org/api.html#configuration) for your project to find and render your components so you don't need to configure anything separately (unless you want to tweak the behaviour or look of Lookbook itself, of course).
75
+ > If you are new to ViewComponent development, checkout the ViewComponent [documentation](https://viewcomponent.org/guide/) on how to get started developing your components and [creating previews](https://viewcomponent.org/guide/previews.html).
76
76
 
77
- > If you are new to ViewComponent development, checkout the [ViewComponent docs](https://viewcomponent.org/guide/) on how to get started developing your components and creating previews.
77
+ ### Annotating preview files
78
78
 
79
- Lookbook uses the exact same [preview files](https://viewcomponent.org/guide/previews.html) as 'regular' ViewComponent previews, so using preview templates, custom layouts and even bespoke [preview controllers](https://viewcomponent.org/guide/previews.html#configuring-preview-controller) all works as you would expect.
79
+ Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/docs/Tags.md) in your preview classes to customise and extend the standard ViewComponent preview experience:
80
80
 
81
- ### Comment tags
81
+ ```ruby
82
+ # @label Basic Button
83
+ class ButtonComponentPreview < ViewComponent::Preview
82
84
 
83
- Lookbook uses [Yard-style tags](https://rubydoc.info/gems/yard/file/docs/Tags.md) in class and method comments to extract additional information about previews and examples.
85
+ # Primary button
86
+ # ---------------
87
+ # This is the button style you should use for most things.
88
+ #
89
+ # @label Primary
90
+ def default
91
+ render ButtonComponent.new do
92
+ "Click me"
93
+ end
94
+ end
84
95
 
85
- Tags are just strings identified by their `@` prefix - for example `@hidden`. Tags are always placed in a comment above the relevant preview class or example method. The comments can still contain any other text, and multiple tags can be included in any one comment. For example:
96
+ # Secondary button
97
+ # ---------------
98
+ # This should be used for less important actions.
99
+ def secondary
100
+ render ButtonComponent.new(style: :secondary) do
101
+ "Click me"
102
+ end
103
+ end
86
104
 
87
- ```ruby
88
- # This is a class-level comment.
89
- # @hidden
90
- class MyClass
91
- # This is a method-level comment.
105
+ # Unicorn button
106
+ # ---------------
107
+ # This button style is still a **work in progress**.
108
+ #
92
109
  # @hidden
93
- def my_method
110
+ def secondary
111
+ render ButtonComponent.new do
112
+ "Click me"
113
+ end
94
114
  end
115
+
116
+ # @!group More examples
117
+
118
+ def short_text
119
+ render ButtonComponent.new do
120
+ "Go"
121
+ end
122
+ end
123
+
124
+ def long_text
125
+ render ButtonComponent.new do
126
+ "Click here to do this thing because it's the best way to do it"
127
+ end
128
+ end
129
+
130
+ def emoji_text
131
+ render ButtonComponent.new do
132
+ "👀📗"
133
+ end
134
+ end
135
+
136
+ # @!endgroup
137
+
95
138
  end
96
139
  ```
97
140
 
98
- Some tags can also require additional arguments. Further information on the tags Lookbook uses are detailed in the docs below.
141
+ **Tags** are just strings identified by their `@` prefix - for example `@hidden`. Tags are always placed in a comment above the relevant preview class or example method.
99
142
 
100
- ### 📝 Adding notes to previews
143
+ The following Lookbook-specific tags are available for use:
101
144
 
102
- Lookbook lets you add notes to your preview examples which are then displayed in the inspector panel. They look something like this:
145
+ #### `@label <text>`
103
146
 
104
- <img src=".github/assets/preview_example_notes.png" width="400">
147
+ Used to replace the auto-generated navigation label for the item with `<text>`.
105
148
 
106
- Notes are generated from comments above example methods in your preview files. Below is an example of two preview examples that both have notes:
149
+ > Available for preview classes & example methods.
107
150
 
108
151
  ```ruby
109
- class ButtonComponentPreview < ViewComponent::Preview
152
+ # @label Preview Label
153
+ class FooComponentPreview < ViewComponent::Preview
110
154
 
111
- # Add notes as comments above the example methods.
112
- # Multi-line is just fine and **markdown** is supported too!
113
- #
114
- # It's a good place to put usage and implementation instructions
115
- # for people browsing the component previews in the UI.
155
+ # @label Example Label
116
156
  def default
117
- render ButtonComponent.new(text: "Click me")
118
- end
119
-
120
- # Each preview example has it's own notes, extracted from the method comments.
121
- def danger
122
- render ButtonComponent.new(text: "Don't do it!", theme: :danger)
123
157
  end
124
158
  end
125
159
  ```
126
160
 
127
- ### 👀 Navigation customisation
161
+ #### `@hidden`
128
162
 
129
- Lookbook generates a nested navigation menu based on the file structure of your component preview directory (or directories). This can be customised in a number of ways.
163
+ Used to temporarily exclude an item from the Lookbook navigation. The item will still be accessible via it's URL.
130
164
 
131
- #### Preview and example labels
165
+ 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.
132
166
 
133
- By default, the labels shown for previews and examples are stripped and 'titlized' versions of the preview file names and the example method names, respectively.
134
-
135
- If you wish to override the automatic navigation label generation for a preview or example you can use the `@label` comment tag:
167
+ > Available for both preview classes & example methods.
136
168
 
137
169
  ```ruby
138
- # @label Standard Button
139
- class BtnPreview < ViewComponent::Preview
170
+ # @hidden
171
+ class FooComponentPreview < ViewComponent::Preview
140
172
 
141
- # @label Icon Button
142
- def with_icon
173
+ # @hidden
174
+ def default
143
175
  end
144
176
  end
145
177
  ```
146
178
 
147
- In the example above, the preview and example would be displayed like this:
179
+ #### `@!group <name> ... @!endgroup`
180
+
181
+ For smaller components, it can often make sense to render a set of preview examples in a single window, rather than representing them as individual items in the navigation which can start to look a bit cluttered.
182
+
183
+ You can group a set of examples by wrapping them in `@!group <name>` / `@!endgroup` tags within your preview file:
148
184
 
149
- <img src=".github/assets/nav_labels.png" width="200">
185
+ ```ruby
186
+ class HeaderComponentPreview < ViewComponent::Preview
150
187
 
151
- #### Excluding previews and/or examples from the navigation
188
+ def standard
189
+ render Elements::HeaderComponent.new do
190
+ "Standard header"
191
+ end
192
+ end
152
193
 
153
- Sometimes you may want to temporarily hide a preview or an individual example from the Lookbook UI. This means that the preview or example will not show up in the navigation, but will still be accessible via it's URL. You can use the `@hidden` comment tag to manage this.
194
+ # @!group Sizes
154
195
 
155
- To **hide an entire preview** include the `@hidden` tag in a class comment:
196
+ def small
197
+ render Elements::HeaderComponent.new(size: 12) do
198
+ "Small header"
199
+ end
200
+ end
201
+
202
+ def medium
203
+ render Elements::HeaderComponent.new(size: 16) do
204
+ "Small header"
205
+ end
206
+ end
207
+
208
+ def big
209
+ render Elements::HeaderComponent.new(size: 24) do
210
+ "Small header"
211
+ end
212
+ end
213
+
214
+ # @!endgroup
156
215
 
157
- ```ruby
158
- # @hidden
159
- class MyComponentPreview < ViewComponent::Preview
160
- # examples here....
161
216
  end
162
217
  ```
163
218
 
164
- To **hide an individual example** include the `@hidden` tag in the appropriate method comment:
219
+ The example above would display the `Sizes` examples grouped together on a single page, rather than as indiviual items in the navigation:
165
220
 
166
- ```ruby
167
- class MyComponentPreview < ViewComponent::Preview
221
+ <img src=".github/assets/nav_group.png">
168
222
 
169
- # Hidden Example
170
- # ----------
171
- # You won't see this in the nav!
172
- #
173
- # @hidden
174
- def hidden_example
175
- # ...
176
- end
223
+ You can have as many groups as you like within a single preview class, but each example can only belong to one group.
177
224
 
178
- def a_visible_example
179
- # ...
180
- end
225
+ #### Adding notes
181
226
 
182
- # @hidden
183
- def another_hidden_example
184
- # ...
227
+ All comment text other than tags will be treated as markdown and rendered in the **Notes** panel for that example in the Lookbook UI.
228
+
229
+ ```ruby
230
+ # @hidden
231
+ class ProfileCardComponentPreview < ViewComponent::Preview
232
+
233
+ # Profile Card
234
+ # ------------
235
+ # Use the default profile card component whenever you need to represent a user.
236
+ def default
185
237
  end
186
238
  end
187
239
  ```
188
240
 
241
+ <img src=".github/assets/preview_example_notes.png" width="400">
242
+
189
243
  ## Configuration
190
244
 
191
- Lookbook uses ViewComponent's configuration options for anything to do with previews, paths and general setup, so you won't need to duplicate any settings.
245
+ 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.
192
246
 
193
247
  However the following Lookbook-specific config options are also available:
194
248
 
@@ -208,6 +262,30 @@ If you wish to add additional paths to listen for changes in, you can use the `l
208
262
  config.lookbook.listen_paths << Rails.root.join('app/other/directory')
209
263
  ```
210
264
 
265
+ ## Keyboard shortcuts
266
+
267
+ Lookbook provides a few keyboard shortcuts to help you quickly move around the UI.
268
+
269
+ - `f` - move focus to the nav filter box
270
+ - `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
271
+ - `s` - Switch to Source tab in the inspector
272
+ - `o` - Switch to Output tab in the inspector
273
+ - `n` - Switch to Notes tab in the inspector
274
+ - `r` - Refresh the preview (useful if using something like Faker to generate randomised data for the preview)
275
+ - `w` - Open the standalone rendered preview in a new window
276
+
277
+ ## Troubleshooting
278
+
279
+ #### Blank preview window
280
+
281
+ 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).
282
+
283
+ 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:
284
+
285
+ ```ruby
286
+ config.lookbook.preview_srcdoc = false
287
+ ```
288
+
211
289
  ## Contributing
212
290
 
213
291
  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 😜
@@ -34,4 +34,32 @@
34
34
  stroke-linejoin: round;
35
35
  fill: none;
36
36
  }
37
+
38
+ .h-fill {
39
+ height: -webkit-fill-available;
40
+ }
41
+
42
+ .min-h-fill {
43
+ min-height: -webkit-fill-available;
44
+ }
45
+
46
+ ::-webkit-scrollbar {
47
+ width: 8px;
48
+ height: 8px;
49
+ }
50
+
51
+ ::-webkit-scrollbar-track {
52
+ background: transparent;
53
+ }
54
+
55
+ ::-webkit-scrollbar-thumb {
56
+ @apply bg-gray-300 transition-colors;
57
+ border-radius: 6px;
58
+ border: 2px solid transparent;
59
+ background-clip: content-box;
60
+ }
61
+
62
+ ::-webkit-scrollbar-thumb:hover {
63
+ @apply bg-gray-400;
64
+ }
37
65
  }
@@ -1,47 +1,74 @@
1
+ import { install } from "@github/hotkey";
1
2
  import Alpine from "alpinejs";
2
3
  import Fern from "@ryangjchandler/fern";
3
- import Tooltip from "@ryangjchandler/alpine-tooltip";
4
- import Clipboard from "@ryangjchandler/alpine-clipboard";
5
- import split from "./split";
6
- import preview from "./preview";
7
- import observeSize from "./size_observer";
8
- import reloader from "./reloader";
4
+ import AlpineTooltip from "@ryangjchandler/alpine-tooltip";
5
+ import AlpineClipboard from "@ryangjchandler/alpine-clipboard";
6
+ import Screen from "./utils/screen";
7
+ import split from "./utils/split";
8
+ import page from "./page";
9
+ import workbench from "./workbench";
10
+ import preview from "./workbench/preview";
11
+ import inspector from "./workbench/inspector";
12
+ import nav from "./nav";
13
+ import navNode from "./nav/node";
14
+ import navLeaf from "./nav/leaf";
15
+ import sizeObserver from "./utils/size_observer";
16
+ import reloader from "./utils/reloader";
17
+ import clipboard from "./utils/clipboard";
18
+
19
+ window.Alpine = Alpine;
9
20
 
10
21
  // Plugins
11
22
 
12
23
  Alpine.plugin(Fern);
13
- Alpine.plugin(Tooltip);
14
- Alpine.plugin(Clipboard);
15
-
16
- // Data
17
-
18
- Alpine.data("preview", preview);
19
- Alpine.data("sizeObserver", observeSize);
20
- Alpine.data("split", split);
24
+ Alpine.plugin(AlpineTooltip);
25
+ Alpine.plugin(AlpineClipboard);
26
+ Alpine.plugin(Screen);
21
27
 
22
28
  // Stores
23
29
 
24
- Alpine.store("app", { reflowing: false });
30
+ Alpine.store("page", {
31
+ reflowing: false,
32
+ doc: window.document,
33
+ });
34
+
25
35
  Alpine.persistedStore("nav", {
26
36
  width: 280,
27
37
  filter: "",
28
38
  open: {},
29
- scrollTop: 0,
30
- shouldDisplay(previewName) {
31
- const cleanFilter = this.filter.replace(/\s/g, "");
32
- return (
33
- cleanFilter === "" || previewName.includes(cleanFilter.toLowerCase())
34
- );
35
- },
36
39
  });
37
- Alpine.persistedStore("preview", {});
40
+
38
41
  Alpine.persistedStore("inspector", {
39
42
  height: 200,
40
43
  active: "source",
41
44
  });
42
45
 
46
+ Alpine.persistedStore("preview", {
47
+ width: "100%",
48
+ });
49
+
50
+ // Components & utils
51
+
52
+ Alpine.data("page", page);
53
+ Alpine.data("nav", nav);
54
+ Alpine.data("navNode", navNode);
55
+ Alpine.data("navLeaf", navLeaf);
56
+ Alpine.data("workbench", workbench);
57
+ Alpine.data("preview", preview);
58
+ Alpine.data("inspector", inspector);
59
+ Alpine.data("clipboard", clipboard);
60
+ Alpine.data("sizeObserver", sizeObserver);
61
+ Alpine.data("split", split);
62
+
43
63
  // Init
44
64
 
65
+ for (const el of document.querySelectorAll("[data-hotkey]")) {
66
+ install(el);
67
+ }
68
+
69
+ if (window.SOCKET_PATH) {
70
+ reloader(window.SOCKET_PATH).start();
71
+ }
72
+
45
73
  window.Alpine = Alpine;
46
- reloader(window.SOCKET_PATH).start();
47
74
  Alpine.start();
@@ -0,0 +1,20 @@
1
+ export default function navLeaf() {
2
+ return {
3
+ path: null,
4
+ matchers: [],
5
+ active: false,
6
+ hidden: false,
7
+ setActive() {
8
+ this.active = this.path === window.location.pathname;
9
+ },
10
+ filter() {
11
+ if (this.$store.nav.filtering) {
12
+ const text = this.$store.nav.filterText;
13
+ const matched = this.matchers.map((m) => m.includes(text));
14
+ this.hidden = !matched.filter((m) => m).length;
15
+ } else {
16
+ this.hidden = false;
17
+ }
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,31 @@
1
+ export default function navNode() {
2
+ return {
3
+ id: null,
4
+ hidden: true,
5
+ children: [],
6
+ init() {
7
+ this.id = this.$el.id;
8
+ },
9
+ open() {
10
+ return this.$store.nav.open[this.id];
11
+ },
12
+ getChildren() {
13
+ return this.$refs.items
14
+ ? Array.from(this.$refs.items.querySelectorAll(":scope > li"))
15
+ : [];
16
+ },
17
+ filter() {
18
+ this.hidden = true;
19
+ this.getChildren().forEach((child) => {
20
+ const data = child._x_dataStack[0];
21
+ data.filter();
22
+ if (!data.hidden) {
23
+ this.hidden = false;
24
+ }
25
+ });
26
+ },
27
+ toggle() {
28
+ this.$store.nav.open[this.id] = !this.$store.nav.open[this.id];
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,36 @@
1
+ import morph from "./utils/morph";
2
+
3
+ export default function () {
4
+ return {
5
+ clearFilter() {
6
+ this.$store.nav.filter = "";
7
+ },
8
+ init() {
9
+ this.$watch("$store.nav.filter", (value) => {
10
+ const nav = this.$store.nav;
11
+ nav.filterText = value.replace(/\s/g, "").toLowerCase();
12
+ nav.filtering = nav.filterText.length > 0;
13
+ });
14
+ },
15
+ updateNav(event) {
16
+ const nav = document.getElementById("nav");
17
+ nav.style.height = `${this.$refs.shim.offsetHeight}px`;
18
+ morph(nav, event.detail.doc.getElementById("nav"));
19
+ Promise.resolve().then(() => {
20
+ this.$refs.shim.style.height = "auto";
21
+ this.$dispatch("nav:updated");
22
+ });
23
+ },
24
+ navigate($event) {
25
+ history.pushState({}, null, $event.currentTarget.href);
26
+ this.$dispatch("popstate");
27
+ },
28
+ focusFilter() {
29
+ this.currentFocus = this.$refs.filter;
30
+ setTimeout(() => this.$refs.filter.focus(), 0);
31
+ },
32
+ unfocusFilter() {
33
+ this.$refs.filter.blur();
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,33 @@
1
+ import morph from "./utils/morph";
2
+
3
+ export default function page() {
4
+ const store = Alpine.store("page");
5
+ return {
6
+ ready: false,
7
+ sidebarOpenMobile: false,
8
+ init() {
9
+ this.$nextTick(() => (this.ready = true));
10
+ },
11
+ splitProps: {
12
+ minSize: 200,
13
+ onDrag(splits) {
14
+ Alpine.store("nav").width = Math.min(splits[0], 500);
15
+ },
16
+ },
17
+ async fetchHTML() {
18
+ const response = await fetch(window.document.location);
19
+ if (!response.ok) return window.location.reload();
20
+ const html = await response.text();
21
+ store.doc = new DOMParser().parseFromString(html, "text/html");
22
+ return store.doc;
23
+ },
24
+ updateTitle() {
25
+ document.title = store.doc.title;
26
+ },
27
+ render() {
28
+ if (this.ready) {
29
+ morph(this.$el, store.doc.getElementById(this.$el.id));
30
+ }
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,13 @@
1
+ export default function clipboard() {
2
+ return {
3
+ content: null,
4
+ done: false,
5
+ save() {
6
+ this.$clipboard(this.content);
7
+ this.done = true;
8
+ setTimeout(() => {
9
+ this.done = false;
10
+ }, 1000);
11
+ },
12
+ };
13
+ }
@@ -0,0 +1,16 @@
1
+ import morph from "morphdom";
2
+
3
+ export default function (from, to, opts = {}) {
4
+ morph(from, to, {
5
+ onBeforeElUpdated: function (fromEl, toEl) {
6
+ if (fromEl._x_dataStack) {
7
+ Alpine.clone(fromEl, toEl);
8
+ }
9
+ if (fromEl.isEqualNode(toEl)) {
10
+ return false;
11
+ }
12
+ return true;
13
+ },
14
+ ...opts,
15
+ });
16
+ }
@@ -0,0 +1,44 @@
1
+ // Adapted from: https://github.com/alpine-collective/toolkit
2
+
3
+ export default function (Alpine) {
4
+ // Create reactive data context
5
+ let data = Alpine.reactive({ screensize: window.innerWidth });
6
+
7
+ // Configuration
8
+ const defaultBreakpoints = {
9
+ xs: 0,
10
+ sm: 640,
11
+ md: 768,
12
+ lg: 1024,
13
+ xl: 1280,
14
+ "2xl": 1536,
15
+ };
16
+
17
+ const breakpoints =
18
+ window.AlpineMagicHelpersConfig &&
19
+ window.AlpineMagicHelpersConfig.breakpoints
20
+ ? window.AlpineMagicHelpersConfig.breakpoints
21
+ : defaultBreakpoints;
22
+
23
+ window.addEventListener("resize", () => {
24
+ data.screensize = window.innerWidth;
25
+ });
26
+
27
+ Alpine.magic("screen", () => (breakpoint) => {
28
+ let width = data.screensize;
29
+
30
+ if (Number.isInteger(breakpoint)) return breakpoint <= width;
31
+
32
+ // Check if breakpoint exists
33
+ if (breakpoints[breakpoint] === undefined) {
34
+ throw Error(
35
+ "Undefined $screen property: " +
36
+ breakpoint +
37
+ ". Supported properties: " +
38
+ Object.keys(breakpoints).join(", ")
39
+ );
40
+ }
41
+
42
+ return breakpoints[breakpoint] <= width;
43
+ });
44
+ }
@@ -1,4 +1,4 @@
1
- export default function () {
1
+ export default function sizeObserver() {
2
2
  return {
3
3
  observedWidth: 0,
4
4
  observedHeight: 0,