lookbook 0.6.1 → 0.7.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +233 -5
  3. data/app/assets/lookbook/css/app.css +15 -3
  4. data/app/assets/lookbook/js/app.js +8 -0
  5. data/app/assets/lookbook/js/components/app.js +55 -0
  6. data/app/assets/lookbook/js/components/embed.js +89 -0
  7. data/app/assets/lookbook/js/components/filter.js +13 -2
  8. data/app/assets/lookbook/js/components/nav-group.js +3 -6
  9. data/app/assets/lookbook/js/components/nav-item.js +3 -1
  10. data/app/assets/lookbook/js/components/nav.js +8 -15
  11. data/app/assets/lookbook/js/components/page.js +19 -41
  12. data/app/assets/lookbook/js/components/sidebar.js +16 -1
  13. data/app/assets/lookbook/js/components/splitter.js +1 -1
  14. data/app/assets/lookbook/js/components/tabs.js +3 -1
  15. data/app/assets/lookbook/js/embed.js +1 -0
  16. data/app/assets/lookbook/js/lib/split.js +0 -6
  17. data/app/assets/lookbook/js/stores/layout.js +3 -0
  18. data/app/assets/lookbook/js/stores/pages.js +5 -0
  19. data/app/assets/lookbook/js/stores/sidebar.js +2 -1
  20. data/app/controllers/lookbook/application_controller.rb +28 -0
  21. data/app/controllers/lookbook/page_controller.rb +20 -0
  22. data/app/controllers/lookbook/pages_controller.rb +40 -0
  23. data/app/controllers/lookbook/{app_controller.rb → previews_controller.rb} +39 -67
  24. data/app/helpers/lookbook/application_helper.rb +8 -61
  25. data/app/helpers/lookbook/component_helper.rb +40 -0
  26. data/app/helpers/lookbook/output_helper.rb +15 -0
  27. data/app/helpers/lookbook/page_helper.rb +46 -0
  28. data/app/helpers/lookbook/preview_helper.rb +1 -1
  29. data/app/views/layouts/lookbook/application.html.erb +30 -0
  30. data/app/views/layouts/lookbook/basic.html.erb +7 -0
  31. data/app/views/layouts/lookbook/preview.html.erb +5 -1
  32. data/app/views/layouts/lookbook/skeleton.html.erb +28 -0
  33. data/app/views/lookbook/components/_branding.html.erb +8 -0
  34. data/app/views/lookbook/components/_code.html.erb +2 -2
  35. data/app/views/lookbook/components/_copy_button.html.erb +11 -0
  36. data/app/views/lookbook/components/_drawer.html.erb +4 -16
  37. data/app/views/lookbook/components/_embed.html.erb +39 -0
  38. data/app/views/lookbook/components/_filter.html.erb +8 -5
  39. data/app/views/lookbook/components/_header.html.erb +2 -4
  40. data/app/views/lookbook/components/_icon.html.erb +2 -2
  41. data/app/views/lookbook/components/_nav.html.erb +7 -8
  42. data/app/views/lookbook/components/_nav_group.html.erb +1 -1
  43. data/app/views/lookbook/components/_nav_item.html.erb +4 -3
  44. data/app/views/lookbook/components/_nav_page.html.erb +22 -0
  45. data/app/views/lookbook/components/_nav_preview.html.erb +1 -1
  46. data/app/views/lookbook/components/_not_found.html.erb +11 -0
  47. data/app/views/lookbook/components/_param.html.erb +1 -1
  48. data/app/views/lookbook/components/_preview.html.erb +16 -10
  49. data/app/views/lookbook/components/_sidebar.html.erb +55 -0
  50. data/app/views/lookbook/pages/not_found.html.erb +15 -0
  51. data/app/views/lookbook/pages/show.html.erb +71 -0
  52. data/app/views/lookbook/previews/error.html.erb +1 -0
  53. data/app/views/lookbook/{inputs → previews/inputs}/_select.html.erb +0 -0
  54. data/app/views/lookbook/{inputs → previews/inputs}/_text.html.erb +0 -0
  55. data/app/views/lookbook/{inputs → previews/inputs}/_textarea.html.erb +0 -0
  56. data/app/views/lookbook/{inputs → previews/inputs}/_toggle.html.erb +0 -0
  57. data/app/views/lookbook/{not_found.html.erb → previews/not_found.html.erb} +2 -2
  58. data/app/views/lookbook/{panels → previews/panels}/_notes.html.erb +2 -2
  59. data/app/views/lookbook/{panels → previews/panels}/_output.html.erb +0 -0
  60. data/app/views/lookbook/{panels → previews/panels}/_params.html.erb +0 -0
  61. data/app/views/lookbook/{panels → previews/panels}/_preview.html.erb +0 -0
  62. data/app/views/lookbook/{panels → previews/panels}/_source.html.erb +0 -0
  63. data/app/views/lookbook/{show.html.erb → previews/show.html.erb} +0 -0
  64. data/config/routes.rb +9 -4
  65. data/lib/lookbook/code_formatter.rb +22 -1
  66. data/lib/lookbook/code_inspector.rb +73 -0
  67. data/lib/lookbook/collection.rb +110 -8
  68. data/lib/lookbook/engine.rb +59 -32
  69. data/lib/lookbook/features.rb +1 -1
  70. data/lib/lookbook/markdown.rb +31 -0
  71. data/lib/lookbook/page.rb +146 -0
  72. data/lib/lookbook/page_collection.rb +11 -0
  73. data/lib/lookbook/parser.rb +2 -0
  74. data/lib/lookbook/preview.rb +52 -56
  75. data/lib/lookbook/preview_collection.rb +15 -0
  76. data/lib/lookbook/preview_example.rb +27 -29
  77. data/lib/lookbook/preview_group.rb +12 -6
  78. data/lib/lookbook/utils.rb +74 -0
  79. data/lib/lookbook/version.rb +1 -1
  80. data/lib/lookbook.rb +18 -1
  81. data/public/lookbook-assets/css/app.css +1 -1
  82. data/public/lookbook-assets/css/app.css.map +1 -1
  83. data/public/lookbook-assets/js/app.js +1 -1
  84. data/public/lookbook-assets/js/app.js.map +1 -1
  85. data/public/lookbook-assets/js/embed.js +2 -0
  86. data/public/lookbook-assets/js/embed.js.map +1 -0
  87. metadata +44 -18
  88. data/app/assets/lookbook/js/lib/utils.js +0 -3
  89. data/app/views/layouts/lookbook/app.html.erb +0 -60
  90. data/app/views/lookbook/error.html.erb +0 -1
  91. data/lib/lookbook/taggable.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca448bf7c2f60a27c0cec712165fb3d6eb230739c93e9cee559ebd81a5dcd2ad
4
- data.tar.gz: 4ef5134c7853c526ced67e56568250575934f6b25d4e558f21275e13e968e07b
3
+ metadata.gz: 40ec6640c4c188089ff874cb11fb129ddc6c541d078681b1b3dea133f3afaedf
4
+ data.tar.gz: 331f4693438b931929b40d58c4b7c0d0cf0ece9089891d8e5863c8971f5e4138
5
5
  SHA512:
6
- metadata.gz: 75cda1bd9921636c6a76998f4228f6193ad5d8a3da10e2c7fef3434a52ffef937e40b5032245155ff47a340c8976e926db4e1d6fa267606bc923d82f4d7fadb7
7
- data.tar.gz: 2e719c65802e23e4e4dfe8308eb01208181454cc76fd043de97916537421016c8b20ef8f7cc2e944c48f04c46ffc31eda50024760425774e89d1544cf4bd5570
6
+ metadata.gz: a538f52d85d4d5e833c2549442b001e44e749b42e87a5bbe4676e718300405b7b9b32d6efd18b1ce23dedcfefce8c82a4f4128236fa83dcff570b7f609b15004
7
+ data.tar.gz: a5a6fe4dbba3e951441b5c4fb64b6a268de687c99bb7779892d5e819eeae19fbcee8d740c749808671bf65202fc6af581a958869f3fb67c6f9aac5c5af364007
data/README.md CHANGED
@@ -12,6 +12,12 @@
12
12
 
13
13
  ---
14
14
 
15
+ <div align="center">
16
+ <a href="#installing">Installing</a> • <a href="#previews">Previews</a> • <a href="#pages">Pages</a> • <a href="#config">Configuration</a>
17
+ </div>
18
+
19
+ ---
20
+
15
21
  **Lookbook gives [ViewComponent](http://viewcomponent.org/)-based projects a _ready-to-go_ development UI for navigating, inspecting and interacting with component previews.**
16
22
 
17
23
  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.
@@ -29,6 +35,7 @@ Lookbook uses [RDoc/Yard-style comment tags](#annotating-preview-files) to exten
29
35
  - Use comment tag annotations for granular customisation of the preview experience
30
36
  - Fully compatible with standard the ViewComponent preview system
31
37
  - In-browser live-editable preview parameters (similar to basic Storybook Controls/Knobs)
38
+ - [**Experimental**] Markdown-powerered documentation pages with embeddable previews
32
39
 
33
40
  ## Lookbook demo
34
41
 
@@ -73,13 +80,13 @@ If you would like to expose the Lookbook UI in production as well as in developm
73
80
  2. Add `config.view_component.show_previews = true` to `config/environments/production.rb`
74
81
 
75
82
 
76
- ## Usage
83
+ <h2 id="previews">Previews</h2>
77
84
 
78
85
  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.
79
86
 
80
87
  > 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).
81
88
 
82
- ## Annotating preview files
89
+ ### Annotating preview files
83
90
 
84
91
  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:
85
92
 
@@ -474,11 +481,217 @@ class ProfileCardComponentPreview < ViewComponent::Preview
474
481
  end
475
482
  ```
476
483
 
477
- ## Configuration
484
+ ---
485
+
486
+ <h2 id="pages">🚧 Documentation Pages [experimental]</h2>
487
+
488
+ If you need to add more long-form documentation to live alongside your component previews you can do so using Lookbook's markdown-powered `pages` system.
489
+
490
+ > ⚠️ This feature is currently flagged as an **experimental** feature which requires [feature opt-in](#experimental-features) to use. Its API and implementation may change before it is released.
491
+ >
492
+ > To enable support for pages in your project, add `config.lookbook.experimental_features = [:pages]` into your application configuration file.
493
+
494
+ ### Pages demo
495
+
496
+ For an example of some pages in Lookbook, check out the [example pages](https://lookbook-demo-app.herokuapp.com/lookbook) in the Lookbook demo app and the associated [page files](https://github.com/allmarkedup/lookbook-demo/tree/main/test/components/docs) in the demo repo.
497
+
498
+ ### Usage
499
+
500
+ By default, pages should be placed in the `test/components/docs` directory (although this can be customised) and can be nested in directories as deeply as required.
501
+
502
+ Pages must have either a `.html.erb` or a `.md.erb` file extension. All pages are rendered as ERB templates but `.md.erb` files will also additionally be run through a markdown parser.
503
+
504
+ Pages can optionally make use of a **YAML frontmatter block** to customise the behaviour and content of the page itself.
505
+
506
+ An example page might look like this:
507
+
508
+ ```markdown
509
+ ---
510
+ title: An example page
511
+ label: Nice example
512
+ ---
513
+
514
+ This is an example page. If it has a `.md.erb` file extension its
515
+ contents will be run through a Markdown parser/renderer before display.
516
+
517
+ Fenced code blocks are fully supported and will be highlighted appropriately.
518
+
519
+ ERB can be used in here.
520
+ The template will be rendered **before** being parsed as Markdown.
521
+
522
+ You can can access data about the page using the `@page` variable.
523
+ The title of this page is "<%= @page.title %>".
524
+ ```
525
+
526
+ ### YAML Frontmatter
527
+
528
+ Pages can use an optional YAML frontmatter block to configure the behaviour of the page and to provide custom data, if required.
529
+
530
+ The following page options can be customised via frontmatter:
531
+
532
+ * `id` - a custom page ID that can be used for linking to it from other pages
533
+ * `label` - The name of the page that will be displayed in the navigation (auto-generated from the file name if not set)
534
+ * `title` - The main page title displayed on the page (defaults to the label value if not set).
535
+ * `hidden` - If `false` the page will not appear in the navigation but will still be accessible at it's URL (useful for pages that are still in development) [default: `true`]
536
+ * `landing` - Set to `true` to use the page as the Lookbook landing page [default: `false`]
537
+ * `header` - Set to `false` to hide the page header containing the page title [default: `true`]
538
+ * `footer` - Set to `false` to hide the page footer containing the previous/next page links [default: `true`]
539
+ * `data` - Optional hash of custom data to make available for use in the page - see info on [page variables](#page-variables) below. [default: `{}`]
478
540
 
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.
541
+ #### Frontmatter defaults
480
542
 
481
- However the following Lookbook-specific config options are also available:
543
+ You can set global default values for page options in the application configuration:
544
+
545
+ ```ruby
546
+ # config/application.rb
547
+ config.lookbook.page_options = {
548
+ footer: false,
549
+ data: {
550
+ brand_colors: {
551
+ red: #ff0000
552
+ }
553
+ }
554
+ }
555
+ ```
556
+
557
+ These will be merged with any page-level frontmatter data. Options set in the frontmatter will override those set at the global level (apart from `data`, which will be deep-merged with the any globally defined data).
558
+
559
+ ### Page variables
560
+
561
+ All pages have the following variables available for use in the page template:
562
+
563
+ * `@page` - The current page object
564
+ * `@next_page` - The next page object (if available)
565
+ * `@previous_page` - The previous page object (if available)
566
+ * `@pages` - Collection of all pages
567
+
568
+ Page objects have access to frontmatter variables:
569
+
570
+ ```ruby
571
+ The page title is <%= @page.title %>
572
+
573
+ Our brand color hex value is <%= @page.data[:brand_colors][:red] %>
574
+ ```
575
+
576
+ ### Ordering pages and directories
577
+
578
+ If you want to enforce a specific order for pages and directories in the Lookbook navigation you can prefix the file/directory basename with an 'order number' integer value followed by an underscore or hyphen.
579
+
580
+ For example: `01_example_page.md.erb` will be displayed first in the navigation (`01`) within the directory it is in.
581
+
582
+ The integer value will be parsed out from the filename so that it doesn't appear in navigation labels or URLs, and the value itself will be used as a 'position' number when sorting the navigation items.
583
+
584
+ For example, an ordered directory of pages might look like:
585
+
586
+ ```
587
+ test/components/docs/
588
+ ├── 01_overview.md.erb
589
+ ├── 02_implementation_notes/
590
+ │ ├── 01_slots.md.erb
591
+ │ └── 02_html_attributes.md.erb
592
+ └── 03_helpful_examples/
593
+ ├── 01_basic_components.md.erb
594
+ └── 02_complex_components.md.erb
595
+ ```
596
+
597
+ Without the number prefixes on the file names the pages may not have appeared in the navigation in the desired order.
598
+
599
+ ### Linking to other pages
600
+
601
+ You can get the path to a page using the `page_path` helper. This accepts a page `id` (as a `Symbol`) or a page object:
602
+
603
+ ```markdown
604
+ Visit the [about page](<%= page_path :about %>)
605
+
606
+ Go to the [next page](<%= page_path @next_page %>)
607
+ ```
608
+
609
+ Page ids can be set in the YAML frontmatter block for that page:
610
+
611
+ ```
612
+ ---
613
+ id: about
614
+ ---
615
+
616
+ This is the about page.
617
+ ```
618
+
619
+ ### Embedding previews
620
+
621
+ You can embed preview examples from your project directly into the documentation pages using the `embed` helper, which renders an iframe with the rendered preview in it at any point in your document.
622
+
623
+ The output looks like this:
624
+
625
+ <img src=".github/assets/preview_embed.png">
626
+
627
+ To specify which preview example to render, the helper accepts a preview class and a method name (as a symbol), like this:
628
+
629
+ ```erb
630
+ <%= embed Elements:ButtonComponentPreview, :default %>
631
+ ```
632
+
633
+ #### Preview params
634
+
635
+ If you have configured your examples to accept preview params (see the [`@param`](#param-tag) docs), then you can supply values for those params when rendering the embedded preview:
636
+
637
+ ```erb
638
+ <%= embed Elements:ButtonComponentPreview, :default, params: {
639
+ icon: "plus",
640
+ text: "Add new"
641
+ } %>
642
+ ```
643
+
644
+ ### Displaying code
645
+
646
+ You can use language-scoped [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) in the markdown file to render nicely highlighted code examples.
647
+
648
+ However, if you are not using Markdown, or need a little more control, you can use the `code` helper instead:
649
+
650
+ ```erb
651
+ <%= code do %>
652
+ # code goes here
653
+ <% end %>
654
+ ```
655
+
656
+ The default language is `ruby`. To highlight a different language you need to specify it's name as an argument:
657
+
658
+ ```erb
659
+ <%= code :html do %>
660
+ <!-- code goes here -->
661
+ <% end %>
662
+ ```
663
+
664
+ > Lookbook uses [Rouge](https://github.com/rouge-ruby/rouge) for syntax highlighting. You can find a [full list of supported languages here](https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md).
665
+
666
+ ---
667
+
668
+ ### Pages configuration
669
+
670
+ These options can be set in your application configuration files to customise the pages behaviour.
671
+
672
+ #### `page_paths`
673
+
674
+ An array of directories to look for pages in.
675
+ Default: `["test/previews/docs"]`
676
+
677
+ ```ruby
678
+ config.lookbook.page_paths = ["path/to/my/pages"]
679
+ ```
680
+
681
+ #### `page_route`
682
+
683
+ The URL segment used to prefix page routes.
684
+ Default: `pages`
685
+
686
+ ```ruby
687
+ config.lookbook.page_route = `docs`
688
+ ```
689
+
690
+ <h2 id="config">General Configuration</h2>
691
+
692
+ 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 much else separately.
693
+
694
+ However the following Lookbook-specific configuration options are also available:
482
695
 
483
696
  ### UI auto-refresh
484
697
 
@@ -508,6 +721,16 @@ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
508
721
 
509
722
  > To disable the favicon entirely, set the value to `false`.
510
723
 
724
+ ### Project name
725
+
726
+ Specify a project name to display in the top left of the UI (instead of the default "Lookbook"):
727
+
728
+ ```ruby
729
+ config.lookbook.project_name = "My Project"
730
+ ```
731
+
732
+ > If you don't want to display a project name at all, set the value to `false`.
733
+
511
734
 
512
735
  <h3 id="experimental-features">Experimental features opt-in</h3>
513
736
 
@@ -523,6 +746,11 @@ To opt into individual experimental features, include the name of the feature in
523
746
  config.lookbook.experimental_features = ["feature_name"]
524
747
  ```
525
748
 
749
+ The current experimental features that can be opted into are:
750
+
751
+ - `pages`: Markdown-powered documentation pages with embeddable previews
752
+
753
+
526
754
  #### Opting into all experimental features (not recommended!)
527
755
 
528
756
  If you want to live life on the bleeding-edge you can opt-in to all current **and future** experimental features (usual caveats apply):
@@ -58,7 +58,7 @@
58
58
  }
59
59
 
60
60
  @layer components {
61
- #nav > ul > li > div {
61
+ .unsectioned > #nav > ul > li > div {
62
62
  @apply py-1 border-b border-gray-300;
63
63
  }
64
64
 
@@ -87,7 +87,7 @@
87
87
  }
88
88
 
89
89
  .code.numbered {
90
- @apply relative pt-3;
90
+ @apply relative;
91
91
  }
92
92
 
93
93
  .code.numbered:before {
@@ -97,7 +97,7 @@
97
97
  }
98
98
 
99
99
  .code.numbered .line {
100
- padding-left: calc(2.7em + 8px);
100
+ padding-left: calc(2.7em + 14px);
101
101
  @apply relative;
102
102
  }
103
103
 
@@ -115,6 +115,18 @@
115
115
  @apply flex-none pr-4;
116
116
  }
117
117
 
118
+ .prose .code {
119
+ @apply !bg-white border border-gray-300 text-gray-600 my-8 text-sm rounded-md py-4 overflow-auto max-w-3xl w-full mx-auto;
120
+ }
121
+
122
+ .prose .code:not(.numbered) .line {
123
+ @apply px-4;
124
+ }
125
+
126
+ .prose .embed {
127
+ @apply my-8;
128
+ }
129
+
118
130
  .resize-handle {
119
131
  @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;
120
132
  }
@@ -4,11 +4,13 @@ import Persist from "@alpinejs/persist";
4
4
  import Morph from "@alpinejs/morph";
5
5
  import Tooltip from "@ryangjchandler/alpine-tooltip";
6
6
 
7
+ import app from "./components/app";
7
8
  import page from "./components/page";
8
9
  import inspector from "./components/inspector";
9
10
  import previewWindow from "./components/preview-window";
10
11
  import filter from "./components/filter";
11
12
  import param from "./components/param";
13
+ import sidebar from "./components/sidebar";
12
14
  import nav from "./components/nav";
13
15
  import navItem from "./components/nav-item";
14
16
  import navGroup from "./components/nav-group";
@@ -17,12 +19,14 @@ import tabs from "./components/tabs";
17
19
  import copy from "./components/copy";
18
20
  import code from "./components/code";
19
21
  import sizes from "./components/sizes";
22
+ import embed from "./components/embed";
20
23
 
21
24
  import initFilterStore from "./stores/filter";
22
25
  import initLayoutStore from "./stores/layout";
23
26
  import initNavStore from "./stores/nav";
24
27
  import initSidebarStore from "./stores/sidebar";
25
28
  import initInspectorStore from "./stores/inspector";
29
+ import initPagesStore from "./stores/pages";
26
30
 
27
31
  // Plugins
28
32
 
@@ -37,10 +41,13 @@ Alpine.store("layout", initLayoutStore(Alpine));
37
41
  Alpine.store("nav", initNavStore(Alpine));
38
42
  Alpine.store("sidebar", initSidebarStore(Alpine));
39
43
  Alpine.store("inspector", initInspectorStore(Alpine));
44
+ Alpine.store("pages", initPagesStore(Alpine));
40
45
 
41
46
  // Components
42
47
 
48
+ Alpine.data("app", app);
43
49
  Alpine.data("page", page);
50
+ Alpine.data("sidebar", sidebar);
44
51
  Alpine.data("splitter", splitter);
45
52
  Alpine.data("previewWindow", previewWindow);
46
53
  Alpine.data("copy", copy);
@@ -53,6 +60,7 @@ Alpine.data("nav", nav);
53
60
  Alpine.data("tabs", tabs);
54
61
  Alpine.data("navItem", navItem);
55
62
  Alpine.data("navGroup", navGroup);
63
+ Alpine.data("embed", embed);
56
64
 
57
65
  // Init
58
66
 
@@ -0,0 +1,55 @@
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
+ lookahead: false,
8
+ updating(el, toEl, childrenOnly, skip) {
9
+ if (
10
+ el.getAttribute &&
11
+ el.getAttribute("data-morph-strategy") === "replace"
12
+ ) {
13
+ el.innerHTML = toEl.innerHTML;
14
+ return skip();
15
+ }
16
+ },
17
+ };
18
+
19
+ export default function app() {
20
+ return {
21
+ init() {
22
+ if (window.SOCKET_PATH) {
23
+ const socket = createSocket(window.SOCKET_PATH);
24
+ socket.addListener("Lookbook::ReloadChannel", () => this.refresh());
25
+ }
26
+ },
27
+ async update() {
28
+ const response = await fetch(window.document.location);
29
+ if (!response.ok) return window.location.reload();
30
+ const html = await response.text();
31
+ const newDoc = new DOMParser().parseFromString(html, "text/html");
32
+ this.morph(newDoc);
33
+ document.title = newDoc.title;
34
+ },
35
+ setLocation(loc) {
36
+ let path;
37
+ if (loc instanceof Event) {
38
+ path = loc.currentTarget.href;
39
+ loc.preventDefault();
40
+ } else {
41
+ path = loc;
42
+ }
43
+ history.pushState({}, null, path);
44
+ this.$dispatch("popstate");
45
+ },
46
+ refresh() {
47
+ this.$dispatch("refresh");
48
+ },
49
+ morph(dom) {
50
+ const pageHtml = dom.getElementById(this.$root.id).outerHTML;
51
+ Alpine.morph(this.$root, pageHtml, morphOpts);
52
+ this.$dispatch("page:morphed");
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,89 @@
1
+ import "iframe-resizer/js/iframeResizer";
2
+
3
+ export default function embed() {
4
+ return {
5
+ init() {
6
+ if (!this.$store.pages.embeds[this.$root.id]) {
7
+ this.$store.pages.embeds[this.$root.id] = {
8
+ width: "100%",
9
+ };
10
+ }
11
+ },
12
+ lastWidth: null,
13
+ reflowing: false,
14
+ get resizer() {
15
+ if (this.$refs.iframe) {
16
+ if (!this.$refs.iframe.iFrameResizer) {
17
+ window.iFrameResize(
18
+ {
19
+ heightCalculationMethod: "lowestElement",
20
+ onResized: this.onIframeResized.bind(this),
21
+ },
22
+ this.$refs.iframe
23
+ );
24
+ }
25
+ return this.$refs.iframe.iFrameResizer;
26
+ }
27
+ return null;
28
+ },
29
+ set width(value) {
30
+ this.store.width = value;
31
+ },
32
+ get width() {
33
+ return this.store.width || "100%";
34
+ },
35
+ get height() {
36
+ return this.store.height;
37
+ },
38
+ get parentWidth() {
39
+ return Math.round(this.$root.parentElement.clientWidth);
40
+ },
41
+ get maxWidth() {
42
+ return this.width === "100%" ? "100%" : `${this.width}px`;
43
+ },
44
+ get store() {
45
+ return this.$store.pages.embeds[this.$root.id];
46
+ },
47
+ recaclulateIframeHeight() {
48
+ if (this.resizer) this.resizer.resize();
49
+ },
50
+ onIframeResized({ iframe, height }) {
51
+ if (iframe.isSameNode(this.$refs.iframe)) {
52
+ this.store.height = height;
53
+ }
54
+ },
55
+ onResizeWidth(e) {
56
+ const width =
57
+ this.resizeStartWidth - (this.resizeStartPositionX - e.pageX);
58
+ const boundedWidth = Math.min(
59
+ Math.max(Math.round(width), 200),
60
+ this.parentWidth
61
+ );
62
+ this.width = boundedWidth === this.parentWidth ? "100%" : boundedWidth;
63
+ this.recaclulateIframeHeight();
64
+ },
65
+ onResizeWidthStart(e) {
66
+ this.reflowing = true;
67
+ this.onResizeWidth = this.onResizeWidth.bind(this);
68
+ this.onResizeWidthEnd = this.onResizeWidthEnd.bind(this);
69
+ this.resizeStartPositionX = e.pageX;
70
+ this.resizeStartWidth = this.$refs.resizer.clientWidth;
71
+ window.addEventListener("pointermove", this.onResizeWidth);
72
+ window.addEventListener("pointerup", this.onResizeWidthEnd);
73
+ },
74
+ onResizeWidthEnd() {
75
+ window.removeEventListener("pointermove", this.onResizeWidth);
76
+ window.removeEventListener("pointerup", this.onResizeWidthEnd);
77
+ this.reflowing = false;
78
+ },
79
+ toggleFullWidth() {
80
+ if (this.width === "100%" && this.lastWidth) {
81
+ this.width = this.lastWidth;
82
+ } else {
83
+ this.lastWidth = this.width;
84
+ this.width = "100%";
85
+ }
86
+ this.$nextTick(() => this.recaclulateIframeHeight());
87
+ },
88
+ };
89
+ }
@@ -3,6 +3,7 @@ export default function filter() {
3
3
  get active() {
4
4
  return this.$store.filter.active;
5
5
  },
6
+ focussed: false,
6
7
  checkEsc($event) {
7
8
  if ($event.key === "Escape") {
8
9
  this.active ? this.clear() : this.blur();
@@ -15,10 +16,20 @@ export default function filter() {
15
16
  if ($event && $event.target.tagName === "INPUT") {
16
17
  return;
17
18
  }
18
- setTimeout(() => this.$refs.input.focus(), 0);
19
+ setTimeout(() => {
20
+ this.$dispatch("filter:focus");
21
+ this.$nextTick(() => {
22
+ this.focussed = true;
23
+ this.$refs.input.focus();
24
+ });
25
+ }, 0);
19
26
  },
20
27
  blur() {
21
- setTimeout(() => this.$refs.input.blur(), 0);
28
+ setTimeout(() => {
29
+ this.focussed = false;
30
+ this.$refs.input.blur();
31
+ this.$dispatch("filter:blur");
32
+ }, 0);
22
33
  },
23
34
  };
24
35
  }
@@ -1,5 +1,3 @@
1
- import { getAlpineData } from "../lib/utils";
2
-
3
1
  export default function navGroup() {
4
2
  return {
5
3
  hidden: false,
@@ -32,7 +30,7 @@ export default function navGroup() {
32
30
  filter(text) {
33
31
  this.hidden = true;
34
32
  this.getChildren().forEach((child) => {
35
- const data = getAlpineData(child);
33
+ const data = Alpine.$data(child);
36
34
  data.filter(text);
37
35
  if (!data.hidden) {
38
36
  this.hidden = false;
@@ -41,9 +39,8 @@ export default function navGroup() {
41
39
  },
42
40
  firstVisibleChild() {
43
41
  return this.getChildren().find((child) => {
44
- return child._x_dataStack
45
- ? child._x_dataStack[0].hidden === false
46
- : false;
42
+ const data = Alpine.$data(child);
43
+ return data.hidden === false;
47
44
  });
48
45
  },
49
46
  };
@@ -12,7 +12,9 @@ export default function navItem(matchers) {
12
12
  },
13
13
  navigate() {
14
14
  this.setLocation(this.path);
15
- this.$store.sidebar.open = false;
15
+ if (this.$store.layout.mobile) {
16
+ this.$store.sidebar.open = false;
17
+ }
16
18
  },
17
19
  filter(text) {
18
20
  this.hidden = false;
@@ -1,19 +1,18 @@
1
- import { getAlpineData } from "../lib/utils";
2
-
3
- export default function nav() {
1
+ export default function nav(filterable = true) {
4
2
  return {
5
3
  empty: false,
6
4
  init() {
7
- this.$watch("$store.filter.text", () => this.filter());
8
- this.$nextTick(() => {
9
- this.setActive();
10
- this.filter();
11
- });
5
+ if (filterable) {
6
+ this.$watch("$store.filter.text", () => this.filter());
7
+ this.$nextTick(() => {
8
+ this.filter();
9
+ });
10
+ }
12
11
  },
13
12
  filter() {
14
13
  this.empty = true;
15
14
  this.getChildren().forEach((child) => {
16
- const data = getAlpineData(child);
15
+ const data = Alpine.$data(child);
17
16
  data.filter(this.$store.filter.text);
18
17
  if (!data.hidden) {
19
18
  this.empty = false;
@@ -25,11 +24,5 @@ export default function nav() {
25
24
  ? Array.from(this.$refs.items.querySelectorAll(":scope > li > div"))
26
25
  : [];
27
26
  },
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
27
  };
35
28
  }