lookbook 0.4.5 → 0.5.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -4
  3. data/app/assets/lookbook/css/app.css +63 -5
  4. data/app/assets/lookbook/css/tooltip_theme.css +28 -0
  5. data/app/assets/lookbook/js/app.js +40 -54
  6. data/app/assets/lookbook/js/components/copy.js +16 -0
  7. data/app/assets/lookbook/js/components/filter.js +24 -0
  8. data/app/assets/lookbook/js/components/inspector.js +58 -0
  9. data/app/assets/lookbook/js/{nav/node.js → components/nav-group.js} +17 -16
  10. data/app/assets/lookbook/js/components/nav-item.js +27 -0
  11. data/app/assets/lookbook/js/components/nav.js +35 -0
  12. data/app/assets/lookbook/js/components/page.js +45 -0
  13. data/app/assets/lookbook/js/components/param.js +32 -0
  14. data/app/assets/lookbook/js/components/preview-window.js +107 -0
  15. data/app/assets/lookbook/js/components/sidebar.js +3 -0
  16. data/app/assets/lookbook/js/components/sizes.js +16 -0
  17. data/app/assets/lookbook/js/components/splitter.js +25 -0
  18. data/app/assets/lookbook/js/components/tabs.js +50 -0
  19. data/app/assets/lookbook/js/config.js +20 -0
  20. data/app/assets/lookbook/js/{utils/reloader.js → lib/socket.js} +7 -12
  21. data/app/assets/lookbook/js/lib/split.js +21 -0
  22. data/app/assets/lookbook/js/lib/utils.js +3 -0
  23. data/app/assets/lookbook/js/stores/filter.js +11 -0
  24. data/app/assets/lookbook/js/stores/inspector.js +24 -0
  25. data/app/assets/lookbook/js/stores/layout.js +12 -0
  26. data/app/assets/lookbook/js/stores/nav.js +21 -0
  27. data/app/assets/lookbook/js/stores/sidebar.js +14 -0
  28. data/app/controllers/lookbook/app_controller.rb +71 -96
  29. data/app/helpers/lookbook/application_helper.rb +48 -4
  30. data/app/views/layouts/lookbook/app.html.erb +58 -0
  31. data/app/views/layouts/lookbook/preview.html.erb +12 -0
  32. data/app/views/lookbook/components/_code.html.erb +8 -0
  33. data/app/views/lookbook/components/_copy.html.erb +14 -0
  34. data/app/views/lookbook/components/_drawer.html.erb +121 -0
  35. data/app/views/lookbook/components/_filter.html.erb +15 -0
  36. data/app/views/lookbook/{shared → components}/_header.html.erb +3 -3
  37. data/app/views/lookbook/components/_icon.html.erb +5 -0
  38. data/app/views/lookbook/components/_nav.html.erb +17 -0
  39. data/app/views/lookbook/components/_nav_collection.html.erb +5 -0
  40. data/app/views/lookbook/components/_nav_group.html.erb +14 -0
  41. data/app/views/lookbook/components/_nav_item.html.erb +23 -0
  42. data/app/views/lookbook/components/_nav_preview.html.erb +11 -0
  43. data/app/views/lookbook/components/_param.html.erb +24 -0
  44. data/app/views/lookbook/components/_preview.html.erb +52 -0
  45. data/app/views/lookbook/{app/error.html.erb → error.html.erb} +0 -0
  46. data/app/views/lookbook/index.html.erb +9 -0
  47. data/app/views/lookbook/{workbench/inspector/params → inputs}/_select.html.erb +2 -2
  48. data/app/views/lookbook/{workbench/inspector/params → inputs}/_text.html.erb +3 -2
  49. data/app/views/lookbook/inputs/_textarea.html.erb +9 -0
  50. data/app/views/lookbook/{workbench/inspector/params → inputs}/_toggle.html.erb +5 -5
  51. data/app/views/lookbook/{app/not_found.html.erb → not_found.html.erb} +2 -4
  52. data/app/views/lookbook/panels/_notes.html.erb +25 -0
  53. data/app/views/lookbook/panels/_output.html.erb +18 -0
  54. data/app/views/lookbook/panels/_params.html.erb +17 -0
  55. data/app/views/lookbook/panels/_source.html.erb +20 -0
  56. data/app/views/lookbook/show.html.erb +73 -0
  57. data/lib/lookbook/code_formatter.rb +20 -0
  58. data/lib/lookbook/engine.rb +8 -1
  59. data/lib/lookbook/lang.rb +10 -5
  60. data/lib/lookbook/preview.rb +1 -1
  61. data/lib/lookbook/preview_controller.rb +1 -1
  62. data/lib/lookbook/preview_group.rb +5 -1
  63. data/lib/lookbook/version.rb +1 -1
  64. data/lib/lookbook.rb +2 -0
  65. data/public/lookbook-assets/css/app.css +4 -0
  66. data/public/lookbook-assets/css/app.css.map +1 -0
  67. data/public/lookbook-assets/js/app.js +2 -0
  68. data/public/lookbook-assets/js/app.js.map +1 -0
  69. metadata +58 -45
  70. data/app/assets/lookbook/js/nav/leaf.js +0 -20
  71. data/app/assets/lookbook/js/nav.js +0 -38
  72. data/app/assets/lookbook/js/page.js +0 -38
  73. data/app/assets/lookbook/js/utils/clipboard.js +0 -13
  74. data/app/assets/lookbook/js/utils/morph.js +0 -19
  75. data/app/assets/lookbook/js/utils/screen.js +0 -44
  76. data/app/assets/lookbook/js/utils/size_observer.js +0 -16
  77. data/app/assets/lookbook/js/utils/split.js +0 -26
  78. data/app/assets/lookbook/js/workbench/inspector.js +0 -11
  79. data/app/assets/lookbook/js/workbench/param.js +0 -19
  80. data/app/assets/lookbook/js/workbench/preview.js +0 -39
  81. data/app/assets/lookbook/js/workbench.js +0 -14
  82. data/app/views/lookbook/app/index.html.erb +0 -11
  83. data/app/views/lookbook/app/show.html.erb +0 -1
  84. data/app/views/lookbook/layouts/app.html.erb +0 -41
  85. data/app/views/lookbook/nav/_collection.html.erb +0 -5
  86. data/app/views/lookbook/nav/_leaf.html.erb +0 -22
  87. data/app/views/lookbook/nav/_node.html.erb +0 -19
  88. data/app/views/lookbook/nav/_preview.html.erb +0 -11
  89. data/app/views/lookbook/preview/group.html.erb +0 -8
  90. data/app/views/lookbook/shared/_clipboard.html.erb +0 -11
  91. data/app/views/lookbook/shared/_sidebar.html.erb +0 -45
  92. data/app/views/lookbook/shared/_workbench.html.erb +0 -12
  93. data/app/views/lookbook/workbench/_header.html.erb +0 -39
  94. data/app/views/lookbook/workbench/_inspector.html.erb +0 -38
  95. data/app/views/lookbook/workbench/_preview.html.erb +0 -24
  96. data/app/views/lookbook/workbench/inspector/_code.html.erb +0 -3
  97. data/app/views/lookbook/workbench/inspector/_notes.html.erb +0 -24
  98. data/app/views/lookbook/workbench/inspector/_params.html.erb +0 -28
  99. data/app/views/lookbook/workbench/inspector/_plain.html.erb +0 -3
  100. data/app/views/lookbook/workbench/inspector/params/_textarea.html.erb +0 -8
  101. data/public/lookbook-assets/app.css +0 -2626
  102. data/public/lookbook-assets/app.js +0 -8718
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 676b8a78237532971ec7d22d7265b3da7d3b896aa8020d7ab1c514edc3859b45
4
- data.tar.gz: dfb016bf927d7492afdeacd3b10a2425563e227b5cbd4ebd4d3f7a5bfa97609f
3
+ metadata.gz: 95624cc7d6d6e9389e87c3b13c6f4980f0be8ae2f13ac81757a9ed7acbaf554d
4
+ data.tar.gz: e8e8c6787b96e23fc3d71bc3b47c4a9253ef8a607f8f41c292c52e2ecc99549d
5
5
  SHA512:
6
- metadata.gz: 2e5aeb26c2db3322e7aa127e176b691744d5af3cfbd207159f65d0e9dee1362382f418b07bda2fa7b75d7f85b35f522efe106707e665c6e12f9ab5e4ca1f4cd1
7
- data.tar.gz: be2908ed45f9645dd9387adf6547c8c45ba262310325438197de22d6db9909705fa3e45de0912914004d516c4546618ccc73fd96fea3fbdc86f2affa5e7ebb3d
6
+ metadata.gz: 5ac7b680d183349e374b1a2368b2839fa9c9ec8790f53cbf76a97171c0da7375cf6599d2e4936e2fcb1a0dca685aae9d37cfd54672e68ef0e935086af43b60bc
7
+ data.tar.gz: 03bee3f6b73191fc5bcfd5afed389fee54b3ec41ce31827ddedf647dc17f68740db1314f6f4a0c78d7dfe4960851d9cce12e23e6598970b74d8ec428f258db88
data/README.md CHANGED
@@ -34,7 +34,9 @@ Lookbook uses [RDoc/Yard-style comment tags](#annotating-preview-files) to exten
34
34
 
35
35
  If you want to have a quick play with Lookbook, the easiest way is to [give the demo app](https://github.com/allmarkedup/lookbook-demo) a spin. It's a basic Rails/ViewComponent app with a few test components included to tinker with.
36
36
 
37
- The [demo app repo](https://github.com/allmarkedup/lookbook-demo) contains instructions on how to get it up and running.
37
+ **Online demo: https://lookbook-demo-app.herokuapp.com/lookbook**
38
+
39
+ If you'd rather dig in a bit more and run the demo app locally, the [demo repo](https://github.com/allmarkedup/lookbook-demo) contains instructions on how to get it up and running.
38
40
 
39
41
  ## Installing
40
42
 
@@ -77,7 +79,7 @@ Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/do
77
79
 
78
80
  ```ruby
79
81
  # @label Basic Button
80
- # @display bg_color #fff
82
+ # @display bg_color "#fff"
81
83
  class ButtonComponentPreview < ViewComponent::Preview
82
84
 
83
85
  # Primary button
@@ -108,7 +110,7 @@ class ButtonComponentPreview < ViewComponent::Preview
108
110
  # ---------------
109
111
  # For light-on-dark screens
110
112
  #
111
- # @display bg_color #000
113
+ # @display bg_color "#000"
112
114
  def secondary
113
115
  render ButtonComponent.new(style: :inverted) do
114
116
  "Click me"
@@ -186,7 +188,7 @@ end
186
188
  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.
187
189
 
188
190
  ```ruby
189
- # @display bg_color #eee
191
+ # @display bg_color "#eee"
190
192
  class FooComponentPreview < ViewComponent::Preview
191
193
 
192
194
  # @display max_width 500px
@@ -205,6 +207,8 @@ The `@display` tag can be applied at the preview (class) or at the example (meth
205
207
  - `<key>` must be a valid Ruby hash key name, without quotes or spaces
206
208
  - `<value>` will be parsed using the [Ruby YAML parser](https://yaml.org/YAML_for_ruby.html) to resolve the value
207
209
 
210
+ > Note: Ruby YAML does not (generally) require quoting of string values. However in some cases it _is_ required due to the presence of [indicator characters](https://yaml.org/YAML_for_ruby.html#indicators_in_strings) (such as `#`, `:` etc) - hence why the hex color code in the example above is surrounded by quotes. It's perfectly ok to quote all string values if you prefer.
211
+
208
212
  These display parameters can then be accessed via the `params` hash in your preview layout using `params[:lookbook][:display][<key>]`:
209
213
 
210
214
  ```html
@@ -2,6 +2,7 @@
2
2
  @import "tailwindcss/components";
3
3
  @import "tailwindcss/utilities";
4
4
  @import "tippy.js/dist/tippy";
5
+ @import "tippy.js/dist/border";
5
6
  @import "code_theme";
6
7
  @import "tooltip_theme";
7
8
 
@@ -56,16 +57,73 @@
56
57
  }
57
58
  }
58
59
 
59
- @layer utilities {
60
- .h-fill {
61
- height: fill-available;
60
+ @layer components {
61
+ #nav > ul > li > div {
62
+ @apply py-1 border-b border-gray-300;
63
+ }
64
+
65
+ .nav-toggle {
66
+ @apply flex items-center cursor-pointer pr-3 hover:bg-gray-200 hover:bg-opacity-50;
67
+ }
68
+
69
+ .nav-label {
70
+ @apply truncate w-full whitespace-nowrap text-left select-none;
71
+ }
72
+
73
+ .code {
74
+ @apply font-mono;
75
+ }
76
+
77
+ .code pre {
78
+ @apply block;
79
+ /* @apply whitespace-pre-wrap; */
80
+ }
81
+
82
+ .code .line {
83
+ @apply leading-relaxed;
84
+ }
85
+
86
+ .code.numbered {
87
+ @apply relative pt-3;
62
88
  }
63
89
 
64
- .min-h-fill {
65
- min-height: fill-available;
90
+ .code.numbered:before {
91
+ content: "";
92
+ left: 2.7em;
93
+ @apply absolute top-0 bottom-0 border-r border-gray-200;
66
94
  }
67
95
 
96
+ .code.numbered .line {
97
+ padding-left: calc(2.7em + 8px);
98
+ @apply relative;
99
+ }
100
+
101
+ .code .line-number {
102
+ display: inline-block;
103
+ width: calc(2.7em + 8px);
104
+ padding-top: 3px;
105
+ padding-bottom: 3px;
106
+ padding-right: 8px;
107
+ margin-right: 16px;
108
+ @apply font-mono text-right text-gray-400 flex-none text-xs absolute left-0;
109
+ }
110
+
111
+ .code .line-content {
112
+ @apply flex-none pr-4;
113
+ }
114
+
115
+ .resize-handle {
116
+ @apply flex items-center justify-center h-full w-full border-gray-300 bg-white hover:bg-indigo-100 hover:bg-opacity-20 text-gray-400 hover:text-gray-700 transition select-none touch-none;
117
+ }
118
+ }
119
+
120
+ @layer utilities {
68
121
  .form-input {
69
122
  @apply border-gray-300 text-gray-700 focus:ring-indigo-300 focus:border-indigo-300 rounded-sm text-sm w-full;
70
123
  }
124
+
125
+ .checked-bg {
126
+ background-color: #ffffff;
127
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cg fill='%23f3f3f3' fill-opacity='1'%3E%3Cpath fill-rule='evenodd' d='M0 0h4v4H0V0zm4 4h4v4H4V4z'/%3E%3C/g%3E%3C/svg%3E");
128
+ }
71
129
  }
@@ -4,13 +4,41 @@
4
4
  &[data-placement^="top"] > .tippy-arrow::before {
5
5
  border-top-color: theme("colors.indigo.500");
6
6
  }
7
+
7
8
  &[data-placement^="bottom"] > .tippy-arrow::before {
8
9
  border-bottom-color: theme("colors.indigo.500");
9
10
  }
11
+
10
12
  &[data-placement^="left"] > .tippy-arrow::before {
11
13
  border-left-color: theme("colors.indigo.500");
12
14
  }
15
+
13
16
  &[data-placement^="right"] > .tippy-arrow::before {
14
17
  border-right-color: theme("colors.indigo.500");
15
18
  }
16
19
  }
20
+
21
+ .tippy-box[data-theme~="menu"] {
22
+ border: 1px solid theme("colors.gray.300");
23
+ @apply bg-white text-gray-600 shadow-md rounded;
24
+
25
+ & > .tippy-content {
26
+ padding: 0;
27
+ }
28
+
29
+ &[data-placement^="top"] > .tippy-arrow::before {
30
+ border-top-color: white;
31
+ }
32
+
33
+ &[data-placement^="bottom"] > .tippy-arrow::before {
34
+ border-bottom-color: white;
35
+ }
36
+
37
+ &[data-placement^="left"] > .tippy-arrow::before {
38
+ border-left-color: white;
39
+ }
40
+
41
+ &[data-placement^="right"] > .tippy-arrow::before {
42
+ border-right-color: white;
43
+ }
44
+ }
@@ -1,66 +1,56 @@
1
1
  import { install } from "@github/hotkey";
2
2
  import Alpine from "alpinejs";
3
- import Fern from "@ryangjchandler/fern";
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 param from "./workbench/param";
13
- import nav from "./nav";
14
- import navNode from "./nav/node";
15
- import navLeaf from "./nav/leaf";
16
- import sizeObserver from "./utils/size_observer";
17
- import reloader from "./utils/reloader";
18
- import clipboard from "./utils/clipboard";
19
-
20
- window.Alpine = Alpine;
3
+ import Persist from "@alpinejs/persist";
4
+ import Morph from "@alpinejs/morph";
5
+ import Tooltip from "@ryangjchandler/alpine-tooltip";
6
+
7
+ import page from "./components/page";
8
+ import inspector from "./components/inspector";
9
+ import previewWindow from "./components/preview-window";
10
+ import filter from "./components/filter";
11
+ import param from "./components/param";
12
+ import nav from "./components/nav";
13
+ import navItem from "./components/nav-item";
14
+ import navGroup from "./components/nav-group";
15
+ import splitter from "./components/splitter";
16
+ import tabs from "./components/tabs";
17
+ import copy from "./components/copy";
18
+ import sizes from "./components/sizes";
19
+
20
+ import initFilterStore from "./stores/filter";
21
+ import initLayoutStore from "./stores/layout";
22
+ import initNavStore from "./stores/nav";
23
+ import initSidebarStore from "./stores/sidebar";
24
+ import initInspectorStore from "./stores/inspector";
21
25
 
22
26
  // Plugins
23
27
 
24
- Alpine.plugin(Fern);
25
- Alpine.plugin(AlpineTooltip);
26
- Alpine.plugin(AlpineClipboard);
27
- Alpine.plugin(Screen);
28
+ Alpine.plugin(Persist);
29
+ Alpine.plugin(Morph);
30
+ Alpine.plugin(Tooltip);
28
31
 
29
32
  // Stores
30
33
 
31
- Alpine.store("page", {
32
- reflowing: false,
33
- doc: window.document,
34
- });
35
-
36
- Alpine.persistedStore("nav", {
37
- width: 280,
38
- filter: "",
39
- open: {},
40
- });
41
-
42
- Alpine.persistedStore("inspector", {
43
- height: 200,
44
- active: "source",
45
- });
46
-
47
- Alpine.persistedStore("preview", {
48
- width: "100%",
49
- });
34
+ Alpine.store("filter", initFilterStore(Alpine));
35
+ Alpine.store("layout", initLayoutStore(Alpine));
36
+ Alpine.store("nav", initNavStore(Alpine));
37
+ Alpine.store("sidebar", initSidebarStore(Alpine));
38
+ Alpine.store("inspector", initInspectorStore(Alpine));
50
39
 
51
- // Components & utils
40
+ // Components
52
41
 
53
42
  Alpine.data("page", page);
54
- Alpine.data("nav", nav);
55
- Alpine.data("navNode", navNode);
56
- Alpine.data("navLeaf", navLeaf);
57
- Alpine.data("workbench", workbench);
58
- Alpine.data("preview", preview);
43
+ Alpine.data("splitter", splitter);
44
+ Alpine.data("previewWindow", previewWindow);
45
+ Alpine.data("copy", copy);
59
46
  Alpine.data("inspector", inspector);
47
+ Alpine.data("filter", filter);
60
48
  Alpine.data("param", param);
61
- Alpine.data("clipboard", clipboard);
62
- Alpine.data("sizeObserver", sizeObserver);
63
- Alpine.data("split", split);
49
+ Alpine.data("sizes", sizes);
50
+ Alpine.data("nav", nav);
51
+ Alpine.data("tabs", tabs);
52
+ Alpine.data("navItem", navItem);
53
+ Alpine.data("navGroup", navGroup);
64
54
 
65
55
  // Init
66
56
 
@@ -68,9 +58,5 @@ for (const el of document.querySelectorAll("[data-hotkey]")) {
68
58
  install(el);
69
59
  }
70
60
 
71
- if (window.SOCKET_PATH) {
72
- reloader(window.SOCKET_PATH).start();
73
- }
74
-
75
61
  window.Alpine = Alpine;
76
62
  Alpine.start();
@@ -0,0 +1,16 @@
1
+ export default function copy(id) {
2
+ return {
3
+ get content() {
4
+ const target = document.getElementById(id);
5
+ return (target ? target.innerHTML : "").trim();
6
+ },
7
+ done: false,
8
+ async save() {
9
+ await window.navigator.clipboard.writeText(this.content);
10
+ this.done = true;
11
+ setTimeout(() => {
12
+ this.done = false;
13
+ }, 1000);
14
+ },
15
+ };
16
+ }
@@ -0,0 +1,24 @@
1
+ export default function filter() {
2
+ return {
3
+ get active() {
4
+ return this.$store.filter.active;
5
+ },
6
+ checkEsc($event) {
7
+ if ($event.key === "Escape") {
8
+ this.active ? this.clear() : this.blur();
9
+ }
10
+ },
11
+ clear() {
12
+ this.$store.filter.raw = "";
13
+ },
14
+ focus($event) {
15
+ if ($event && $event.target.tagName === "INPUT") {
16
+ return;
17
+ }
18
+ setTimeout(() => this.$refs.input.focus(), 0);
19
+ },
20
+ blur() {
21
+ setTimeout(() => this.$refs.input.blur(), 0);
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,58 @@
1
+ import sizeObserver from "./sizes";
2
+
3
+ export default function inspector() {
4
+ return {
5
+ width: 0,
6
+ height: 0,
7
+ init() {
8
+ const ro = new ResizeObserver((entries) => {
9
+ const rect = entries[0].contentRect;
10
+ this.width = Math.round(rect.width);
11
+ this.height = Math.round(rect.height);
12
+ });
13
+ ro.observe(this.$el);
14
+ this.width = Math.round(this.$el.clientWidth);
15
+ this.height = Math.round(this.$el.clientHeight);
16
+ },
17
+ get orientation() {
18
+ return this.$store.inspector.drawer.orientation;
19
+ },
20
+ get view() {
21
+ return this.$store.inspector.preview.view;
22
+ },
23
+ get horizontal() {
24
+ return this.canBeVertical ? this.orientation === "horizontal" : true;
25
+ },
26
+ get vertical() {
27
+ return !this.horizontal;
28
+ },
29
+ get canBeVertical() {
30
+ return this.width > 800;
31
+ },
32
+ get drawerHidden() {
33
+ return this.$store.inspector.drawer.hidden;
34
+ },
35
+ isActivePanel(panel) {
36
+ return this.$store.inspector.drawer.active == panel;
37
+ },
38
+ switchPanel(panel) {
39
+ this.$store.inspector.drawer.active = panel;
40
+ },
41
+ toggleView() {
42
+ this.$store.inspector.preview.view =
43
+ this.view === "html" ? "preview" : "html";
44
+ },
45
+ toggleOrientation() {
46
+ this.$store.inspector.drawer.orientation =
47
+ this.orientation === "horizontal" ? "vertical" : "horizontal";
48
+ },
49
+ toggleDrawer() {
50
+ this.$store.inspector.drawer.hidden =
51
+ !this.$store.inspector.drawer.hidden;
52
+ },
53
+ preview: {
54
+ width: null,
55
+ height: null,
56
+ },
57
+ };
58
+ }
@@ -1,43 +1,44 @@
1
- export default function navNode() {
1
+ import { getAlpineData } from "../lib/utils";
2
+
3
+ export default function navGroup() {
2
4
  return {
3
- id: null,
4
- hidden: true,
5
+ hidden: false,
5
6
  children: [],
6
- init() {
7
- this.id = this.$el.id;
7
+ get id() {
8
+ return this.$root.id;
8
9
  },
9
- open() {
10
- return this.$store.nav.open[this.id];
10
+ get open() {
11
+ return this.$store.nav.isOpen(this.id);
12
+ },
13
+ toggle() {
14
+ this.$store.nav.toggle(this.id);
11
15
  },
12
16
  getChildren() {
13
17
  return this.$refs.items
14
- ? Array.from(this.$refs.items.querySelectorAll(":scope > li"))
18
+ ? Array.from(this.$refs.items.querySelectorAll(":scope > li > div"))
15
19
  : [];
16
20
  },
17
21
  navigateToFirstChild() {
18
- if (this.open()) {
22
+ if (this.open) {
19
23
  const child = this.firstVisibleChild();
20
24
  if (child) {
21
25
  const link = child.querySelector(":scope > a.nav-link");
22
26
  if (link) {
23
- this.navigate(link.getAttribute("href"));
27
+ this.setLocation(link.getAttribute("href"));
24
28
  }
25
29
  }
26
30
  }
27
31
  },
28
- filter() {
32
+ filter(text) {
29
33
  this.hidden = true;
30
34
  this.getChildren().forEach((child) => {
31
- const data = child._x_dataStack[0];
32
- data.filter();
35
+ const data = getAlpineData(child);
36
+ data.filter(text);
33
37
  if (!data.hidden) {
34
38
  this.hidden = false;
35
39
  }
36
40
  });
37
41
  },
38
- toggle() {
39
- this.$store.nav.open[this.id] = !this.$store.nav.open[this.id];
40
- },
41
42
  firstVisibleChild() {
42
43
  return this.getChildren().find((child) => {
43
44
  return child._x_dataStack
@@ -0,0 +1,27 @@
1
+ export default function navItem(matchers) {
2
+ return {
3
+ hidden: false,
4
+ get id() {
5
+ return this.$root.id;
6
+ },
7
+ get path() {
8
+ return this.$root.getAttribute("data-path");
9
+ },
10
+ get active() {
11
+ return this.$store.nav.active === this.id;
12
+ },
13
+ navigate() {
14
+ this.setLocation(this.path);
15
+ this.$store.sidebar.open = false;
16
+ },
17
+ filter(text) {
18
+ this.hidden = false;
19
+ if (text.length) {
20
+ const matched = matchers.map((m) => m.includes(text));
21
+ this.hidden = !matched.filter((m) => m).length;
22
+ } else {
23
+ this.hidden = false;
24
+ }
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,35 @@
1
+ import { getAlpineData } from "../lib/utils";
2
+
3
+ export default function nav() {
4
+ return {
5
+ empty: false,
6
+ init() {
7
+ this.$watch("$store.filter.text", () => this.filter());
8
+ this.$nextTick(() => {
9
+ this.setActive();
10
+ this.filter();
11
+ });
12
+ },
13
+ filter() {
14
+ this.empty = true;
15
+ this.getChildren().forEach((child) => {
16
+ const data = getAlpineData(child);
17
+ data.filter(this.$store.filter.text);
18
+ if (!data.hidden) {
19
+ this.empty = false;
20
+ }
21
+ });
22
+ },
23
+ getChildren() {
24
+ return this.$refs.items
25
+ ? Array.from(this.$refs.items.querySelectorAll(":scope > li > div"))
26
+ : [];
27
+ },
28
+ setActive() {
29
+ const target = this.$el.querySelector(
30
+ `[data-path="${window.location.pathname}"]`
31
+ );
32
+ this.$store.nav.active = target ? target.id : "";
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,45 @@
1
+ import createSocket from "../lib/socket";
2
+
3
+ const morphOpts = {
4
+ key(el) {
5
+ return el.getAttribute("key") ? el.getAttribute("key") : el.id;
6
+ },
7
+ updating(el, toEl, childrenOnly, skip) {
8
+ if (
9
+ el.getAttribute &&
10
+ el.getAttribute("data-morph-strategy") === "replace"
11
+ ) {
12
+ el.innerHTML = toEl.innerHTML;
13
+ return skip();
14
+ }
15
+ },
16
+ lookahead: true,
17
+ };
18
+
19
+ export default function page() {
20
+ return {
21
+ init() {
22
+ const socket = createSocket(window.SOCKET_PATH);
23
+ socket.addListener("Lookbook::ReloadChannel", () => this.refresh());
24
+ },
25
+ async update() {
26
+ const response = await fetch(window.document.location);
27
+ if (!response.ok) return window.location.reload();
28
+ const html = await response.text();
29
+ this.morph(new DOMParser().parseFromString(html, "text/html"));
30
+ },
31
+ setLocation(loc) {
32
+ const path = loc instanceof Event ? loc.currentTarget.href : loc;
33
+ history.pushState({}, null, path);
34
+ this.$dispatch("popstate");
35
+ },
36
+ refresh() {
37
+ this.$dispatch("popstate");
38
+ },
39
+ morph(dom) {
40
+ const pageHtml = dom.getElementById(this.$root.id).outerHTML;
41
+ Alpine.morph(this.$root, pageHtml, morphOpts);
42
+ this.$dispatch("page:morphed");
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,32 @@
1
+ import debounce from "debounce";
2
+
3
+ export default function param(name, value) {
4
+ return {
5
+ name,
6
+ value,
7
+ init() {
8
+ this.$watch(
9
+ "value",
10
+ debounce(() => {
11
+ if (this.validate()) {
12
+ this.update();
13
+ }
14
+ }, 300)
15
+ );
16
+ },
17
+ setFocus() {
18
+ if (this.$refs.input) {
19
+ setTimeout(() => this.$refs.input.focus(), 0);
20
+ }
21
+ },
22
+ update() {
23
+ const searchParams = new URLSearchParams(window.location.search);
24
+ searchParams.set(this.name, this.value);
25
+ const path = location.href.replace(location.search, "");
26
+ this.setLocation(`${path}?${searchParams.toString()}`);
27
+ },
28
+ validate() {
29
+ return this.$el.reportValidity ? this.$el.reportValidity() : true;
30
+ },
31
+ };
32
+ }