lookbook 0.2.1 → 0.3.0.beta.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 +4 -4
- data/README.md +155 -77
- data/app/assets/lookbook/css/app.css +28 -0
- data/app/assets/lookbook/js/app.js +51 -24
- data/app/assets/lookbook/js/nav/leaf.js +20 -0
- data/app/assets/lookbook/js/nav/node.js +31 -0
- data/app/assets/lookbook/js/nav.js +36 -0
- data/app/assets/lookbook/js/page.js +33 -0
- data/app/assets/lookbook/js/utils/clipboard.js +13 -0
- data/app/assets/lookbook/js/utils/morph.js +16 -0
- data/app/assets/lookbook/js/{reloader.js → utils/reloader.js} +0 -0
- data/app/assets/lookbook/js/utils/screen.js +44 -0
- data/app/assets/lookbook/js/{size_observer.js → utils/size_observer.js} +1 -1
- data/app/assets/lookbook/js/{split.js → utils/split.js} +4 -4
- data/app/assets/lookbook/js/workbench/inspector.js +11 -0
- data/app/assets/lookbook/js/workbench/preview.js +39 -0
- data/app/assets/lookbook/js/workbench.js +14 -0
- data/app/controllers/lookbook/{browser_controller.rb → app_controller.rb} +58 -31
- data/app/helpers/lookbook/application_helper.rb +1 -1
- data/app/views/lookbook/_sidebar.html.erb +45 -0
- data/app/views/lookbook/_workbench.html.erb +12 -0
- data/app/views/lookbook/{browser → app}/error.html.erb +0 -0
- data/app/views/lookbook/app/index.html.erb +11 -0
- data/app/views/lookbook/{browser → app}/not_found.html.erb +1 -1
- data/app/views/lookbook/app/show.html.erb +1 -0
- data/app/views/lookbook/layouts/app.html.erb +22 -30
- data/app/views/lookbook/layouts/group.html.erb +6 -0
- data/app/views/lookbook/nav/_collection.html.erb +5 -0
- data/app/views/lookbook/nav/_node.html.erb +19 -0
- data/app/views/lookbook/nav/_preview.html.erb +29 -0
- data/app/views/lookbook/shared/_clipboard.html.erb +11 -0
- data/app/views/lookbook/shared/_header.html.erb +8 -0
- data/app/views/lookbook/workbench/_header.html.erb +37 -0
- data/app/views/lookbook/workbench/_inspector.html.erb +32 -0
- data/app/views/lookbook/workbench/_preview.html.erb +24 -0
- data/app/views/lookbook/workbench/inspector/_code.html.erb +3 -0
- data/app/views/lookbook/workbench/inspector/_notes.html.erb +24 -0
- data/app/views/lookbook/{partials → workbench}/inspector/_plain.html.erb +0 -0
- data/config/routes.rb +6 -4
- data/lib/lookbook/engine.rb +6 -4
- data/lib/lookbook/preview.rb +25 -3
- data/lib/lookbook/preview_controller.rb +6 -1
- data/lib/lookbook/preview_example.rb +3 -2
- data/lib/lookbook/preview_group.rb +37 -0
- data/lib/lookbook/taggable.rb +5 -1
- data/lib/lookbook/version.rb +1 -1
- data/lib/lookbook.rb +1 -0
- data/lib/tasks/lookbook_tasks.rake +1 -1
- data/public/lookbook-assets/app.css +267 -113
- data/public/lookbook-assets/app.js +1014 -116
- data/{app/views/lookbook/partials/_icon_sprite.html.erb → public/lookbook-assets/feather-sprite.svg} +1 -1
- metadata +54 -27
- data/app/assets/lookbook/js/preview.js +0 -76
- data/app/views/lookbook/browser/index.html.erb +0 -8
- data/app/views/lookbook/browser/show.html.erb +0 -33
- data/app/views/lookbook/partials/_preview.html.erb +0 -18
- data/app/views/lookbook/partials/_sidebar.html.erb +0 -21
- data/app/views/lookbook/partials/inspector/_code.html.erb +0 -1
- data/app/views/lookbook/partials/inspector/_inspector.html.erb +0 -43
- data/app/views/lookbook/partials/inspector/_prose.html.erb +0 -3
- data/app/views/lookbook/partials/nav/_collection.html.erb +0 -17
- data/app/views/lookbook/partials/nav/_label.html.erb +0 -13
- data/app/views/lookbook/partials/nav/_nav.html.erb +0 -27
- data/app/views/lookbook/partials/nav/_preview.html.erb +0 -48
- data/config/lookbook_cable.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0eddf2109917e499486420e62609c84456017efee3429da0370057a0792953eb
|
4
|
+
data.tar.gz: f5713d64d58704467646bebf3bdf979ad905c8e70df08cebb20401ed279b2a31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41238edb770517284df464e2a70682abc239dc9efb0212c4f467f596d013ea40ffbbcf391061eba724e2ffb168f9fc4ae3ef5684fe348f1574ea930746b8d796
|
7
|
+
data.tar.gz: c9c78b2399fae7a00185441af62a75568afc3a732ca46889e9314234aabc520f62aa9bbc89c84ba8f4af40d4e64d56d5152b96f48994b6a60d53a06bb9a5de7f
|
data/README.md
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
<div align="center">
|
2
|
-
|
2
|
+
<h1>👀 Lookbook 👀</h1>
|
3
3
|
|
4
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
77
|
+
### Annotating preview files
|
78
78
|
|
79
|
-
Lookbook
|
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
|
-
|
81
|
+
```ruby
|
82
|
+
# @label Basic Button
|
83
|
+
class ButtonComponentPreview < ViewComponent::Preview
|
82
84
|
|
83
|
-
|
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
|
-
|
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
|
-
|
88
|
-
#
|
89
|
-
#
|
90
|
-
|
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
|
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
|
-
|
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
|
-
|
143
|
+
The following Lookbook-specific tags are available for use:
|
101
144
|
|
102
|
-
|
145
|
+
#### `@label <text>`
|
103
146
|
|
104
|
-
|
147
|
+
Used to replace the auto-generated navigation label for the item with `<text>`.
|
105
148
|
|
106
|
-
|
149
|
+
> Available for preview classes & example methods.
|
107
150
|
|
108
151
|
```ruby
|
109
|
-
|
152
|
+
# @label Preview Label
|
153
|
+
class FooComponentPreview < ViewComponent::Preview
|
110
154
|
|
111
|
-
#
|
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
|
-
|
161
|
+
#### `@hidden`
|
128
162
|
|
129
|
-
|
163
|
+
Used to temporarily exclude an item from the Lookbook navigation. The item will still be accessible via it's URL.
|
130
164
|
|
131
|
-
|
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
|
-
|
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
|
-
# @
|
139
|
-
class
|
170
|
+
# @hidden
|
171
|
+
class FooComponentPreview < ViewComponent::Preview
|
140
172
|
|
141
|
-
# @
|
142
|
-
def
|
173
|
+
# @hidden
|
174
|
+
def default
|
143
175
|
end
|
144
176
|
end
|
145
177
|
```
|
146
178
|
|
147
|
-
|
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
|
-
|
185
|
+
```ruby
|
186
|
+
class HeaderComponentPreview < ViewComponent::Preview
|
150
187
|
|
151
|
-
|
188
|
+
def standard
|
189
|
+
render Elements::HeaderComponent.new do
|
190
|
+
"Standard header"
|
191
|
+
end
|
192
|
+
end
|
152
193
|
|
153
|
-
|
194
|
+
# @!group Sizes
|
154
195
|
|
155
|
-
|
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
|
-
|
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
|
-
|
167
|
-
class MyComponentPreview < ViewComponent::Preview
|
221
|
+
<img src=".github/assets/nav_group.png">
|
168
222
|
|
169
|
-
|
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
|
-
|
179
|
-
# ...
|
180
|
-
end
|
225
|
+
#### Adding notes
|
181
226
|
|
182
|
-
|
183
|
-
|
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
|
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
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import
|
8
|
-
import
|
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(
|
14
|
-
Alpine.plugin(
|
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("
|
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
|
-
|
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,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
|
+
}
|
File without changes
|
@@ -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
|
+
}
|