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.
- checksums.yaml +4 -4
- data/README.md +233 -5
- data/app/assets/lookbook/css/app.css +15 -3
- data/app/assets/lookbook/js/app.js +8 -0
- data/app/assets/lookbook/js/components/app.js +55 -0
- data/app/assets/lookbook/js/components/embed.js +89 -0
- data/app/assets/lookbook/js/components/filter.js +13 -2
- data/app/assets/lookbook/js/components/nav-group.js +3 -6
- data/app/assets/lookbook/js/components/nav-item.js +3 -1
- data/app/assets/lookbook/js/components/nav.js +8 -15
- data/app/assets/lookbook/js/components/page.js +19 -41
- data/app/assets/lookbook/js/components/sidebar.js +16 -1
- data/app/assets/lookbook/js/components/splitter.js +1 -1
- data/app/assets/lookbook/js/components/tabs.js +3 -1
- data/app/assets/lookbook/js/embed.js +1 -0
- data/app/assets/lookbook/js/lib/split.js +0 -6
- data/app/assets/lookbook/js/stores/layout.js +3 -0
- data/app/assets/lookbook/js/stores/pages.js +5 -0
- data/app/assets/lookbook/js/stores/sidebar.js +2 -1
- data/app/controllers/lookbook/application_controller.rb +28 -0
- data/app/controllers/lookbook/page_controller.rb +20 -0
- data/app/controllers/lookbook/pages_controller.rb +40 -0
- data/app/controllers/lookbook/{app_controller.rb → previews_controller.rb} +39 -67
- data/app/helpers/lookbook/application_helper.rb +8 -61
- data/app/helpers/lookbook/component_helper.rb +40 -0
- data/app/helpers/lookbook/output_helper.rb +15 -0
- data/app/helpers/lookbook/page_helper.rb +46 -0
- data/app/helpers/lookbook/preview_helper.rb +1 -1
- data/app/views/layouts/lookbook/application.html.erb +30 -0
- data/app/views/layouts/lookbook/basic.html.erb +7 -0
- data/app/views/layouts/lookbook/preview.html.erb +5 -1
- data/app/views/layouts/lookbook/skeleton.html.erb +28 -0
- data/app/views/lookbook/components/_branding.html.erb +8 -0
- data/app/views/lookbook/components/_code.html.erb +2 -2
- data/app/views/lookbook/components/_copy_button.html.erb +11 -0
- data/app/views/lookbook/components/_drawer.html.erb +4 -16
- data/app/views/lookbook/components/_embed.html.erb +39 -0
- data/app/views/lookbook/components/_filter.html.erb +8 -5
- data/app/views/lookbook/components/_header.html.erb +2 -4
- data/app/views/lookbook/components/_icon.html.erb +2 -2
- data/app/views/lookbook/components/_nav.html.erb +7 -8
- data/app/views/lookbook/components/_nav_group.html.erb +1 -1
- data/app/views/lookbook/components/_nav_item.html.erb +4 -3
- data/app/views/lookbook/components/_nav_page.html.erb +22 -0
- data/app/views/lookbook/components/_nav_preview.html.erb +1 -1
- data/app/views/lookbook/components/_not_found.html.erb +11 -0
- data/app/views/lookbook/components/_param.html.erb +1 -1
- data/app/views/lookbook/components/_preview.html.erb +16 -10
- data/app/views/lookbook/components/_sidebar.html.erb +55 -0
- data/app/views/lookbook/pages/not_found.html.erb +15 -0
- data/app/views/lookbook/pages/show.html.erb +71 -0
- data/app/views/lookbook/previews/error.html.erb +1 -0
- data/app/views/lookbook/{inputs → previews/inputs}/_select.html.erb +0 -0
- data/app/views/lookbook/{inputs → previews/inputs}/_text.html.erb +0 -0
- data/app/views/lookbook/{inputs → previews/inputs}/_textarea.html.erb +0 -0
- data/app/views/lookbook/{inputs → previews/inputs}/_toggle.html.erb +0 -0
- data/app/views/lookbook/{not_found.html.erb → previews/not_found.html.erb} +2 -2
- data/app/views/lookbook/{panels → previews/panels}/_notes.html.erb +2 -2
- data/app/views/lookbook/{panels → previews/panels}/_output.html.erb +0 -0
- data/app/views/lookbook/{panels → previews/panels}/_params.html.erb +0 -0
- data/app/views/lookbook/{panels → previews/panels}/_preview.html.erb +0 -0
- data/app/views/lookbook/{panels → previews/panels}/_source.html.erb +0 -0
- data/app/views/lookbook/{show.html.erb → previews/show.html.erb} +0 -0
- data/config/routes.rb +9 -4
- data/lib/lookbook/code_formatter.rb +22 -1
- data/lib/lookbook/code_inspector.rb +73 -0
- data/lib/lookbook/collection.rb +110 -8
- data/lib/lookbook/engine.rb +59 -32
- data/lib/lookbook/features.rb +1 -1
- data/lib/lookbook/markdown.rb +31 -0
- data/lib/lookbook/page.rb +146 -0
- data/lib/lookbook/page_collection.rb +11 -0
- data/lib/lookbook/parser.rb +2 -0
- data/lib/lookbook/preview.rb +52 -56
- data/lib/lookbook/preview_collection.rb +15 -0
- data/lib/lookbook/preview_example.rb +27 -29
- data/lib/lookbook/preview_group.rb +12 -6
- data/lib/lookbook/utils.rb +74 -0
- data/lib/lookbook/version.rb +1 -1
- data/lib/lookbook.rb +18 -1
- data/public/lookbook-assets/css/app.css +1 -1
- data/public/lookbook-assets/css/app.css.map +1 -1
- data/public/lookbook-assets/js/app.js +1 -1
- data/public/lookbook-assets/js/app.js.map +1 -1
- data/public/lookbook-assets/js/embed.js +2 -0
- data/public/lookbook-assets/js/embed.js.map +1 -0
- metadata +44 -18
- data/app/assets/lookbook/js/lib/utils.js +0 -3
- data/app/views/layouts/lookbook/app.html.erb +0 -60
- data/app/views/lookbook/error.html.erb +0 -1
- data/lib/lookbook/taggable.rb +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40ec6640c4c188089ff874cb11fb129ddc6c541d078681b1b3dea133f3afaedf
|
|
4
|
+
data.tar.gz: 331f4693438b931929b40d58c4b7c0d0cf0ece9089891d8e5863c8971f5e4138
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
541
|
+
#### Frontmatter defaults
|
|
480
542
|
|
|
481
|
-
|
|
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
|
|
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 +
|
|
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(() =>
|
|
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(() =>
|
|
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 =
|
|
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
|
-
|
|
45
|
-
|
|
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.
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
export default function nav() {
|
|
1
|
+
export default function nav(filterable = true) {
|
|
4
2
|
return {
|
|
5
3
|
empty: false,
|
|
6
4
|
init() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this
|
|
10
|
-
|
|
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 =
|
|
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
|
}
|