lostcoords-ui 0.4.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +352 -0
- data/app/assets/tailwind/lostcoords/ui/avatar.css +11 -0
- data/app/assets/tailwind/lostcoords/ui/card.css +26 -0
- data/app/assets/tailwind/lostcoords/ui/copy-button.css +33 -0
- data/app/assets/tailwind/lostcoords/ui/dialog.css +60 -0
- data/app/assets/tailwind/lostcoords/ui/disclosure.css +43 -0
- data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +462 -0
- data/app/assets/tailwind/lostcoords/ui/field.css +6 -0
- data/app/assets/tailwind/lostcoords/ui/glass.css +37 -0
- data/app/assets/tailwind/lostcoords/ui/layout.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/popover.css +75 -0
- data/app/assets/tailwind/lostcoords/ui/reading.css +60 -0
- data/app/assets/tailwind/lostcoords/ui/sheet.css +170 -0
- data/app/assets/tailwind/lostcoords/ui/squircle.css +32 -0
- data/app/assets/tailwind/lostcoords/ui/theme.css +255 -0
- data/app/assets/tailwind/lostcoords/ui/typography.css +81 -0
- data/app/assets/tailwind/lostcoords/ui/ui.css +21 -0
- data/app/assets/tailwind/lostcoords/ui/utilities.css +45 -0
- data/app/assets/tailwind/lostcoords_ui/engine.css +1 -0
- data/app/helpers/lostcoords/ui/badge_helper.rb +162 -0
- data/app/helpers/lostcoords/ui/button_helper.rb +70 -0
- data/app/helpers/lostcoords/ui/card_helper.rb +60 -0
- data/app/helpers/lostcoords/ui/copy_button_helper.rb +40 -0
- data/app/helpers/lostcoords/ui/dialog_helper.rb +33 -0
- data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +245 -0
- data/app/helpers/lostcoords/ui/feedback_helper.rb +30 -0
- data/app/helpers/lostcoords/ui/form_helper.rb +93 -0
- data/app/helpers/lostcoords/ui/glass_helper.rb +25 -0
- data/app/helpers/lostcoords/ui/icon_helper.rb +15 -0
- data/app/helpers/lostcoords/ui/layout_helper.rb +60 -0
- data/app/helpers/lostcoords/ui/menu_helper.rb +41 -0
- data/app/helpers/lostcoords/ui/primitives_helper.rb +152 -0
- data/app/helpers/lostcoords/ui/reading_helper.rb +15 -0
- data/app/helpers/lostcoords/ui/render_attributes_helper.rb +46 -0
- data/app/helpers/lostcoords/ui/sheet_helper.rb +54 -0
- data/app/helpers/lostcoords/ui/structures_helper.rb +159 -0
- data/app/helpers/lostcoords/ui/tailwind_helper.rb +14 -0
- data/app/helpers/lostcoords/ui/translation_helper.rb +13 -0
- data/app/helpers/lostcoords/ui/typography_helper.rb +28 -0
- data/app/javascript/lostcoords/ui/controllers/confirm_dialog_controller.js +39 -0
- data/app/javascript/lostcoords/ui/controllers/disclosure_controller.js +163 -0
- data/app/javascript/lostcoords/ui/controllers/emoji_picker_controller.js +366 -0
- data/app/javascript/lostcoords/ui/controllers/modal_controller.js +27 -0
- data/app/javascript/lostcoords/ui/controllers/row_menu_controller.js +83 -0
- data/app/javascript/lostcoords/ui/controllers/sheet_controller.js +422 -0
- data/app/javascript/lostcoords/ui/controllers/toast_controller.js +69 -0
- data/app/javascript/lostcoords/ui/controllers/ui_copy_controller.js +56 -0
- data/app/javascript/lostcoords/ui/controllers/ui_popover_controller.js +17 -0
- data/app/javascript/lostcoords/ui/controllers/ui_selection_controller.js +69 -0
- data/app/javascript/lostcoords/ui/controllers/ui_toggle_controller.js +13 -0
- data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +455 -0
- data/app/javascript/lostcoords/ui/helpers/context_dialog.js +29 -0
- data/app/javascript/lostcoords/ui/helpers/dialog.js +31 -0
- data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +155 -0
- data/app/javascript/lostcoords/ui/helpers/floating_panel.js +108 -0
- data/app/javascript/lostcoords/ui/helpers/menu_keys.js +27 -0
- data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +13 -0
- data/app/javascript/lostcoords/ui/helpers/top_layer.js +102 -0
- data/app/javascript/lostcoords/ui/helpers/viewport.js +13 -0
- data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +176 -0
- data/app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb +29 -0
- data/app/views/lostcoords/ui/components/_form_alert.html.erb +4 -0
- data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +13 -0
- data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +5 -0
- data/app/views/lostcoords/ui/components/_skeleton.html.erb +4 -0
- data/app/views/lostcoords/ui/components/_spinner.html.erb +10 -0
- data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +8 -0
- data/config/emoji_search_aliases.yml +146 -0
- data/config/emoji_search_names.yml +1917 -0
- data/config/emoji_search_names_ja.yml +6902 -0
- data/config/helper_renames.json +113 -0
- data/config/importmap.rb +3 -0
- data/config/locales/en.yml +33 -0
- data/config/locales/es.yml +33 -0
- data/config/locales/ja.yml +33 -0
- data/lib/lostcoords/ui/action_view.rb +10 -0
- data/lib/lostcoords/ui/builders.rb +42 -0
- data/lib/lostcoords/ui/components.rb +13 -0
- data/lib/lostcoords/ui/engine.rb +22 -0
- data/lib/lostcoords/ui/helpers.rb +11 -0
- data/lib/lostcoords/ui/icons.rb +24 -0
- data/lib/lostcoords/ui/rails.rb +3 -0
- data/lib/lostcoords/ui/resources.rb +17 -0
- data/lib/lostcoords/ui/tokens.rb +14 -0
- data/lib/lostcoords/ui/version.rb +5 -0
- data/lib/lostcoords/ui.rb +2 -0
- data/lib/tasks/lostcoords_ui.rake +15 -0
- data/script/migrate_helpers.rb +136 -0
- data/vendor/heroicons/LICENSE +21 -0
- data/vendor/heroicons/icons.json +1303 -0
- metadata +203 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7b432f8fc71804447fa32cf3290c7db2556243ef42c6f6849fa7604ed5081870
|
|
4
|
+
data.tar.gz: 5cf53096cc4479889f98f4764d6dac7391e787b0a3fe4bb2c21f23e5201d2918
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: aa29b884d7e07cc1204088de38ace739805f0b5d7ff853bdd2196f9787c65ad71c80005e653180840c877e880d21b66159b8c660fcab0846dc9a34715236d8b4
|
|
7
|
+
data.tar.gz: 4da2f287536853733eac9ee133fd16b2627d0f54e4c2cfc2bdd2d254588b4ab51f9631191b1558f35ca52b6fbb85ec2caade6674d1987c8eb51cc4ebc07989ec
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lost Coordinates
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# lostcoords-ui
|
|
2
|
+
|
|
3
|
+
Render complete UI components from Rails or standalone ActionView ERB. Helpers include semantic HTML,
|
|
4
|
+
accessible attributes and Stimulus wiring. Tailwind v4 supplies the styles. Rails integrates Propshaft
|
|
5
|
+
and importmap automatically; standalone applications own their asset build.
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= lc_button "Save changes", type: :submit, icon: "check" %>
|
|
9
|
+
<%= lc_button_link_to "Projects", projects_path, variant: :secondary %>
|
|
10
|
+
|
|
11
|
+
<%= lc_card do %>
|
|
12
|
+
<%= lc_field label: "Project name", id: "project-name" do |field| %>
|
|
13
|
+
<%= lc_input name: "project[name]", **field %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The supported stack is ActionView, ERB, Tailwind and Stimulus. Each host application's lockfile pins
|
|
19
|
+
its gem version, so publishing a library update does not upgrade consumers.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem "lostcoords-ui", "~> 0.4.0"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The engine adds its rendering helpers to host views, its JavaScript to the asset paths and its pins to
|
|
28
|
+
the importmap. Translations live under `lostcoords.ui.*`.
|
|
29
|
+
|
|
30
|
+
### Standalone ActionView
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "lostcoords/ui/action_view"
|
|
34
|
+
|
|
35
|
+
class ViewContext < ActionView::Base
|
|
36
|
+
include Lostcoords::Ui::Helpers
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
view = ViewContext.with_empty_template_cache.with_view_paths(Lostcoords::Ui.view_paths)
|
|
40
|
+
view.lc_button("Save", icon: "check")
|
|
41
|
+
```
|
|
42
|
+
|
|
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
|
+
### Stylesheet
|
|
53
|
+
|
|
54
|
+
Tailwind CSS v4 is required. Use `tailwindcss-rails` 4.4 or later to compile the component styles and
|
|
55
|
+
your app's utilities. Add the gem's entry to `app/assets/tailwind/application.css`:
|
|
56
|
+
|
|
57
|
+
```css
|
|
58
|
+
@layer theme, base, layout, components, modules, utilities;
|
|
59
|
+
|
|
60
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
61
|
+
@import "tailwindcss/preflight.css" layer(base);
|
|
62
|
+
@import "tailwindcss/utilities.css" layer(utilities);
|
|
63
|
+
|
|
64
|
+
@plugin "@tailwindcss/typography";
|
|
65
|
+
|
|
66
|
+
@import "../builds/tailwind/lostcoords_ui.css";
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Tailwind resolves `@import` by path, and Bundler decides where the gem lives. tailwindcss-rails' `tailwindcss:engines`
|
|
70
|
+
task, which runs before every `tailwindcss:build` and `tailwindcss:watch` (and so before `assets:precompile`), writes
|
|
71
|
+
`app/assets/builds/tailwind/lostcoords_ui.css` with the absolute path to the gem's
|
|
72
|
+
`app/assets/tailwind/lostcoords_ui/engine.css`. `app/assets/builds` is already ignored by a Rails app's `.gitignore`
|
|
73
|
+
and `.dockerignore`, so there is nothing to commit. The file does not exist until the first Tailwind build, so a lint
|
|
74
|
+
that checks imports should skip it.
|
|
75
|
+
|
|
76
|
+
The gem's stylesheets write into the `components` and `modules` layers, so the host declares that layer order.
|
|
77
|
+
The theme defines the `dark:` variant with `html[data-theme="dark"]`. `ui.css` carries its own `@source` lines, so Tailwind
|
|
78
|
+
scans the gem's helpers, views and JavaScript wherever the gem is installed.
|
|
79
|
+
|
|
80
|
+
### Theming
|
|
81
|
+
|
|
82
|
+
The engine includes a complete light and dark theme, system fonts, shadows, glass effects and motion
|
|
83
|
+
settings. Components work without an app-owned token file. To activate dark mode, set
|
|
84
|
+
`data-theme="dark"` on `<html>`. Your app controls the switch and saved preference.
|
|
85
|
+
|
|
86
|
+
To customize the theme, add CSS variable overrides after the engine import:
|
|
87
|
+
|
|
88
|
+
```css
|
|
89
|
+
:root {
|
|
90
|
+
--sky: oklch(52% 0.14 155);
|
|
91
|
+
--sky-deep: oklch(46% 0.14 155);
|
|
92
|
+
--sky-soft: oklch(96% 0.02 155);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
html[data-theme="dark"] {
|
|
96
|
+
--sky: oklch(75% 0.14 155);
|
|
97
|
+
--sky-deep: oklch(70% 0.14 155);
|
|
98
|
+
--sky-soft: color-mix(in oklab, var(--sky) 18%, var(--paper));
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Override only the values you need. The [theming guide](https://ui.lostcoords.com/theming.html) covers
|
|
103
|
+
colors, fonts, corners, shadows and advanced variables. Defaults live in
|
|
104
|
+
`app/assets/tailwind/lostcoords/ui/theme.css`. `Lostcoords::Ui::TOKENS` lists the variables in that file
|
|
105
|
+
for reference; apps do not have to define them.
|
|
106
|
+
|
|
107
|
+
### Importmap
|
|
108
|
+
|
|
109
|
+
The engine pins its modules under the host's own keys, `controllers/*` and `helpers/*`, so
|
|
110
|
+
`eagerLoadControllersFrom("controllers", application)` registers the gem's controllers with no extra wiring.
|
|
111
|
+
A host file with the same name, such as `app/javascript/controllers/sheet_controller.js`, shadows the gem's.
|
|
112
|
+
|
|
113
|
+
## Components
|
|
114
|
+
|
|
115
|
+
[Installation and setup](https://ui.lostcoords.com/installation.html) · [Live examples](https://ui.lostcoords.com). Each example renders the same ERB source shown
|
|
116
|
+
in its code panel. The API reference reads signatures from the helper definitions.
|
|
117
|
+
[API JSON](https://ui.lostcoords.com/api.json) and [llms.txt](https://ui.lostcoords.com/llms.txt)
|
|
118
|
+
include those signatures and the runnable examples.
|
|
119
|
+
|
|
120
|
+
Buttons use `primary`, `secondary`, `ghost` or `destructive`, with `sm`, `base` or `lg` sizes.
|
|
121
|
+
`lc_button_to` preserves Rails' form, HTTP method and CSRF behavior. Block helpers capture ERB content.
|
|
122
|
+
Use `icon: "plus"` for a leading Heroicon or `icon_position: :end` for a trailing one. Custom icons and
|
|
123
|
+
spinners use `data-icon="inline-start"` or `data-icon="inline-end"`; buttons adjust that edge's padding.
|
|
124
|
+
|
|
125
|
+
```erb
|
|
126
|
+
<%= lc_button_to "Archive", project_path(@project), method: :delete, variant: :destructive %>
|
|
127
|
+
<%= lc_icon_button "Settings", icon: "cog-6-tooth" %>
|
|
128
|
+
|
|
129
|
+
<%= lc_button class: "w-full", data: { action: "editor#save" }, aria: { busy: true } do %>
|
|
130
|
+
<%= lc_spinner size: :sm %>
|
|
131
|
+
Saving…
|
|
132
|
+
<% end %>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use native attributes for application behavior, `class:` to merge additional Tailwind utilities, and
|
|
136
|
+
theme tokens to restyle the library. `lc_field` yields the input's `id` and validation attributes so
|
|
137
|
+
labels, hints and errors stay connected. Linked cards use `lc_card(href: ...)`; cards containing
|
|
138
|
+
buttons or fields remain ordinary containers.
|
|
139
|
+
|
|
140
|
+
## Copy feedback and motion
|
|
141
|
+
|
|
142
|
+
```erb
|
|
143
|
+
<%= lc_copy_button value: "https://example.com/invite" %>
|
|
144
|
+
<%= lc_copy_button "Copy invite link", value: invite_url, copied_label: "Link copied" %>
|
|
145
|
+
<%= lc_copy_button "Copy link", value: invite_url, icon_only: true, variant: :ghost %>
|
|
146
|
+
<%= lc_copy_button "Copy code", source: "code-snippet", reset_after: 2500 %>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Pass exactly one of `value:` (literal text) or `source:` (an element ID). The source is read at click time:
|
|
150
|
+
inputs and textareas use their current value; other elements use their text content. Text is copied as supplied,
|
|
151
|
+
including whitespace. Clipboard access requires HTTPS or localhost and browser permission.
|
|
152
|
+
|
|
153
|
+
After a successful copy, the button transitions to a check and "Copied", then resets after `reset_after:`
|
|
154
|
+
milliseconds (2000 by default). Failure shows a retry icon and label with an accessible explanation.
|
|
155
|
+
The button reserves space for all labels, preserves focus, and announces the outcome through a live region.
|
|
156
|
+
Default labels are translated in English, Japanese and Spanish. Use `label` (the first argument),
|
|
157
|
+
`copied_label:` and `error_label:` to customize visible text, and `aria: { label: ... }` for a contextual name.
|
|
158
|
+
Normal button variants, sizes, classes, disabled state and attributes are supported.
|
|
159
|
+
|
|
160
|
+
The component emits bubbling `ui-copy:copied` (`detail.text`) and `ui-copy:error` (`detail.error`) events
|
|
161
|
+
from its wrapper. Listen on an ancestor to connect application behavior. The documentation uses the error
|
|
162
|
+
event to reveal clipped source code for manual copying.
|
|
163
|
+
|
|
164
|
+
Buttons include hover and press feedback. Override `--ui-motion-quick` (140ms), `--ui-motion-control` (180ms),
|
|
165
|
+
`--ui-motion-feedback` (160ms), `--ui-press-scale` (0.96) and `--ease-out` in your theme or on a container.
|
|
166
|
+
Reduced motion removes control movement and makes copy feedback immediate. Your application owns the
|
|
167
|
+
pending, success and failure states of network actions such as saving a form.
|
|
168
|
+
|
|
169
|
+
[Try the copy button](https://ui.lostcoords.com/components/copy_button.html) ·
|
|
170
|
+
[Motion reference](https://ui.lostcoords.com/components/motion.html)
|
|
171
|
+
|
|
172
|
+
### Icons
|
|
173
|
+
|
|
174
|
+
`lc_icon` renders Heroicons v2.2.0 SVGs. Variants are `outline` (the default), `solid`, `mini` and `micro`.
|
|
175
|
+
Pass SVG attributes through `options:` and existing path attributes through `path_options:`.
|
|
176
|
+
|
|
177
|
+
```erb
|
|
178
|
+
<%= lc_icon "check", options: { class: "size-5" } %>
|
|
179
|
+
<%= lc_icon "check", variant: :mini, options: {
|
|
180
|
+
class: "size-4", role: "img", aria: { label: "Complete", hidden: false }
|
|
181
|
+
} %>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Advanced style composition
|
|
185
|
+
|
|
186
|
+
### Typography and layout
|
|
187
|
+
|
|
188
|
+
The engine stylesheet includes shared text styles and layout defaults. Heading level controls document
|
|
189
|
+
structure independently of its visual size. Layout parents own the spacing between children, including fields.
|
|
190
|
+
Inline `<code>` elements get a shared monospace style with a theme-aware background. `<pre><code>`
|
|
191
|
+
blocks keep their block styling.
|
|
192
|
+
|
|
193
|
+
```erb
|
|
194
|
+
<%= lc_stack gap: :section do %>
|
|
195
|
+
<%= lc_heading "Workspace", level: 1, size: :page %>
|
|
196
|
+
<%= lc_section title: "Members" do %>
|
|
197
|
+
<%= lc_card do %>
|
|
198
|
+
<%= lc_stack gap: :related do %>
|
|
199
|
+
<%= lc_heading "Design team", level: 3, size: :card %>
|
|
200
|
+
<%= lc_text "Three people share this workspace.", tone: :muted %>
|
|
201
|
+
<% end %>
|
|
202
|
+
<% end %>
|
|
203
|
+
<% end %>
|
|
204
|
+
<% end %>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Heading sizes are `page`, `section` and `card`. Text sizes are `body`, `small` and `caption`,
|
|
208
|
+
with `default`, `muted` and `subtle` tones. Stacks and inline groups accept `tight` (4px), `related`
|
|
209
|
+
(8px), `heading` (12px), `content` (16px), `inset` (20px) and `section` (32px) gaps at the default
|
|
210
|
+
browser font size. `lc_section` leaves 12px below its heading and 16px between content blocks.
|
|
211
|
+
`lc_container` provides `page`, `reading` and `form` widths. Only the page container adds gutters.
|
|
212
|
+
|
|
213
|
+
Fonts and colours come from the shared theme and your app's overrides. Optional custom properties override the shared defaults:
|
|
214
|
+
`--ui-space-section`, `--ui-heading-page-size`, `--ui-heading-page-line-height`, `--ui-page-max`,
|
|
215
|
+
`--ui-gutter` and `--ui-reading-measure`. Set them in the host theme or on a container. Japanese headings
|
|
216
|
+
inherit `lang="ja"` and use natural letter spacing.
|
|
217
|
+
|
|
218
|
+
[Browse the foundations examples](https://ui.lostcoords.com/components/foundations.html).
|
|
219
|
+
|
|
220
|
+
### Reading content
|
|
221
|
+
|
|
222
|
+
For articles or rendered Markdown, enable Tailwind Typography in the host input:
|
|
223
|
+
|
|
224
|
+
```css
|
|
225
|
+
@plugin "@tailwindcss/typography";
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The standalone Tailwind CLI used by `tailwindcss-rails` includes this plugin. `lc_prose` supplies the host
|
|
229
|
+
theme colours, a 65ch maximum width, 1.75 line height and proportional block spacing. Its block follows
|
|
230
|
+
Rails' normal escaping rules; sanitise rendered user content before marking it safe.
|
|
231
|
+
|
|
232
|
+
```erb
|
|
233
|
+
<%= lc_prose do %>
|
|
234
|
+
<h2>Share a workspace</h2>
|
|
235
|
+
<p>Invite your team and keep your work together.</p>
|
|
236
|
+
<div class="not-prose"><%= lc_button "Invite someone" %></div>
|
|
237
|
+
<% end %>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Override `--ui-reading-size`, `--ui-reading-leading`, `--ui-reading-flow` and `--ui-reading-measure`
|
|
241
|
+
to tune a reading context. Use `not-prose` for embedded components.
|
|
242
|
+
|
|
243
|
+
### Class helpers
|
|
244
|
+
|
|
245
|
+
Class helpers remain available for custom markup. The application owns its semantics and behavior.
|
|
246
|
+
`lc_glass_class` supplies translucent surfaces; `lc_tw` merges utilities with the last class winning.
|
|
247
|
+
|
|
248
|
+
```erb
|
|
249
|
+
<nav class="<%= lc_glass_class(shape: :bar) %>" aria-label="Project navigation">
|
|
250
|
+
<%= link_to "Overview", project_path(@project), class: lc_button_class(variant: :ghost) %>
|
|
251
|
+
</nav>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Generated by `bin/rails lostcoords:ui:builders`; `bin/lint` checks this list against the style helpers.
|
|
255
|
+
|
|
256
|
+
<!-- builders -->
|
|
257
|
+
- `lc_icon_badge_class(size: :base, extra: nil)` (BadgeHelper)
|
|
258
|
+
- `lc_icon_medallion_class(size: :sm, tone: :accent, extra: nil)` (BadgeHelper)
|
|
259
|
+
- `lc_avatar_class(size: :base, ink: false, linked: false, extra: nil)` (BadgeHelper)
|
|
260
|
+
- `lc_avatar_stack_class(compact: false, extra: nil)` (BadgeHelper)
|
|
261
|
+
- `lc_avatar_stack_row_class(extra: nil)` (BadgeHelper)
|
|
262
|
+
- `lc_eyebrow_class(extra: nil)` (BadgeHelper)
|
|
263
|
+
- `lc_badge_class(tone: :neutral, extra: nil)` (BadgeHelper)
|
|
264
|
+
- `lc_status_pill_class(tone: :accent, extra: nil)` (BadgeHelper)
|
|
265
|
+
- `lc_pill_class(active: false, ghost: false, size: :base, extra: nil)` (BadgeHelper)
|
|
266
|
+
- `lc_pill_row_class(extra: nil)` (BadgeHelper)
|
|
267
|
+
- `lc_button_class(variant: :default, size: :base, block: false, extra: nil)` (ButtonHelper)
|
|
268
|
+
- `lc_icon_button_class(shape: :square, size: :base, extra: nil)` (ButtonHelper)
|
|
269
|
+
- `lc_card_class(interactive: false, dense: false, extra: nil)` (CardHelper)
|
|
270
|
+
- `lc_card_surface_class(extra: nil)` (CardHelper)
|
|
271
|
+
- `lc_form_card_class(padding: :default, extra: nil)` (CardHelper)
|
|
272
|
+
- `lc_dialog_class(kind = :modal, extra: nil)` (DialogHelper)
|
|
273
|
+
- `lc_dialog_surface_class(extra: nil)` (DialogHelper)
|
|
274
|
+
- `lc_emoji_picker_groups` (EmojiPickerHelper)
|
|
275
|
+
- `lc_emoji_picker_count` (EmojiPickerHelper)
|
|
276
|
+
- `lc_emoji_picker_version` (EmojiPickerHelper)
|
|
277
|
+
- `lc_emoji_picker_search_text(emoji)` (EmojiPickerHelper)
|
|
278
|
+
- `lc_emoji_picker_search_name(emoji)` (EmojiPickerHelper)
|
|
279
|
+
- `lc_emoji_picker_search_labels(emoji)` (EmojiPickerHelper)
|
|
280
|
+
- `lc_emoji_picker_toneable?(emoji)` (EmojiPickerHelper)
|
|
281
|
+
- `lc_spinner_class(size: :base, extra: nil)` (FeedbackHelper)
|
|
282
|
+
- `lc_skeleton_class(extra: nil)` (FeedbackHelper)
|
|
283
|
+
- `lc_empty_state_class(extra: nil)` (FeedbackHelper)
|
|
284
|
+
- `lc_segmented_class(extra: nil)` (FormHelper)
|
|
285
|
+
- `lc_segmented_tab_class(active: false, extra: nil)` (FormHelper)
|
|
286
|
+
- `lc_input_class(size: :base, extra: nil)` (FormHelper)
|
|
287
|
+
- `lc_dropdown_trigger_class(size: :base, extra: nil)` (FormHelper)
|
|
288
|
+
- `lc_select_class(size: :base, extra: nil)` (FormHelper)
|
|
289
|
+
- `lc_textarea_class(extra: nil)` (FormHelper)
|
|
290
|
+
- `lc_field_class(extra: nil)` (FormHelper)
|
|
291
|
+
- `lc_field_label_class(extra: nil)` (FormHelper)
|
|
292
|
+
- `lc_form_actions_class(extra: nil)` (FormHelper)
|
|
293
|
+
- `lc_form_alert_class(extra: nil)` (FormHelper)
|
|
294
|
+
- `lc_form_alert_icon(tone: :error)` (FormHelper)
|
|
295
|
+
- `lc_glass_class(shape: :bar, thick: false, extra: nil)` (GlassHelper)
|
|
296
|
+
- `lc_stack_class(gap: :content, extra: nil)` (LayoutHelper)
|
|
297
|
+
- `lc_inline_class(gap: :related, extra: nil)` (LayoutHelper)
|
|
298
|
+
- `lc_container_class(size: :page, extra: nil)` (LayoutHelper)
|
|
299
|
+
- `lc_section_header_class(extra: nil)` (LayoutHelper)
|
|
300
|
+
- `lc_popover_class(align: :end, extra: nil)` (MenuHelper)
|
|
301
|
+
- `lc_menu_popover_class(align: :end, extra: nil)` (MenuHelper)
|
|
302
|
+
- `lc_menu_option_class(tone: :default, extra: nil)` (MenuHelper)
|
|
303
|
+
- `lc_prose_class(extra: nil)` (ReadingHelper)
|
|
304
|
+
- `lc_sheet_data(data = {}, detents: "fit")` (SheetHelper)
|
|
305
|
+
- `lc_sheet_row_class(extra: nil)` (SheetHelper)
|
|
306
|
+
- `lc_sheet_row_label_class(extra: nil)` (SheetHelper)
|
|
307
|
+
- `lc_sheet_chip_class(active: false, member: false, extra: nil)` (SheetHelper)
|
|
308
|
+
- `lc_sheet_datetime_input_class(extra: nil)` (SheetHelper)
|
|
309
|
+
- `lc_sheet_text_input_class(extra: nil)` (SheetHelper)
|
|
310
|
+
- `lc_tw(*classes)` (TailwindHelper)
|
|
311
|
+
- `lc_heading_class(size: :section, extra: nil)` (TypographyHelper)
|
|
312
|
+
- `lc_text_class(size: :body, tone: :default, extra: nil)` (TypographyHelper)
|
|
313
|
+
<!-- /builders -->
|
|
314
|
+
|
|
315
|
+
## Blocks
|
|
316
|
+
|
|
317
|
+
[Browse the blocks](https://ui.lostcoords.com/blocks.html) for complete layouts assembled from the
|
|
318
|
+
existing components. Each block has an interactive preview and the ERB, CSS and JavaScript that
|
|
319
|
+
render it. Copy the files into your app and adapt the content, routes and actions.
|
|
320
|
+
|
|
321
|
+
Blocks live in the docs app. They add no rendering helpers or installable block APIs to the gem.
|
|
322
|
+
|
|
323
|
+
## Docs
|
|
324
|
+
|
|
325
|
+
The docs app lives in `test/dummy`. Run `parcela up --detach` and open https://ui.localhost.
|
|
326
|
+
Parcela starts Rails and the Tailwind watcher. `parcela down` stops this environment.
|
|
327
|
+
|
|
328
|
+
`bin/rails lostcoords:ui:showcase` exports the home, component pages, blocks, token reference, machine-readable
|
|
329
|
+
API and assets to `docs/` for GitHub Pages. CI checks that the export matches the source and that every
|
|
330
|
+
public rendering helper has a runnable example.
|
|
331
|
+
|
|
332
|
+
## Development
|
|
333
|
+
|
|
334
|
+
```sh
|
|
335
|
+
bundle install && npm ci
|
|
336
|
+
bin/rails server # the dummy app in test/dummy
|
|
337
|
+
bin/rails tailwindcss:build
|
|
338
|
+
bin/test
|
|
339
|
+
bin/lint
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The helper migration tool updates Ruby calls, ERB code, and Ruby/ERB Markdown examples to the `lc_`
|
|
343
|
+
namespace. Run it in a consumer checkout to preview changes, then add `--write` to apply them.
|
|
344
|
+
Review dynamic method names stored in strings separately.
|
|
345
|
+
|
|
346
|
+
```sh
|
|
347
|
+
ruby "$(bundle show lostcoords-ui)/script/migrate_helpers.rb" .
|
|
348
|
+
ruby "$(bundle show lostcoords-ui)/script/migrate_helpers.rb" . --write
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The icon catalog and its MIT license live in `vendor/heroicons`. Regenerate them from the pinned
|
|
352
|
+
upstream archive with `ruby bin/update-icons`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* A linked avatar's ring draws inside its own footprint, so it takes exactly the space of an
|
|
2
|
+
unlinked one: the outline sits on the edge, over any cutout border, and the inset shadow
|
|
3
|
+
paints the gap in the surface colour. Contexts that already cut the avatar out with a
|
|
4
|
+
border set --avatar-ring-inset (or --avatar-ring-gap) so the gap doesn't double up. */
|
|
5
|
+
@utility avatar-ring {
|
|
6
|
+
--avatar-ring: clamp(1.5px, calc(var(--avatar-size, var(--avatar-size-default, --spacing(8))) * 0.06), 3px);
|
|
7
|
+
|
|
8
|
+
outline: var(--avatar-ring) solid color-mix(in oklab, var(--sky) 74%, var(--paper));
|
|
9
|
+
outline-offset: calc(var(--avatar-ring) * -1);
|
|
10
|
+
box-shadow: inset 0 0 0 var(--avatar-ring-inset, calc(var(--avatar-ring) * var(--avatar-ring-gap, 2))) var(--avatar-cutout, var(--paper));
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Shared paper-card chrome. Used as a bare `.card-surface` hook in ~17
|
|
3
|
+
places across many feature partials (group lists, transactions, overview
|
|
4
|
+
cards and currency tools. Kept authored — not folded into
|
|
5
|
+
card_surface_class — because several call sites override the border
|
|
6
|
+
(`border-transparent`), background or radius with utilities that must
|
|
7
|
+
out-cascade this base; a utilities-layer helper emitting a `[border:…]`
|
|
8
|
+
shorthand would defeat those overrides. Semantic classes set the
|
|
9
|
+
--card-surface-* vars for local tone. (Form cards use form_card_class,
|
|
10
|
+
which carries this same chrome plus padding/container/error border.) */
|
|
11
|
+
.card-surface {
|
|
12
|
+
--avatar-cutout: var(--card-surface-bg, var(--paper-2));
|
|
13
|
+
background: var(--card-surface-bg, var(--paper-2));
|
|
14
|
+
border: var(--card-surface-border, 1px solid transparent);
|
|
15
|
+
border-radius: var(--card-surface-radius, var(--radius-3xl));
|
|
16
|
+
box-shadow: var(--card-surface-shadow, none);
|
|
17
|
+
transition:
|
|
18
|
+
background 0.18s var(--ease-out),
|
|
19
|
+
border-color 0.18s var(--ease-out),
|
|
20
|
+
box-shadow 0.18s var(--ease-out);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.form-card + .form-card { margin-block-start: 1rem; }
|
|
24
|
+
|
|
25
|
+
.form-submit { margin-block-start: 2rem; }
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.ui-copy { display: inline-grid; }
|
|
3
|
+
|
|
4
|
+
.ui-copy-content {
|
|
5
|
+
grid-area: 1 / 1;
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
gap: inherit;
|
|
10
|
+
opacity: 0;
|
|
11
|
+
visibility: hidden;
|
|
12
|
+
transform: translateY(0.2rem) scale(0.96);
|
|
13
|
+
transition:
|
|
14
|
+
opacity var(--ui-motion-feedback) var(--ease-out),
|
|
15
|
+
transform var(--ui-motion-feedback) var(--ease-out),
|
|
16
|
+
visibility var(--ui-motion-feedback);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ui-copy[data-state="idle"] [data-copy-state="idle"],
|
|
20
|
+
.ui-copy[data-state="copied"] [data-copy-state="copied"],
|
|
21
|
+
.ui-copy[data-state="error"] [data-copy-state="error"] {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
visibility: visible;
|
|
24
|
+
transform: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (prefers-reduced-motion: reduce) {
|
|
28
|
+
.ui-copy-content {
|
|
29
|
+
transform: none;
|
|
30
|
+
transition: none;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.app-dialog {
|
|
3
|
+
--dialog-from: translateY(0.4rem) scale(0.98);
|
|
4
|
+
--dialog-fade: 0.2s;
|
|
5
|
+
--dialog-duration: 0.2s;
|
|
6
|
+
|
|
7
|
+
/* Preflight's `margin: 0` clobbers the UA `margin: auto` that centres a modal dialog. */
|
|
8
|
+
margin: auto;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
overscroll-behavior: contain;
|
|
11
|
+
opacity: 0;
|
|
12
|
+
transform: var(--dialog-from);
|
|
13
|
+
transition:
|
|
14
|
+
opacity var(--dialog-fade) var(--ease-out-expo),
|
|
15
|
+
transform var(--dialog-duration) var(--ease-out-expo),
|
|
16
|
+
display var(--dialog-duration) allow-discrete,
|
|
17
|
+
overlay var(--dialog-duration) allow-discrete;
|
|
18
|
+
|
|
19
|
+
&[open] {
|
|
20
|
+
opacity: 1;
|
|
21
|
+
transform: none;
|
|
22
|
+
|
|
23
|
+
@starting-style {
|
|
24
|
+
opacity: 0;
|
|
25
|
+
transform: var(--dialog-from);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Fades the tint and the blur, not opacity: Chrome applies a backdrop-filter
|
|
30
|
+
under opacity < 1 to only some of the page's layers. */
|
|
31
|
+
&::backdrop {
|
|
32
|
+
background: transparent;
|
|
33
|
+
backdrop-filter: blur(0);
|
|
34
|
+
transition:
|
|
35
|
+
background-color var(--dialog-fade) var(--ease-out),
|
|
36
|
+
backdrop-filter var(--dialog-fade) var(--ease-out),
|
|
37
|
+
display var(--dialog-fade) allow-discrete,
|
|
38
|
+
overlay var(--dialog-fade) allow-discrete;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&[open]::backdrop {
|
|
42
|
+
background: var(--scrim);
|
|
43
|
+
backdrop-filter: blur(2px);
|
|
44
|
+
|
|
45
|
+
@starting-style {
|
|
46
|
+
background: transparent;
|
|
47
|
+
backdrop-filter: blur(0);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.context-dialog[open]::backdrop {
|
|
52
|
+
background: var(--scrim-soft);
|
|
53
|
+
backdrop-filter: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@media (prefers-reduced-motion: reduce) {
|
|
57
|
+
&::backdrop { transition: none; }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Removing [open] hides the content at once, so disclosure_controller holds the
|
|
2
|
+
panel open under [data-closing] for the exit, then closes it. Transitioning
|
|
3
|
+
content-visibility makes WebKit keep the panel hidden for part of the opening. */
|
|
4
|
+
@layer components {
|
|
5
|
+
.disclosure-animated {
|
|
6
|
+
interpolate-size: allow-keywords; /* browser-compat: enhancement */
|
|
7
|
+
|
|
8
|
+
&::details-content {
|
|
9
|
+
block-size: 0;
|
|
10
|
+
overflow-y: clip;
|
|
11
|
+
opacity: 0;
|
|
12
|
+
translate: 0 -0.375rem;
|
|
13
|
+
transition:
|
|
14
|
+
block-size 420ms var(--ease-out-expo),
|
|
15
|
+
opacity 220ms var(--ease-out),
|
|
16
|
+
translate 320ms var(--ease-out-expo);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&[open]::details-content {
|
|
20
|
+
block-size: auto;
|
|
21
|
+
opacity: 1;
|
|
22
|
+
translate: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Where `auto` cannot ease, a 0 here would snap shut before the fade
|
|
26
|
+
shows; the controller shrinks the <details> instead. */
|
|
27
|
+
&[data-closing]::details-content {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
translate: 0 -0.375rem;
|
|
30
|
+
transition-duration: 260ms, 160ms, 200ms;
|
|
31
|
+
transition-timing-function: var(--ease-out);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@supports (interpolate-size: allow-keywords) {
|
|
35
|
+
&[data-closing]::details-content { block-size: 0; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* The global reduced-motion reset's *::after does not reach this pseudo. */
|
|
39
|
+
@media (prefers-reduced-motion: reduce) {
|
|
40
|
+
&::details-content { transition-duration: 0.01ms; }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|