lostcoords-ui 0.8.2 → 0.9.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 +104 -66
- data/app/assets/tailwind/lostcoords/ui/components/shell.css +4 -0
- data/app/assets/tailwind/lostcoords/ui/components.css +1 -0
- data/app/assets/tailwind/lostcoords/ui/core.css +1 -1
- data/app/assets/tailwind/lostcoords/ui/prose.css +9 -0
- data/app/assets/tailwind/lostcoords/ui/shell.css +161 -0
- data/app/assets/tailwind/lostcoords/ui/typography.css +26 -0
- data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +3 -3
- data/app/helpers/lostcoords/ui/layout_helper.rb +5 -3
- data/app/helpers/lostcoords/ui/navigation_helper.rb +29 -0
- data/app/helpers/lostcoords/ui/reading_helper.rb +23 -2
- data/app/helpers/lostcoords/ui/shell_helper.rb +64 -0
- data/app/helpers/lostcoords/ui/translation_helper.rb +1 -5
- data/app/helpers/lostcoords/ui/typography_helper.rb +9 -2
- data/app/javascript/lostcoords/ui/controllers/lc_app_header_controller.js +15 -0
- data/app/javascript/lostcoords/ui/controllers/lc_dock_controller.js +21 -0
- data/app/javascript/lostcoords/ui/controllers/lc_toc_controller.js +96 -0
- data/app/javascript/lostcoords/ui/index.js +6 -0
- data/config/locales/en.yml +9 -0
- data/config/locales/es.yml +9 -0
- data/config/locales/ja.yml +9 -0
- data/lib/lostcoords/ui/helpers.rb +1 -1
- data/lib/lostcoords/ui/icons.rb +1 -1
- data/lib/lostcoords/ui/parts.rb +87 -0
- data/lib/lostcoords/ui/version.rb +1 -1
- data/lib/lostcoords/ui.rb +12 -2
- data/lib/tasks/lostcoords_ui.rake +0 -1
- metadata +9 -6
- data/lib/lostcoords/ui/action_view.rb +0 -13
- data/lib/lostcoords/ui/rails.rb +0 -3
- data/lib/lostcoords/ui/resources.rb +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c7fa09422f0fbfb2b1e2caa70338f1c842d12d900097efe8d9577c610041e46
|
|
4
|
+
data.tar.gz: 1b366575265b9d3662710ec2131fe7bfe64e7b2afcce0662d222189643aa1fbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27d701fa2951f3a60871a013ef2618a6dfb09ee9f932bf93b91b3aaae85371c349345e10fbfdfb92c6645e3b24769b2d8c4c55dd144389a05ee4a7cf41f69116
|
|
7
|
+
data.tar.gz: a1ca746f2c919e114d2a0dbf5995704cacc6389eec9b545c9b26bf3055dd1b8803d03a8adb689dda3463dac5cea925aa81934f61e441d24ff943366167100490
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# lostcoords-ui
|
|
2
2
|
|
|
3
|
-
Render complete UI components from Rails
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Render complete UI components from Rails views. Helpers include semantic HTML, accessible attributes and
|
|
4
|
+
Stimulus wiring. Tailwind v4 supplies the styles. The Rails engine integrates Propshaft, importmap and
|
|
5
|
+
`tailwindcss-rails`.
|
|
6
6
|
|
|
7
7
|
```erb
|
|
8
8
|
<%= lc_button "Save changes", type: :submit, icon: "check" %>
|
|
@@ -15,52 +15,33 @@ and importmap automatically; standalone applications own their asset build.
|
|
|
15
15
|
<% end %>
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
The supported stack is
|
|
19
|
-
its gem version, so publishing a library update does not upgrade consumers.
|
|
18
|
+
The supported stack is Rails 8 or later with ERB, Tailwind and Stimulus. Each host application's
|
|
19
|
+
lockfile pins its gem version, so publishing a library update does not upgrade consumers.
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
23
23
|
```ruby
|
|
24
|
-
gem "lostcoords-ui", "~> 0.
|
|
24
|
+
gem "lostcoords-ui", "~> 0.9.0"
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The engine adds its rendering helpers to host views, its
|
|
28
|
-
the importmap. Translations live under
|
|
27
|
+
The engine adds its rendering helpers to host views, its views and translations to the host's lookup
|
|
28
|
+
paths, its JavaScript to the asset paths and its pins to the importmap. Translations live under
|
|
29
|
+
`lostcoords.ui.*`; a host locale file overrides any of them.
|
|
29
30
|
|
|
30
|
-
###
|
|
31
|
+
### Stylesheets
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class ViewContext < ActionView::Base
|
|
36
|
-
include Lostcoords::Ui::Helpers
|
|
37
|
-
end
|
|
33
|
+
Import the engine's stylesheet after your Tailwind imports. `tailwindcss-rails` generates this path from
|
|
34
|
+
the installed gem on every build:
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
```css
|
|
37
|
+
/* app/assets/tailwind/application.css */
|
|
38
|
+
@import "../builds/tailwind/lostcoords_ui.css";
|
|
41
39
|
```
|
|
42
40
|
|
|
43
|
-
For an existing view context, include the helpers and append `Lostcoords::Ui.view_paths` to its
|
|
44
|
-
lookup paths after application paths. The loader registers the library's translations without changing
|
|
45
|
-
the default locale. If the host has its own locale handling, override private `lc_locale` in the view
|
|
46
|
-
context; components do not call the host's `t` helper.
|
|
47
|
-
|
|
48
|
-
Standalone applications compile Tailwind and register Stimulus controllers through their own asset
|
|
49
|
-
pipeline. `Lostcoords::Ui.root` locates the gem's source assets. Rails applications receive automatic
|
|
50
|
-
registration; use `require "lostcoords/ui/rails"` explicitly if Rails loads after the components.
|
|
51
|
-
|
|
52
|
-
### Standalone assets
|
|
53
|
-
|
|
54
|
-
Import `app/assets/tailwind/lostcoords/ui/components.css` from the Bundler-resolved gem root
|
|
55
|
-
into your Tailwind build. This entry includes component rules, default tokens and source scanning.
|
|
56
|
-
It does not set body styles, heading defaults, global inline-code styles or document corner radii.
|
|
57
|
-
Map the library's tokens to your app's theme and keep your Tailwind theme declarations after the import.
|
|
58
|
-
The full `ui.css` entry includes those document defaults and remains the Rails default.
|
|
59
|
-
|
|
60
41
|
Tailwind always ships component rules, used or not, and generates every utility the scanned helpers
|
|
61
42
|
mention. For a smaller build, import `core.css` and the opt-in components you render instead of
|
|
62
|
-
everything. Each entry carries the rules and scans the sources of its own component only.
|
|
63
|
-
|
|
43
|
+
everything. Each entry carries the rules and scans the sources of its own component only. Every
|
|
44
|
+
Tailwind build writes stable paths for them next to the engine's own import:
|
|
64
45
|
|
|
65
46
|
```css
|
|
66
47
|
/* app/assets/tailwind/application.css, instead of ../builds/tailwind/lostcoords_ui.css */
|
|
@@ -70,34 +51,17 @@ every Tailwind build writes stable paths for them next to the engine's own impor
|
|
|
70
51
|
@import "../builds/tailwind/lostcoords_ui/components/menu.css";
|
|
71
52
|
```
|
|
72
53
|
|
|
73
|
-
|
|
74
|
-
|
|
54
|
+
`base.css` holds the document defaults: body styles, heading defaults, global inline-code styles and
|
|
55
|
+
document corner radii. Skip it when the host owns its base styles, map the library's tokens to your
|
|
56
|
+
app's theme and keep your Tailwind theme declarations after the imports.
|
|
75
57
|
`core.css` holds the tokens, buttons, badges, avatars, cards, alerts, form controls, choices,
|
|
76
58
|
toggles, layout, typography and navigation. The opt-in entries are `dialog` (with the phone sheet),
|
|
77
59
|
`menu` (menus and popovers), `toast`, `search`, `tooltip`, `collapsible`, `code-block`,
|
|
78
|
-
`emoji-picker`, `table` and `
|
|
79
|
-
|
|
80
|
-
Start one Stimulus application in the host, then register the controllers you use:
|
|
81
|
-
|
|
82
|
-
```javascript
|
|
83
|
-
import { Application } from "@hotwired/stimulus"
|
|
84
|
-
import { registerControllers } from "lostcoords/ui"
|
|
85
|
-
|
|
86
|
-
const application = Application.start()
|
|
87
|
-
registerControllers(application, { only: ["lc-copy", "lc-collapsible"] })
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Resolve `lostcoords/ui/*` to the gem's `app/javascript/lostcoords/ui/*` and `@hotwired/stimulus` to
|
|
91
|
-
the host's installed package. `registerControllers` imports every controller, so a bundle carries
|
|
92
|
-
them all; to bundle only some, import them directly:
|
|
93
|
-
`import Dialog from "lostcoords/ui/controllers/lc_dialog_controller"` and
|
|
94
|
-
`application.register("lc-dialog", Dialog)`. Build configuration belongs to the host. The entry does not start
|
|
95
|
-
Stimulus or register anything on import. Omit `only` to register all library controllers. Register
|
|
96
|
-
defaults before application overrides. Repeated calls on the same application preserve later
|
|
97
|
-
overrides; unknown controller identifiers raise before registration.
|
|
60
|
+
`emoji-picker`, `table`, `prose` and `shell` (`lc_app_header`, `lc_docs_shell` and `lc_dock`; the dock's action
|
|
61
|
+
menu also needs `menu`). A component whose entry is missing renders unstyled.
|
|
98
62
|
|
|
99
63
|
The gem's stylesheets write into the `components` and `modules` layers, so the host declares that layer order.
|
|
100
|
-
The theme defines the `dark:` variant with `html[data-theme="dark"]`.
|
|
64
|
+
The theme defines the `dark:` variant with `html[data-theme="dark"]`. Each entry carries its own `@source` lines, so Tailwind
|
|
101
65
|
scans the gem's helpers, views and JavaScript wherever the gem is installed.
|
|
102
66
|
|
|
103
67
|
### Theming
|
|
@@ -194,6 +158,53 @@ lazyLoadControllersFrom("controllers", application)
|
|
|
194
158
|
`eagerLoadControllersFrom` also works and downloads all of them on every page. A host file with the
|
|
195
159
|
same name, such as `app/javascript/controllers/lc_dialog_controller.js`, replaces the gem's.
|
|
196
160
|
|
|
161
|
+
To register controllers by hand instead, import `registerControllers` from the `lostcoords/ui` pin:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
import { registerControllers } from "lostcoords/ui"
|
|
165
|
+
|
|
166
|
+
registerControllers(application, { only: ["lc-copy", "lc-collapsible"] })
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Omit `only` to register every library controller. Unknown identifiers raise before anything is
|
|
170
|
+
registered, and repeated calls on the same application keep later overrides, so register defaults
|
|
171
|
+
before application overrides.
|
|
172
|
+
|
|
173
|
+
## Changes in 0.9.0
|
|
174
|
+
|
|
175
|
+
The gem is Rails-only. It loads through its Rails engine, and the standalone ActionView mode is gone:
|
|
176
|
+
|
|
177
|
+
- **`require "lostcoords/ui/action_view"` and `require "lostcoords/ui/rails"` are removed.** Use
|
|
178
|
+
`require "lostcoords/ui"`, or let Bundler require the gem. Both load the engine.
|
|
179
|
+
- **`Lostcoords::Ui.view_paths`, `Lostcoords::Ui.load_translations!` and `Lostcoords::Ui.root` are removed.**
|
|
180
|
+
The engine adds the gem's views and translations to the host's lookup paths. Use
|
|
181
|
+
`Lostcoords::Ui::Engine.root` to locate the gem's files.
|
|
182
|
+
- **Components no longer call a private `lc_locale`.** They translate with `I18n.locale`. Set the locale
|
|
183
|
+
through Rails, for example with `I18n.with_locale` in a controller `around_action`.
|
|
184
|
+
- **The gem depends on `railties` instead of `activesupport`.** An app outside Rails moves to a Rails
|
|
185
|
+
application and the engine's asset setup above.
|
|
186
|
+
|
|
187
|
+
Rails apps that load the gem through Bundler need no edits.
|
|
188
|
+
|
|
189
|
+
New components and options:
|
|
190
|
+
|
|
191
|
+
- **`lc_toc` is an "On this page" list that follows the reader.** It marks the section scrolled to with
|
|
192
|
+
`aria-current="location"` and slides an indicator to it. Pass links with `toc.link "Usage", "#usage"`, or
|
|
193
|
+
rendered HTML with `lc_toc(headings: html)` to list its `h2` and `h3` that carry an id.
|
|
194
|
+
- **Headings and sections can link to themselves.** `lc_heading "Usage", id: "usage", anchor: true` and
|
|
195
|
+
`lc_section title: "Usage", id: "usage", anchor: true` wrap the title in a link with a `#` mark on hover.
|
|
196
|
+
`anchor: "section-id"` links a heading to an element around it. `lc_prose(anchors: true)` does the same for
|
|
197
|
+
rendered Markdown, whose headings Kiln gives ids.
|
|
198
|
+
- **Anchored targets clear sticky chrome.** `.lc-anchor-target` sets a scroll margin of `--lc-sticky-offset`,
|
|
199
|
+
which `lc_app_header` sets to `5rem` while it renders. Override the property if your header is taller.
|
|
200
|
+
- **`lc_app_header`, `lc_docs_shell` and `lc_dock` move from the Blocks examples into the gem**, in the new
|
|
201
|
+
`shell` stylesheet entry, with the `lc-app-header` and `lc-dock` controllers. `lc_app_header` yields
|
|
202
|
+
`header.brand`, `header.nav` and `header.actions`. `lc_docs_shell(navigation:, contents:)` lays out a sidebar,
|
|
203
|
+
the page and an `lc_toc`. `lc_dock` yields `dock.nav` and `dock.menu`, reserves its height at the end of the
|
|
204
|
+
page and hides while the on-screen keyboard is open. If you copied the Application header, Documentation
|
|
205
|
+
layout or Mobile navigation block, you can replace the copy with the helper and delete its stylesheet and
|
|
206
|
+
controller.
|
|
207
|
+
|
|
197
208
|
## Changes in 0.8.2
|
|
198
209
|
|
|
199
210
|
Nothing needs an edit. New options, and one change you may see:
|
|
@@ -540,8 +551,31 @@ on the scroll container to keep its position across Turbo visits and reloads. Co
|
|
|
540
551
|
`data-lc-scroll-keeper-key-value` share one position for the browser tab. Without a saved position,
|
|
541
552
|
it scrolls the `aria-current="page"` link into view.
|
|
542
553
|
|
|
543
|
-
|
|
544
|
-
|
|
554
|
+
Page chrome lives in the `shell` stylesheet entry. `lc_app_header` is a sticky glass header,
|
|
555
|
+
`lc_docs_shell` lays out a documentation page, and `lc_toc` lists the page's sections and marks the one
|
|
556
|
+
the reader has scrolled to. This documentation site is built with them.
|
|
557
|
+
|
|
558
|
+
```erb
|
|
559
|
+
<%= lc_app_header do |header| %>
|
|
560
|
+
<%= header.brand root_path, label: "Acme home" do %><%= image_tag "logo.svg", alt: "" %> Acme<% end %>
|
|
561
|
+
<%= header.nav class: "max-sm:hidden" do |nav| %>
|
|
562
|
+
<%= nav.link "Guides", guides_path, current: true %>
|
|
563
|
+
<% end %>
|
|
564
|
+
<%= header.actions do %><%= lc_icon_button "Search", icon: "magnifying-glass", size: :sm %><% end %>
|
|
565
|
+
<% end %>
|
|
566
|
+
|
|
567
|
+
<% contents = lc_toc do |toc| %>
|
|
568
|
+
<%= toc.link "Usage", "#usage" %>
|
|
569
|
+
<%= toc.link "Sizes", "#sizes", indent: true %>
|
|
570
|
+
<% end %>
|
|
571
|
+
<%= lc_docs_shell navigation: render("guides/sidebar"), contents: do %>
|
|
572
|
+
<%= lc_section title: "Usage", id: "usage", anchor: true do %>…<% end %>
|
|
573
|
+
<% end %>
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
For rendered Markdown, such as a Kiln `.md` view, pass the HTML to both: `lc_prose(anchors: true) { html }`
|
|
577
|
+
links its headings, and `lc_toc(headings: html)` lists them. On phones, `lc_dock` floats `dock.nav` and a
|
|
578
|
+
`dock.menu` of quick actions at the bottom of the screen; render it last, since it reserves its height there.
|
|
545
579
|
|
|
546
580
|
```erb
|
|
547
581
|
<%= lc_menu id: "member-actions", label: "Actions for #{member.name}", trigger: :icon do %>
|
|
@@ -935,11 +969,15 @@ Blocks live in the docs app. They add no rendering helpers or installable block
|
|
|
935
969
|
## Docs
|
|
936
970
|
|
|
937
971
|
The docs app lives in `test/dummy`. Run `parcela up --detach` and open https://ui.localhost.
|
|
938
|
-
Parcela starts Rails
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
972
|
+
Parcela starts Rails (with live reload through Hotwire Spark) and the Tailwind watcher.
|
|
973
|
+
`parcela down` stops this environment.
|
|
974
|
+
|
|
975
|
+
`bin/rails kiln:build` exports every docs route to `docs/` for GitHub Pages with
|
|
976
|
+
[Kiln](https://github.com/davafons/kiln): pages, blocks, the token reference, the machine-readable API and
|
|
977
|
+
assets. Param sources for the component and block pages live in `test/dummy/config/kiln.rb`, and a route
|
|
978
|
+
without one fails the build. `bin/rails kiln:serve` serves the export the way GitHub Pages does. CI runs
|
|
979
|
+
`bin/rails kiln:build` and fails when `docs/` changes, and checks that every public rendering helper has a
|
|
980
|
+
runnable example.
|
|
943
981
|
|
|
944
982
|
## Development
|
|
945
983
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@import "./theme.css";
|
|
6
6
|
@source "../../../../helpers/lostcoords/ui/{badge,button,card,choice,copy_button,description_list,feedback,form,glass,icon,layout,list_group,navigation,primitives,render_attributes,structures,tailwind,translation,typography}_helper.rb";
|
|
7
7
|
@source "../../../../javascript/lostcoords/ui/helpers/*.js";
|
|
8
|
-
@source "../../../../javascript/lostcoords/ui/controllers/lc_{copy,kbd,nav,scroll_keeper,selection,shortcut,tabs,toggle}_controller.js";
|
|
8
|
+
@source "../../../../javascript/lostcoords/ui/controllers/lc_{copy,kbd,nav,scroll_keeper,selection,shortcut,tabs,toc,toggle}_controller.js";
|
|
9
9
|
@source "../../../../../lib/lostcoords/ui/parts.rb";
|
|
10
10
|
|
|
11
11
|
@import "./glass.css";
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/* A sticky lc_app_header covers the top of the page; anchors and the docs
|
|
2
|
+
sidebars start below it. Override --lc-sticky-offset if your header is taller. */
|
|
3
|
+
:root:has(.lc-app-header) {
|
|
4
|
+
--lc-sticky-offset: 5rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@layer components {
|
|
8
|
+
.lc-app-header__glass {
|
|
9
|
+
opacity: 0;
|
|
10
|
+
transition: opacity 300ms ease;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.lc-app-header[data-scrolled] .lc-app-header__glass {
|
|
14
|
+
opacity: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The sidebar scrolls on its own without a visible scrollbar. */
|
|
18
|
+
.lc-docs-shell__sidebar {
|
|
19
|
+
scrollbar-width: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.lc-docs-shell__sidebar::-webkit-scrollbar {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.lc-dock {
|
|
27
|
+
--lc-dock-edge: max(1rem, env(safe-area-inset-left));
|
|
28
|
+
--lc-dock-gap: max(1rem, env(safe-area-inset-right));
|
|
29
|
+
--lc-dock-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
|
30
|
+
--lc-dock-size: 4rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Holds the dock's height at the end of the page, so the last content scrolls clear of it. */
|
|
34
|
+
.lc-dock__space {
|
|
35
|
+
block-size: calc(var(--lc-dock-bottom) + var(--lc-dock-size) + 1rem);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.lc-dock__menu {
|
|
39
|
+
position: fixed;
|
|
40
|
+
z-index: calc(var(--lc-z-sticky) + 1);
|
|
41
|
+
inset-inline-end: var(--lc-dock-gap);
|
|
42
|
+
bottom: var(--lc-dock-bottom);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.lc-dock__trigger,
|
|
46
|
+
.lc-dock__action {
|
|
47
|
+
display: grid;
|
|
48
|
+
width: var(--lc-dock-size);
|
|
49
|
+
height: var(--lc-dock-size);
|
|
50
|
+
place-items: center;
|
|
51
|
+
padding: 0;
|
|
52
|
+
color: var(--primary);
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.lc-dock__trigger {
|
|
57
|
+
position: relative;
|
|
58
|
+
z-index: 2;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.lc-dock__trigger svg {
|
|
62
|
+
transition: rotate 300ms var(--lc-ease-sheet, ease);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.lc-dock__trigger[aria-expanded="true"] svg {
|
|
66
|
+
rotate: 135deg;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.lc-dock__actions {
|
|
70
|
+
visibility: hidden;
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.lc-dock__actions[data-open] {
|
|
75
|
+
visibility: visible;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.lc-dock__scrim {
|
|
79
|
+
position: fixed;
|
|
80
|
+
inset: 0;
|
|
81
|
+
border: 0;
|
|
82
|
+
background: color-mix(in oklab, var(--foreground) 18%, transparent);
|
|
83
|
+
opacity: 0;
|
|
84
|
+
-webkit-backdrop-filter: blur(calc(var(--spacing) * 2));
|
|
85
|
+
backdrop-filter: blur(calc(var(--spacing) * 2));
|
|
86
|
+
transition: opacity 220ms ease;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.lc-dock__actions[data-open] .lc-dock__scrim {
|
|
90
|
+
pointer-events: auto;
|
|
91
|
+
opacity: 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.lc-dock__action {
|
|
95
|
+
position: absolute;
|
|
96
|
+
z-index: 1;
|
|
97
|
+
inset: 0;
|
|
98
|
+
width: calc(var(--lc-dock-size) - 0.5rem);
|
|
99
|
+
height: calc(var(--lc-dock-size) - 0.5rem);
|
|
100
|
+
margin: 0.25rem;
|
|
101
|
+
color: var(--foreground);
|
|
102
|
+
opacity: 0;
|
|
103
|
+
transform: scale(0.4);
|
|
104
|
+
transition: transform 300ms ease, opacity 150ms ease;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.lc-dock__actions[data-open] .lc-dock__action {
|
|
108
|
+
pointer-events: auto;
|
|
109
|
+
opacity: 1;
|
|
110
|
+
transform: translateY(calc(var(--lc-dock-action-index, 1) * -4.5rem)) scale(1);
|
|
111
|
+
transition: transform 420ms cubic-bezier(0.34, 1.45, 0.64, 1), opacity 160ms ease;
|
|
112
|
+
transition-delay: calc((var(--lc-dock-action-index, 1) - 1) * 40ms);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.lc-dock__action-label {
|
|
116
|
+
position: absolute;
|
|
117
|
+
inset-inline-end: calc(100% + 0.75rem);
|
|
118
|
+
width: max-content;
|
|
119
|
+
max-width: calc(100vw - 7rem);
|
|
120
|
+
padding: 0.5rem 0.875rem;
|
|
121
|
+
border-radius: var(--radius-pill);
|
|
122
|
+
background: var(--background);
|
|
123
|
+
box-shadow: var(--lc-shadow);
|
|
124
|
+
color: var(--foreground);
|
|
125
|
+
font-size: 0.875rem;
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.lc-dock[data-keyboard] :is(.lc-dock__nav, .lc-dock__menu, .lc-dock__space) {
|
|
130
|
+
display: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@media (prefers-reduced-motion: reduce) {
|
|
134
|
+
.lc-app-header__glass,
|
|
135
|
+
.lc-dock__trigger svg,
|
|
136
|
+
.lc-dock__scrim,
|
|
137
|
+
.lc-dock__action,
|
|
138
|
+
.lc-dock__actions[data-open] .lc-dock__action {
|
|
139
|
+
transition: none;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* The nav is an lc_nav_list, whose relative position is a utility, so the dock's
|
|
145
|
+
placement sits in the utilities layer too. */
|
|
146
|
+
@layer utilities {
|
|
147
|
+
.lc-dock__nav {
|
|
148
|
+
position: fixed;
|
|
149
|
+
z-index: var(--lc-z-sticky);
|
|
150
|
+
inset-inline: var(--lc-dock-edge) var(--lc-dock-gap);
|
|
151
|
+
bottom: var(--lc-dock-bottom);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.lc-dock:has(.lc-dock__menu) .lc-dock__nav {
|
|
155
|
+
inset-inline-end: calc(var(--lc-dock-gap) + var(--lc-dock-size) + 0.75rem);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.lc-dock[data-keyboard] .lc-dock__nav {
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -12,6 +12,32 @@
|
|
|
12
12
|
overflow-wrap: anywhere;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/* Headings and sections a link can point to. --lc-sticky-offset is the height
|
|
16
|
+
of sticky chrome, such as lc_app_header, so the target lands below it. */
|
|
17
|
+
:where(.lc-anchor-target) {
|
|
18
|
+
scroll-margin-block-start: calc(var(--lc-sticky-offset, 0rem) + var(--lc-anchor-gap, 1.5rem));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* A heading that links to itself. The mark shows on hover and keyboard focus. */
|
|
22
|
+
:where(.lc-anchor) {
|
|
23
|
+
color: inherit;
|
|
24
|
+
text-decoration: none;
|
|
25
|
+
border-radius: var(--radius-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.lc-anchor::after {
|
|
29
|
+
content: "#" / "";
|
|
30
|
+
margin-inline-start: 0.4em;
|
|
31
|
+
color: var(--muted-foreground);
|
|
32
|
+
opacity: 0;
|
|
33
|
+
transition: opacity var(--lc-motion-quick) ease;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.lc-anchor:hover::after,
|
|
37
|
+
.lc-anchor:focus-visible::after {
|
|
38
|
+
opacity: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
:where(.lc-eyebrow) {
|
|
16
42
|
font-family: var(--font-eyebrow);
|
|
17
43
|
text-transform: uppercase;
|
|
@@ -15,9 +15,9 @@ module Lostcoords::Ui
|
|
|
15
15
|
picker_class: picker_class, trigger_class: trigger_class, popover_class: popover_class, attributes: attributes
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
SEARCH_NAMES_PATH = Lostcoords::Ui.root.join("config/emoji_search_names.yml")
|
|
19
|
-
SEARCH_NAMES_JA_PATH = Lostcoords::Ui.root.join("config/emoji_search_names_ja.yml")
|
|
20
|
-
SEARCH_ALIASES_PATH = Lostcoords::Ui.root.join("config/emoji_search_aliases.yml")
|
|
18
|
+
SEARCH_NAMES_PATH = Lostcoords::Ui::Engine.root.join("config/emoji_search_names.yml")
|
|
19
|
+
SEARCH_NAMES_JA_PATH = Lostcoords::Ui::Engine.root.join("config/emoji_search_names_ja.yml")
|
|
20
|
+
SEARCH_ALIASES_PATH = Lostcoords::Ui::Engine.root.join("config/emoji_search_aliases.yml")
|
|
21
21
|
|
|
22
22
|
# Representative glyph for each category's tab in the picker navigation.
|
|
23
23
|
CATEGORY_ICONS = {
|
|
@@ -51,11 +51,13 @@ module Lostcoords::Ui
|
|
|
51
51
|
tag.div(**lc_attributes({ class: lc_container_class(size: size) }, attributes), &block)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
# anchor: true links the heading to the section's id:, for pages readers link into.
|
|
55
|
+
def lc_section(title:, level: 2, actions: nil, anchor: false, **attributes, &block)
|
|
55
56
|
raise ArgumentError, "title must not be blank" if title.blank?
|
|
57
|
+
raise ArgumentError, "anchor: true needs an id" if anchor && attributes[:id].blank?
|
|
56
58
|
|
|
57
|
-
tag.section(**lc_attributes({ class: lc_stack_class(gap: :heading) }, attributes)) do
|
|
58
|
-
heading = lc_heading(title, level: level, size: :heading)
|
|
59
|
+
tag.section(**lc_attributes({ class: lc_stack_class(gap: :heading, extra: ("lc-anchor-target" if anchor)) }, attributes)) do
|
|
60
|
+
heading = lc_heading(title, level: level, size: :heading, anchor: (attributes[:id] if anchor))
|
|
59
61
|
action_content = actions.respond_to?(:call) ? capture(&actions) : actions
|
|
60
62
|
header = lc_section_header { safe_join([ heading, action_content ].compact) }
|
|
61
63
|
safe_join([ header, lc_stack(&block) ])
|
|
@@ -84,6 +84,35 @@ module Lostcoords::Ui
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
TOC_INDICATOR = "pointer-events-none absolute -start-px top-0 h-0 w-0.5 rounded-pill bg-primary opacity-0 " \
|
|
88
|
+
"transition-[translate,height,opacity] duration-300 ease-(--lc-ease-out-expo) motion-reduce:transition-none"
|
|
89
|
+
|
|
90
|
+
# An "On this page" list that marks the section the reader has scrolled to with aria-current="location".
|
|
91
|
+
# Yields a builder: `toc.link "Usage", "#usage"`, with indent: true for a subsection. headings: takes
|
|
92
|
+
# rendered HTML, such as Markdown output, and lists its headings that carry an id, nesting the deeper level.
|
|
93
|
+
def lc_toc(label: lc_t("lostcoords.ui.toc.label"), headings: nil, levels: 2..3, **attributes, &block)
|
|
94
|
+
lc_label!(label, :label)
|
|
95
|
+
raise ArgumentError, "lc_toc takes headings: or a block, not both" if headings && block
|
|
96
|
+
raise ArgumentError, "lc_toc needs headings: or a block" unless headings || block
|
|
97
|
+
levels = Array(levels)
|
|
98
|
+
raise ArgumentError, "levels must be heading levels from 1 to 6" unless levels.any? && levels.all? { |level| (1..6).cover?(level) }
|
|
99
|
+
|
|
100
|
+
toc = Parts::Toc.new(self)
|
|
101
|
+
links = if block
|
|
102
|
+
capture(toc, &block)
|
|
103
|
+
else
|
|
104
|
+
found = Nokogiri::HTML5.fragment(headings.to_s).css(levels.map { |level| "h#{level}[id]" }.join(", "))
|
|
105
|
+
safe_join(found.map { |heading| toc.link(heading.text.strip, "##{heading['id']}", indent: heading.name.delete("h").to_i > levels.min) })
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
tag.nav(**lc_attributes({ class: "relative", aria: { label: label }, data: { slot: "toc", controller: "lc-toc" } }, attributes)) do
|
|
109
|
+
safe_join([
|
|
110
|
+
tag.span(class: TOC_INDICATOR, aria: { hidden: true }, data: { slot: "toc-indicator", lc_toc_target: "indicator" }),
|
|
111
|
+
tag.ul(links, class: "m-0 flex list-none flex-col border-s border-border p-0")
|
|
112
|
+
])
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
87
116
|
# Previous and next links between pages in a sequence. Each side takes [title, url] or nil.
|
|
88
117
|
# compact: true shows icon buttons, for a page header.
|
|
89
118
|
def lc_pager(previous: nil, next: nil, label: lc_t("lostcoords.ui.pager.label"), compact: false, **attributes)
|
|
@@ -8,8 +8,29 @@ module Lostcoords::Ui
|
|
|
8
8
|
lc_tw(PROSE_BASE, extra)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# anchors: true links every h2, h3 and h4 that has an id to itself, as rendered Markdown's headings
|
|
12
|
+
# do. Pass the same HTML to lc_toc(headings:) for an "On this page" list.
|
|
13
|
+
def lc_prose(anchors: false, **attributes, &block)
|
|
14
|
+
content = capture(&block) if block
|
|
15
|
+
content = lc_anchor_headings(content) if anchors && content
|
|
16
|
+
tag.div(content, **lc_attributes({ class: lc_prose_class }, attributes))
|
|
13
17
|
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def lc_anchor_headings(html)
|
|
21
|
+
fragment = Nokogiri::HTML5.fragment(html.to_s)
|
|
22
|
+
fragment.css("h2[id], h3[id], h4[id]").each do |heading|
|
|
23
|
+
next if heading.at_css("a.lc-anchor")
|
|
24
|
+
|
|
25
|
+
heading["class"] = [ heading["class"], "lc-anchor-target" ].compact.join(" ")
|
|
26
|
+
link = Nokogiri::XML::Node.new("a", fragment.document)
|
|
27
|
+
link["href"] = "##{heading['id']}"
|
|
28
|
+
link["class"] = "lc-anchor"
|
|
29
|
+
link["data-slot"] = "heading-anchor"
|
|
30
|
+
link.children = heading.children
|
|
31
|
+
heading.add_child(link)
|
|
32
|
+
end
|
|
33
|
+
fragment.to_html.html_safe
|
|
34
|
+
end
|
|
14
35
|
end
|
|
15
36
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Lostcoords::Ui
|
|
2
|
+
# Page chrome: the sticky application header, the documentation layout and the phone dock.
|
|
3
|
+
module ShellHelper
|
|
4
|
+
DOCS_SHELL = "lc-docs-shell mx-auto flex w-full max-w-360 gap-10 px-5 sm:px-8 lg:px-10"
|
|
5
|
+
DOCS_SHELL_ASIDE = "sticky top-(--lc-sticky-offset,0rem) hidden h-[calc(100dvh-var(--lc-sticky-offset,0rem))] shrink-0 overflow-y-auto"
|
|
6
|
+
|
|
7
|
+
# A sidebar, the page and an optional "On this page" column. navigation: is the sidebar, usually
|
|
8
|
+
# lc_nav_list; it also fills a collapsible menu above the page below lg. contents: is usually lc_toc
|
|
9
|
+
# and shows from xl. main: adds attributes to the <main>, such as a max width for reading pages.
|
|
10
|
+
def lc_docs_shell(navigation:, contents: nil, menu_label: lc_t("lostcoords.ui.docs_shell.menu"), sidebar_label: lc_t("lostcoords.ui.docs_shell.sidebar"), main: {}, **attributes, &block)
|
|
11
|
+
lc_label!(menu_label, :menu_label)
|
|
12
|
+
lc_label!(sidebar_label, :sidebar_label)
|
|
13
|
+
raise ArgumentError, "lc_docs_shell needs navigation:" if navigation.blank?
|
|
14
|
+
|
|
15
|
+
chevron = lc_icon("chevron-down", options: { class: "size-4 transition-transform duration-(--lc-motion-quick) group-open/menu:rotate-180", "aria-hidden": true })
|
|
16
|
+
menu = tag.details(class: "group/menu relative z-raised rounded-2xl lc-squircle bg-muted lg:hidden", data: { slot: "docs-shell-menu" }) do
|
|
17
|
+
safe_join([
|
|
18
|
+
tag.summary(safe_join([ menu_label, chevron ]), class: "flex min-h-12 cursor-pointer list-none items-center justify-between gap-content px-4 text-label [&::-webkit-details-marker]:hidden"),
|
|
19
|
+
tag.div(navigation, class: "max-h-[60dvh] overflow-y-auto p-3 pt-0")
|
|
20
|
+
])
|
|
21
|
+
end
|
|
22
|
+
page = tag.main(**lc_attributes({ id: "main", class: "isolate min-w-0 flex-1 py-5 lg:py-10", data: { slot: "docs-shell-main" } }, main)) do
|
|
23
|
+
safe_join([ menu, tag.div(capture(&block), class: "pt-6 lg:pt-0") ])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
tag.div(**lc_attributes({ class: DOCS_SHELL, data: { slot: "docs-shell" } }, attributes)) do
|
|
27
|
+
safe_join([
|
|
28
|
+
tag.aside(navigation, tabindex: 0, aria: { label: sidebar_label }, class: lc_tw(DOCS_SHELL_ASIDE, "lc-docs-shell__sidebar w-52 py-8 pe-4 lg:block"),
|
|
29
|
+
data: { slot: "docs-shell-sidebar", controller: "lc-scroll-keeper", lc_scroll_keeper_key_value: "lc-docs-shell" }),
|
|
30
|
+
page,
|
|
31
|
+
(tag.aside(contents, class: lc_tw(DOCS_SHELL_ASIDE, "w-44 py-12 ps-4 xl:block"), data: { slot: "docs-shell-contents" }) if contents.present?)
|
|
32
|
+
].compact)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A sticky header whose glass fades in once the page scrolls. Yields a builder: header.brand,
|
|
37
|
+
# header.nav and header.actions render in that order along the bar. bar: adds attributes to the
|
|
38
|
+
# bar, such as a max width. While it renders, --lc-sticky-offset clears it for anchors and sidebars.
|
|
39
|
+
def lc_app_header(bar: {}, **attributes, &block)
|
|
40
|
+
raise ArgumentError, "lc_app_header needs a block" unless block
|
|
41
|
+
|
|
42
|
+
content = capture(Parts::AppHeader.new(self), &block)
|
|
43
|
+
tag.header(**lc_attributes({ class: "lc-app-header sticky top-0 z-sticky px-3 pt-3 pb-4 sm:px-6",
|
|
44
|
+
data: { slot: "app-header", controller: "lc-app-header", action: "scroll@window->lc-app-header#scroll" } }, attributes)) do
|
|
45
|
+
tag.div(**lc_attributes({ class: "relative isolate mx-auto flex min-h-16 w-full max-w-page items-center gap-related px-3 py-2 sm:gap-content sm:px-5", data: { slot: "app-header-bar" } }, bar)) do
|
|
46
|
+
safe_join([ tag.span(class: lc_glass_class(thick: true, extra: "lc-app-header__glass pointer-events-none absolute inset-0 -z-(--lc-z-raised)"), aria: { hidden: true }), content ])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A floating bar at the bottom of a phone screen: dock.nav for primary destinations and dock.menu
|
|
52
|
+
# for a button whose actions fan out above it. It reserves its height at the end of the page, so
|
|
53
|
+
# render it last, and it hides while the on-screen keyboard is open.
|
|
54
|
+
def lc_dock(**attributes, &block)
|
|
55
|
+
raise ArgumentError, "lc_dock needs a block" unless block
|
|
56
|
+
|
|
57
|
+
content = capture(Parts::Dock.new(self), &block)
|
|
58
|
+
tag.div(**lc_attributes({ class: "lc-dock", data: { slot: "dock", controller: "lc-dock",
|
|
59
|
+
action: "resize@window->lc-dock#keyboard focusin@document->lc-dock#keyboard focusout@document->lc-dock#keyboard" } }, attributes)) do
|
|
60
|
+
safe_join([ content, tag.div(class: "lc-dock__space", aria: { hidden: true }) ])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -24,10 +24,17 @@ module Lostcoords::Ui
|
|
|
24
24
|
lc_tw(KBD_BASE, extra)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
# anchor: true links the heading to itself, so readers can copy a link to it; it needs id:.
|
|
28
|
+
# anchor: "section-id" links it to an element around it instead, such as its <section>.
|
|
29
|
+
def lc_heading(content = nil, level: 2, size: :heading, trim: false, anchor: false, **attributes, &block)
|
|
28
30
|
raise ArgumentError, "Invalid level: #{level.inspect}. Expected an integer from 1 to 6" unless (1..6).include?(level) && level.is_a?(Integer)
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
target = anchor == true ? attributes[:id].to_s : anchor.presence&.to_s
|
|
33
|
+
raise ArgumentError, "anchor: true needs an id" if anchor == true && target.blank?
|
|
34
|
+
|
|
35
|
+
content = capture(&block) if block
|
|
36
|
+
content = tag.a(content, href: "##{target}", class: "lc-anchor", data: { slot: "heading-anchor" }) if target
|
|
37
|
+
tag.public_send("h#{level}", content, **lc_attributes({ class: lc_heading_class(size: size, trim: trim, extra: ("lc-anchor-target" if anchor == true)) }, attributes))
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
# as: picks the element, such as :span inside a row or :h3 for a heading set in a text role.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Fades the header's glass in once the page scrolls.
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
connect() {
|
|
6
|
+
this.scroll()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// An open modal pins the body, so scrollY reads 0 until it closes; the header keeps the
|
|
10
|
+
// state it had rather than fading the glass out behind the dialog.
|
|
11
|
+
scroll() {
|
|
12
|
+
if (document.body.dataset.modalScrollLock === "true") return
|
|
13
|
+
this.element.toggleAttribute("data-scrolled", window.scrollY > 8)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Hides the dock while the on-screen keyboard is open, so it doesn't cover the field being typed in.
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
connect() {
|
|
6
|
+
this.viewportHandler = () => this.keyboard()
|
|
7
|
+
window.visualViewport?.addEventListener("resize", this.viewportHandler)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
disconnect() {
|
|
11
|
+
window.visualViewport?.removeEventListener("resize", this.viewportHandler)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
keyboard() {
|
|
15
|
+
const typing = document.activeElement?.matches(
|
|
16
|
+
"input, textarea, select, [contenteditable]:not([contenteditable=false])",
|
|
17
|
+
)
|
|
18
|
+
const inset = window.innerHeight - (window.visualViewport?.height ?? window.innerHeight)
|
|
19
|
+
this.element.toggleAttribute("data-keyboard", Boolean(typing) && inset > 150)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Marks the link to the section the reader has scrolled to with aria-current="location" and slides
|
|
4
|
+
// the indicator to it. A section is current once its top passes its own scroll margin, which is
|
|
5
|
+
// where a click on its link puts it.
|
|
6
|
+
export default class extends Controller {
|
|
7
|
+
static targets = ["link", "indicator"]
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.schedule = () => {
|
|
11
|
+
this.frame ||= requestAnimationFrame(() => {
|
|
12
|
+
this.frame = null
|
|
13
|
+
this.update()
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
addEventListener("scroll", this.schedule, { passive: true })
|
|
17
|
+
addEventListener("resize", this.schedule, { passive: true })
|
|
18
|
+
this.update({ animate: false })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
disconnect() {
|
|
22
|
+
removeEventListener("scroll", this.schedule)
|
|
23
|
+
removeEventListener("resize", this.schedule)
|
|
24
|
+
cancelAnimationFrame(this.frame)
|
|
25
|
+
this.frame = null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The last sections can't scroll up to the top, so at the end of the page the clicked one stays current.
|
|
29
|
+
select(event) {
|
|
30
|
+
this.chosen = event.currentTarget
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
update({ animate = true } = {}) {
|
|
34
|
+
const sections = this.linkTargets.flatMap((link) => {
|
|
35
|
+
const target = document.getElementById(decodeURIComponent(link.hash.slice(1)))
|
|
36
|
+
return target ? [{ link, target, top: target.getBoundingClientRect().top }] : []
|
|
37
|
+
})
|
|
38
|
+
let current = null
|
|
39
|
+
for (const section of sections) {
|
|
40
|
+
const margin = Number.parseFloat(getComputedStyle(section.target).scrollMarginTop) || 0
|
|
41
|
+
if (section.top <= margin + 4) current = section.link
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const root = document.scrollingElement ?? document.documentElement
|
|
45
|
+
if (root.scrollTop > 0 && root.scrollTop + innerHeight >= root.scrollHeight - 2) {
|
|
46
|
+
const visible = sections.filter((section) => section.top < innerHeight)
|
|
47
|
+
const chosen = visible.find((section) => section.link === this.chosen)
|
|
48
|
+
current = (chosen ?? visible.at(-1))?.link ?? current
|
|
49
|
+
}
|
|
50
|
+
if (current === this.current && !animate) return this.place(current, { animate })
|
|
51
|
+
if (current === this.current) return
|
|
52
|
+
|
|
53
|
+
this.current = current
|
|
54
|
+
for (const link of this.linkTargets) {
|
|
55
|
+
if (link === current) link.setAttribute("aria-current", "location")
|
|
56
|
+
else link.removeAttribute("aria-current")
|
|
57
|
+
}
|
|
58
|
+
this.place(current, { animate })
|
|
59
|
+
this.reveal(current)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
place(link, { animate }) {
|
|
63
|
+
if (!this.hasIndicatorTarget) return
|
|
64
|
+
|
|
65
|
+
const style = this.indicatorTarget.style
|
|
66
|
+
style.transition = animate ? "" : "none"
|
|
67
|
+
if (link) {
|
|
68
|
+
style.translate = `0 ${link.offsetTop}px`
|
|
69
|
+
style.height = `${link.offsetHeight}px`
|
|
70
|
+
}
|
|
71
|
+
style.opacity = link ? "1" : "0"
|
|
72
|
+
if (!animate) {
|
|
73
|
+
this.indicatorTarget.offsetWidth
|
|
74
|
+
style.transition = ""
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Keeps the current link inside a scrolling sidebar without scrolling the page.
|
|
79
|
+
reveal(link) {
|
|
80
|
+
if (!link) return
|
|
81
|
+
|
|
82
|
+
let box = this.element.parentElement
|
|
83
|
+
while (box && box !== document.body) {
|
|
84
|
+
const { overflowY } = getComputedStyle(box)
|
|
85
|
+
if ((overflowY === "auto" || overflowY === "scroll") && box.scrollHeight > box.clientHeight)
|
|
86
|
+
break
|
|
87
|
+
box = box.parentElement
|
|
88
|
+
}
|
|
89
|
+
if (!box || box === document.body) return
|
|
90
|
+
|
|
91
|
+
const linkBox = link.getBoundingClientRect()
|
|
92
|
+
const frame = box.getBoundingClientRect()
|
|
93
|
+
if (linkBox.top < frame.top) box.scrollTop -= frame.top - linkBox.top
|
|
94
|
+
else if (linkBox.bottom > frame.bottom) box.scrollTop += linkBox.bottom - frame.bottom
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import AppHeader from "lostcoords/ui/controllers/lc_app_header_controller"
|
|
1
2
|
import CodeBlock from "lostcoords/ui/controllers/lc_code_block_controller"
|
|
2
3
|
import Collapsible from "lostcoords/ui/controllers/lc_collapsible_controller"
|
|
3
4
|
import Copy from "lostcoords/ui/controllers/lc_copy_controller"
|
|
4
5
|
import Dialog from "lostcoords/ui/controllers/lc_dialog_controller"
|
|
6
|
+
import Dock from "lostcoords/ui/controllers/lc_dock_controller"
|
|
5
7
|
import EmojiPicker from "lostcoords/ui/controllers/lc_emoji_picker_controller"
|
|
6
8
|
import Kbd from "lostcoords/ui/controllers/lc_kbd_controller"
|
|
7
9
|
import Menu from "lostcoords/ui/controllers/lc_menu_controller"
|
|
@@ -15,14 +17,17 @@ import Shortcut from "lostcoords/ui/controllers/lc_shortcut_controller"
|
|
|
15
17
|
import Tabs from "lostcoords/ui/controllers/lc_tabs_controller"
|
|
16
18
|
import Toast from "lostcoords/ui/controllers/lc_toast_controller"
|
|
17
19
|
import ToastRegion from "lostcoords/ui/controllers/lc_toast_region_controller"
|
|
20
|
+
import Toc from "lostcoords/ui/controllers/lc_toc_controller"
|
|
18
21
|
import Toggle from "lostcoords/ui/controllers/lc_toggle_controller"
|
|
19
22
|
import Tooltip from "lostcoords/ui/controllers/lc_tooltip_controller"
|
|
20
23
|
|
|
21
24
|
const controllers = {
|
|
25
|
+
"lc-app-header": AppHeader,
|
|
22
26
|
"lc-collapsible": Collapsible,
|
|
23
27
|
"lc-code-block": CodeBlock,
|
|
24
28
|
"lc-copy": Copy,
|
|
25
29
|
"lc-dialog": Dialog,
|
|
30
|
+
"lc-dock": Dock,
|
|
26
31
|
"lc-emoji-picker": EmojiPicker,
|
|
27
32
|
"lc-kbd": Kbd,
|
|
28
33
|
"lc-menu": Menu,
|
|
@@ -34,6 +39,7 @@ const controllers = {
|
|
|
34
39
|
"lc-sheet": Sheet,
|
|
35
40
|
"lc-shortcut": Shortcut,
|
|
36
41
|
"lc-tabs": Tabs,
|
|
42
|
+
"lc-toc": Toc,
|
|
37
43
|
"lc-toast": Toast,
|
|
38
44
|
"lc-toast-region": ToastRegion,
|
|
39
45
|
"lc-toggle": Toggle,
|
data/config/locales/en.yml
CHANGED
|
@@ -20,6 +20,15 @@ en:
|
|
|
20
20
|
label: "Actions"
|
|
21
21
|
popover:
|
|
22
22
|
trigger: "Open popover"
|
|
23
|
+
toc:
|
|
24
|
+
label: "On this page"
|
|
25
|
+
docs_shell:
|
|
26
|
+
menu: "Menu"
|
|
27
|
+
sidebar: "Documentation sidebar"
|
|
28
|
+
app_header:
|
|
29
|
+
navigation: "Main"
|
|
30
|
+
dock:
|
|
31
|
+
close: "Close %{label}"
|
|
23
32
|
breadcrumb:
|
|
24
33
|
label: "Breadcrumb"
|
|
25
34
|
code_block:
|
data/config/locales/es.yml
CHANGED
|
@@ -20,6 +20,15 @@ es:
|
|
|
20
20
|
label: "Acciones"
|
|
21
21
|
popover:
|
|
22
22
|
trigger: "Abrir panel"
|
|
23
|
+
toc:
|
|
24
|
+
label: "En esta página"
|
|
25
|
+
docs_shell:
|
|
26
|
+
menu: "Menú"
|
|
27
|
+
sidebar: "Barra lateral de la documentación"
|
|
28
|
+
app_header:
|
|
29
|
+
navigation: "Principal"
|
|
30
|
+
dock:
|
|
31
|
+
close: "Cerrar %{label}"
|
|
23
32
|
breadcrumb:
|
|
24
33
|
label: "Ruta de navegación"
|
|
25
34
|
code_block:
|
data/config/locales/ja.yml
CHANGED
|
@@ -20,6 +20,15 @@ ja:
|
|
|
20
20
|
label: "操作"
|
|
21
21
|
popover:
|
|
22
22
|
trigger: "パネルを開く"
|
|
23
|
+
toc:
|
|
24
|
+
label: "このページの内容"
|
|
25
|
+
docs_shell:
|
|
26
|
+
menu: "メニュー"
|
|
27
|
+
sidebar: "ドキュメントのサイドバー"
|
|
28
|
+
app_header:
|
|
29
|
+
navigation: "メイン"
|
|
30
|
+
dock:
|
|
31
|
+
close: "%{label}を閉じる"
|
|
23
32
|
breadcrumb:
|
|
24
33
|
label: "パンくずリスト"
|
|
25
34
|
code_block:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module Helpers
|
|
3
|
-
paths = Dir[Lostcoords::Ui.root.join("app/helpers/lostcoords/ui/*_helper.rb")].sort_by do |path|
|
|
3
|
+
paths = Dir[Lostcoords::Ui::Engine.root.join("app/helpers/lostcoords/ui/*_helper.rb")].sort_by do |path|
|
|
4
4
|
[ %w[tailwind_helper.rb button_helper.rb].index(File.basename(path)) || 2, path ]
|
|
5
5
|
end
|
|
6
6
|
paths.each do |path|
|
data/lib/lostcoords/ui/icons.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Lostcoords::Ui
|
|
|
8
8
|
|
|
9
9
|
def self.fetch(name, variant)
|
|
10
10
|
LOCK.synchronize do
|
|
11
|
-
@catalog ||= JSON.parse(File.read(Lostcoords::Ui.root.join("vendor/heroicons/icons.json")), freeze: true).fetch("icons")
|
|
11
|
+
@catalog ||= JSON.parse(File.read(Lostcoords::Ui::Engine.root.join("vendor/heroicons/icons.json")), freeze: true).fetch("icons")
|
|
12
12
|
source = @catalog.fetch(variant.to_s).fetch(name.to_s)
|
|
13
13
|
template = TEMPLATES[source] ||= begin
|
|
14
14
|
document = Nokogiri::XML(source) { |config| config.strict.nonet }
|
data/lib/lostcoords/ui/parts.rb
CHANGED
|
@@ -195,6 +195,93 @@ module Lostcoords::Ui
|
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
197
|
|
|
198
|
+
class Toc < Base
|
|
199
|
+
LINK = "relative block py-tight ps-content pe-tight text-small text-muted-foreground no-underline transition-colors " \
|
|
200
|
+
"duration-(--lc-motion-quick) hover:text-foreground aria-[current=location]:font-medium aria-[current=location]:text-foreground"
|
|
201
|
+
|
|
202
|
+
# url: an in-page anchor such as "#usage". indent: true nests the entry under the one above it.
|
|
203
|
+
def link(label, url, indent: false, **attributes)
|
|
204
|
+
view.send(:lc_label!, label, :label)
|
|
205
|
+
raise ArgumentError, "toc link needs an anchor URL, such as \"#usage\"" unless url.to_s.match?(/#./)
|
|
206
|
+
|
|
207
|
+
view.tag.li(data: { slot: "toc-item" }) do
|
|
208
|
+
view.link_to(label, url, **merge({ class: view.lc_tw(LINK, ("ps-[calc(var(--spacing-content)*2)]" if indent)),
|
|
209
|
+
data: { slot: "toc-link", lc_toc_target: "link", action: "lc-toc#select" } }, attributes))
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class AppHeader < Base
|
|
215
|
+
# The home link. label: names it for screen readers, since the block is usually a logo.
|
|
216
|
+
def brand(url = "/", label:, **attributes, &block)
|
|
217
|
+
view.send(:lc_label!, label, :label)
|
|
218
|
+
raise ArgumentError, "header brand needs a block" unless block
|
|
219
|
+
|
|
220
|
+
view.link_to(url, **merge({ class: "flex min-h-11 shrink-0 items-center gap-related pe-2 font-semibold tracking-tight text-inherit no-underline",
|
|
221
|
+
aria: { label: label }, data: { slot: "app-header-brand" } }, attributes), &block)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# A row of links with a sliding indicator; yields the lc_nav_list builder. Hide it on phones
|
|
225
|
+
# with class: "max-sm:hidden" when a dock or menu carries the same links.
|
|
226
|
+
def nav(label: view.send(:lc_t, "lostcoords.ui.app_header.navigation"), **attributes, &block)
|
|
227
|
+
view.lc_nav_list(label: label, layout: :row, indicator: true, **attributes, &block)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Controls at the end of the bar, such as search, a theme switch or an account menu.
|
|
231
|
+
def actions(**attributes, &block)
|
|
232
|
+
part(:div, "app-header-actions", "ms-auto flex shrink-0 items-center gap-related", attributes, &block)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Dock < Base
|
|
237
|
+
# The primary destinations, icon above label; yields the lc_nav_list builder.
|
|
238
|
+
def nav(label:, **attributes, &block)
|
|
239
|
+
view.lc_nav_list(label: label, layout: :dock, indicator: true,
|
|
240
|
+
**merge({ class: view.lc_glass_class(shape: :panel, thick: true, extra: "lc-dock__nav rounded-pill") }, attributes), &block)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# A round button at the end of the dock whose actions fan out above it. Yields a builder:
|
|
244
|
+
# `menu.action "New note", icon: "pencil-square"`.
|
|
245
|
+
def menu(label:, icon: "plus", id: nil, **attributes, &block)
|
|
246
|
+
view.send(:lc_label!, label, :label)
|
|
247
|
+
raise ArgumentError, "dock menu needs a block" unless block
|
|
248
|
+
|
|
249
|
+
id ||= "#{label.parameterize.presence || 'dock'}-menu"
|
|
250
|
+
actions = view.capture(DockMenu.new(view), &block)
|
|
251
|
+
view.tag.div(**merge({ class: "lc-dock__menu", data: { slot: "dock-menu", controller: "lc-menu", lc_menu_lift_value: false } }, attributes)) do
|
|
252
|
+
view.safe_join([
|
|
253
|
+
view.tag.button(view.lc_icon(icon, options: { class: "size-7", "aria-hidden": true }), type: "button",
|
|
254
|
+
class: view.lc_glass_class(shape: :disc, thick: true, extra: "lc-dock__trigger"), aria: { label: label, haspopup: "menu", expanded: false, controls: id },
|
|
255
|
+
data: { slot: "dock-menu-trigger", lc_menu_target: "trigger", action: "lc-menu#toggle" }),
|
|
256
|
+
view.tag.div(id: id, class: "lc-dock__actions", role: "menu", aria: { label: label }, data: { lc_menu_target: "popover" }) do
|
|
257
|
+
view.safe_join([
|
|
258
|
+
view.tag.button(type: "button", class: "lc-dock__scrim", tabindex: -1, aria: { label: view.send(:lc_t, "lostcoords.ui.dock.close", label: label) }, data: { action: "lc-menu#close" }),
|
|
259
|
+
actions
|
|
260
|
+
])
|
|
261
|
+
end
|
|
262
|
+
])
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
class DockMenu < Base
|
|
268
|
+
def initialize(view)
|
|
269
|
+
super
|
|
270
|
+
@count = 0
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# An action that fans out above the dock button. Pass data: { action: "…" } for your controller;
|
|
274
|
+
# the menu closes after it runs.
|
|
275
|
+
def action(label, icon:, **attributes)
|
|
276
|
+
view.send(:lc_label!, label, :label)
|
|
277
|
+
@count += 1
|
|
278
|
+
defaults = { class: view.lc_glass_class(shape: :disc, thick: true, extra: "lc-dock__action"), data: { slot: "dock-action", action: "lc-menu#close" } }
|
|
279
|
+
view.tag.button(**merge(defaults, attributes), type: "button", role: "menuitem", style: "--lc-dock-action-index: #{@count}") do
|
|
280
|
+
view.safe_join([ view.tag.span(label, class: "lc-dock__action-label"), view.lc_icon(icon, options: { class: "size-6", "aria-hidden": true }) ])
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
198
285
|
class Panel < Base
|
|
199
286
|
include CustomTrigger
|
|
200
287
|
|
data/lib/lostcoords/ui.rb
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
require "
|
|
2
|
-
require "
|
|
1
|
+
require "rails/engine"
|
|
2
|
+
require "action_view"
|
|
3
|
+
require "active_support/core_ext/hash"
|
|
4
|
+
require "active_support/core_ext/object/blank"
|
|
5
|
+
require "tailwind_merge"
|
|
6
|
+
require "lostcoords/ui/version"
|
|
7
|
+
require "lostcoords/ui/engine"
|
|
8
|
+
require "lostcoords/ui/icons"
|
|
9
|
+
require "lostcoords/ui/parts"
|
|
10
|
+
require "lostcoords/ui/form_builder"
|
|
11
|
+
require "lostcoords/ui/helpers"
|
|
12
|
+
require "lostcoords/ui/stylesheets"
|
|
@@ -15,7 +15,6 @@ namespace :lostcoords do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
# Runs with tailwindcss-rails' own engines step, before every tailwindcss:build and tailwindcss:watch.
|
|
18
|
-
# Apps without tailwindcss-rails import the gem's stylesheets by path instead.
|
|
19
18
|
if defined?(Tailwindcss::Engines)
|
|
20
19
|
namespace :tailwindcss do
|
|
21
20
|
task :engines do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lostcoords-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lost Coordinates
|
|
@@ -24,7 +24,7 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '8.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: railties
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
@@ -99,6 +99,7 @@ files:
|
|
|
99
99
|
- app/assets/tailwind/lostcoords/ui/components/menu.css
|
|
100
100
|
- app/assets/tailwind/lostcoords/ui/components/prose.css
|
|
101
101
|
- app/assets/tailwind/lostcoords/ui/components/search.css
|
|
102
|
+
- app/assets/tailwind/lostcoords/ui/components/shell.css
|
|
102
103
|
- app/assets/tailwind/lostcoords/ui/components/table.css
|
|
103
104
|
- app/assets/tailwind/lostcoords/ui/components/toast.css
|
|
104
105
|
- app/assets/tailwind/lostcoords/ui/components/tooltip.css
|
|
@@ -113,6 +114,7 @@ files:
|
|
|
113
114
|
- app/assets/tailwind/lostcoords/ui/prose.css
|
|
114
115
|
- app/assets/tailwind/lostcoords/ui/search.css
|
|
115
116
|
- app/assets/tailwind/lostcoords/ui/sheet.css
|
|
117
|
+
- app/assets/tailwind/lostcoords/ui/shell.css
|
|
116
118
|
- app/assets/tailwind/lostcoords/ui/squircle.css
|
|
117
119
|
- app/assets/tailwind/lostcoords/ui/table.css
|
|
118
120
|
- app/assets/tailwind/lostcoords/ui/theme.css
|
|
@@ -145,6 +147,7 @@ files:
|
|
|
145
147
|
- app/helpers/lostcoords/ui/render_attributes_helper.rb
|
|
146
148
|
- app/helpers/lostcoords/ui/search_helper.rb
|
|
147
149
|
- app/helpers/lostcoords/ui/sheet_helper.rb
|
|
150
|
+
- app/helpers/lostcoords/ui/shell_helper.rb
|
|
148
151
|
- app/helpers/lostcoords/ui/structures_helper.rb
|
|
149
152
|
- app/helpers/lostcoords/ui/table_helper.rb
|
|
150
153
|
- app/helpers/lostcoords/ui/tailwind_helper.rb
|
|
@@ -152,10 +155,12 @@ files:
|
|
|
152
155
|
- app/helpers/lostcoords/ui/tooltip_helper.rb
|
|
153
156
|
- app/helpers/lostcoords/ui/translation_helper.rb
|
|
154
157
|
- app/helpers/lostcoords/ui/typography_helper.rb
|
|
158
|
+
- app/javascript/lostcoords/ui/controllers/lc_app_header_controller.js
|
|
155
159
|
- app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js
|
|
156
160
|
- app/javascript/lostcoords/ui/controllers/lc_collapsible_controller.js
|
|
157
161
|
- app/javascript/lostcoords/ui/controllers/lc_copy_controller.js
|
|
158
162
|
- app/javascript/lostcoords/ui/controllers/lc_dialog_controller.js
|
|
163
|
+
- app/javascript/lostcoords/ui/controllers/lc_dock_controller.js
|
|
159
164
|
- app/javascript/lostcoords/ui/controllers/lc_emoji_picker_controller.js
|
|
160
165
|
- app/javascript/lostcoords/ui/controllers/lc_kbd_controller.js
|
|
161
166
|
- app/javascript/lostcoords/ui/controllers/lc_menu_controller.js
|
|
@@ -169,6 +174,7 @@ files:
|
|
|
169
174
|
- app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js
|
|
170
175
|
- app/javascript/lostcoords/ui/controllers/lc_toast_controller.js
|
|
171
176
|
- app/javascript/lostcoords/ui/controllers/lc_toast_region_controller.js
|
|
177
|
+
- app/javascript/lostcoords/ui/controllers/lc_toc_controller.js
|
|
172
178
|
- app/javascript/lostcoords/ui/controllers/lc_toggle_controller.js
|
|
173
179
|
- app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js
|
|
174
180
|
- app/javascript/lostcoords/ui/helpers/catalog_picker.js
|
|
@@ -200,7 +206,6 @@ files:
|
|
|
200
206
|
- config/locales/es.yml
|
|
201
207
|
- config/locales/ja.yml
|
|
202
208
|
- lib/lostcoords/ui.rb
|
|
203
|
-
- lib/lostcoords/ui/action_view.rb
|
|
204
209
|
- lib/lostcoords/ui/builders.rb
|
|
205
210
|
- lib/lostcoords/ui/components.rb
|
|
206
211
|
- lib/lostcoords/ui/engine.rb
|
|
@@ -208,8 +213,6 @@ files:
|
|
|
208
213
|
- lib/lostcoords/ui/helpers.rb
|
|
209
214
|
- lib/lostcoords/ui/icons.rb
|
|
210
215
|
- lib/lostcoords/ui/parts.rb
|
|
211
|
-
- lib/lostcoords/ui/rails.rb
|
|
212
|
-
- lib/lostcoords/ui/resources.rb
|
|
213
216
|
- lib/lostcoords/ui/stylesheets.rb
|
|
214
217
|
- lib/lostcoords/ui/tokens.rb
|
|
215
218
|
- lib/lostcoords/ui/version.rb
|
|
@@ -241,5 +244,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
241
244
|
requirements: []
|
|
242
245
|
rubygems_version: 4.0.6
|
|
243
246
|
specification_version: 4
|
|
244
|
-
summary:
|
|
247
|
+
summary: Rails UI components with Tailwind CSS and Stimulus.
|
|
245
248
|
test_files: []
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
require "bigdecimal"
|
|
2
|
-
require "action_view"
|
|
3
|
-
require "active_support/core_ext/hash"
|
|
4
|
-
require "active_support/core_ext/object/blank"
|
|
5
|
-
require "tailwind_merge"
|
|
6
|
-
require_relative "resources"
|
|
7
|
-
require_relative "icons"
|
|
8
|
-
require_relative "parts"
|
|
9
|
-
require_relative "form_builder"
|
|
10
|
-
require_relative "helpers"
|
|
11
|
-
require_relative "stylesheets"
|
|
12
|
-
|
|
13
|
-
Lostcoords::Ui.load_translations!
|
data/lib/lostcoords/ui/rails.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
require "pathname"
|
|
2
|
-
require_relative "version"
|
|
3
|
-
|
|
4
|
-
module Lostcoords::Ui
|
|
5
|
-
def self.root
|
|
6
|
-
@root ||= Pathname.new(File.expand_path("../../..", __dir__)).freeze
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def self.view_paths
|
|
10
|
-
[ root.join("app/views").to_s ]
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.load_translations!
|
|
14
|
-
paths = (Dir[root.join("config/locales/*.yml")] + I18n.load_path).uniq
|
|
15
|
-
I18n.load_path = paths unless I18n.load_path == paths
|
|
16
|
-
end
|
|
17
|
-
end
|