studio-engine 0.114.1.rc1 → 0.114.2.rc1
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/CHANGELOG.md +77 -0
- data/README.md +93 -10
- data/app/assets/tailwind/studio_engine/engine-motion.css +13 -24
- data/app/assets/tailwind/studio_engine/engine.css +9 -10
- data/app/assets/tailwind/studio_engine/host.css +56 -0
- data/app/assets/tailwind/studio_engine/theme.css +139 -0
- data/app/helpers/studio/admin_models_table_helper.rb +6 -1
- data/app/views/studio/modals/_scoped_host.html.erb +2 -2
- data/app/views/studio/modals/blocks/_card_header.html.erb +18 -4
- data/app/views/studio/modals/blocks/_processing_card.html.erb +11 -1
- data/app/views/studio/modals/blocks/_success_card.html.erb +16 -2
- data/app/views/style/_modals.html.erb +2 -2
- data/app/views/style/_tricks.html.erb +15 -12
- data/app/views/style/index.html.erb +3 -2
- data/lib/studio/engine.rb +7 -0
- data/lib/studio/tailwind_entry.rb +72 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +1 -0
- data/tailwind/studio.tailwind.config.js +14 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8f4151d1230d0b856b6960e64b6cb4d1c95a6a84c7a699b564c1517cf79342b
|
|
4
|
+
data.tar.gz: 3b558ba4c1c1338ed7079de81b4ed014f8b2ca77f340b2c70916aaa86b1742ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5d15fd2275e519d42ba93342499ddb304a3383a7e8029f7aa611826b5d8e7a542924cba6f863f48db6ae8784f8b3c40706742c21a593c05a731239d15661b3a
|
|
7
|
+
data.tar.gz: 03eef31def9eb2e19ec1e7a7b3c18f5c0e36b85efcab9544674609533265c5e2caf580d12c26d6a09d5dd07bb91046ab597af6afe8f64a476a09005c4a9f2999
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,83 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.114.2 — 2026-10-26
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **The Tailwind layer is CSS-first; a host's whole adoption is `@import
|
|
12
|
+
"../builds/tailwind/studio_engine";`.** That entry now imports the new
|
|
13
|
+
`host.css` (`Studio::TailwindEntry` rewrites it right after
|
|
14
|
+
tailwindcss-rails' `Tailwindcss::Engines.bundle` writes it), which brings
|
|
15
|
+
`engine.css`, `@source` lines for every engine directory that emits a class
|
|
16
|
+
(views, components, helpers, `app/javascript` and `app/assets/javascripts`,
|
|
17
|
+
the vendored third-party bundles excluded by name) and `engine-motion.css`.
|
|
18
|
+
`engine.css` imports `theme.css` (the design tokens as `@theme inline
|
|
19
|
+
reference`, and the class-based `dark:` variant) and otherwise stays the
|
|
20
|
+
narrow component layer: its `@source "../../../components"` line moved to
|
|
21
|
+
`host.css`, so a build importing `engine.css` by path (the hub's client kit)
|
|
22
|
+
compiles only what it names. A host needs no `@config`, no
|
|
23
|
+
content glob over the gem found by a bundler call from Node, no safelist,
|
|
24
|
+
and no motion rake task. The recipe for a host still on `@config`:
|
|
25
|
+
README, "Tailwind". Hosts adopt on their own cards. A host that only bumps
|
|
26
|
+
the gem still builds, with no class lost, but its CSS grows by the motion
|
|
27
|
+
layer (about 44 KB unminified; McRitchie Industries, Cyvasse and Moms App
|
|
28
|
+
get it for the first time) and the classes the new `@source` lines find.
|
|
29
|
+
- **Engine classes are spelled whole, so the scan finds them.** The modal
|
|
30
|
+
blocks' `icon_color:` and `color:` tones (`_card_header`, `_success_card`,
|
|
31
|
+
`_processing_card`) are literal maps rather than `bg-<%= tone %>/15`, which
|
|
32
|
+
compiled only in a host whose safelist or markup spelled the same name; an
|
|
33
|
+
unknown token now paints as primary. Their check icon's `success` and
|
|
34
|
+
`warning` tones use `text-success-ink` and `text-warning-ink`: the bare
|
|
35
|
+
`text-success` the old interpolation produced is deliberately uncompilable,
|
|
36
|
+
so the "green check" painted the inherited colour in every host. The style
|
|
37
|
+
page's button specimens are keyed by whole class. `admin_model_table`'s
|
|
38
|
+
`min_width:` is an inline `min-width` style instead of an arbitrary
|
|
39
|
+
`min-w-[Npx]` class. Visible: a table given any width but 940 (which a host
|
|
40
|
+
happened to compile) gets its minimum width for the first time, so on a phone
|
|
41
|
+
it scrolls sideways instead of squeezing.
|
|
42
|
+
- **A host's build now carries five engine classes it could miss before**
|
|
43
|
+
(`ring-primary`, `ring-red-500`, `ring-2`, `opacity-30`, `!visible`), written
|
|
44
|
+
only in the engine's helpers and scripts, which no host scanned. Visible: a
|
|
45
|
+
rejected drag on the board shows its red ring, and in a host that defines
|
|
46
|
+
`theme-transition` (the hub) a theme switch now animates.
|
|
47
|
+
|
|
48
|
+
### Deprecated
|
|
49
|
+
|
|
50
|
+
- **`tailwind/studio.tailwind.config.js`** is a shim for one release, so a host
|
|
51
|
+
still on `@config` builds unchanged; `test/integration/tailwind_preset_parity_test.rb`
|
|
52
|
+
holds it to the same utilities as `theme.css`. It is removed once every host
|
|
53
|
+
has adopted. Until a host migrates it gets the motion layer twice (through
|
|
54
|
+
`host.css` and through its own generated `studio_engine_motion` import);
|
|
55
|
+
the duplicate is harmless, and step 3 of the recipe removes it.
|
|
56
|
+
**Removing the shim will break, at build time or at runtime, everything that
|
|
57
|
+
still reads it:** every host's `config/tailwind.config.js` (hub, Turf
|
|
58
|
+
Monster, McRitchie Industries, Cyvasse, Moms App and any newer app built from
|
|
59
|
+
the old `NEW_APP_SETUP.md`), the hub's production code
|
|
60
|
+
`app/services/client_apps/studio_kit/css.rb` (`ENGINE_CONFIG` and its own
|
|
61
|
+
`SAFELIST`, which build the client kit CSS at request time), and the tests
|
|
62
|
+
that read the preset's text (hub `test/views/status_colour_tokens_test.rb`,
|
|
63
|
+
Turf `test/views/error_text_contrast_test.rb`). Each moves to `theme.css` on
|
|
64
|
+
its host's adoption card first.
|
|
65
|
+
|
|
66
|
+
### Tests
|
|
67
|
+
|
|
68
|
+
- `test/support/engine_tailwind_build.rb` builds the engine the CSS-first way
|
|
69
|
+
(no `@config`), with `motion:`, `sources:` and `with_variant` for controls.
|
|
70
|
+
- `tailwind_probe_build_test.rb`: the tokens compile from `@theme` (and the
|
|
71
|
+
bare `text-<role>` still does not), the motion layer arrives through
|
|
72
|
+
`host.css`, and dropping the views, components or JavaScript `@source`
|
|
73
|
+
line loses classes only that directory emits.
|
|
74
|
+
- `component_tailwind_source_test.rb`: every class the five emitting
|
|
75
|
+
directories name, read independently of Tailwind's scanner, is in a
|
|
76
|
+
host-shaped build with no safelist; every emitting directory has an
|
|
77
|
+
`@source` line; exclusions name only vendored files.
|
|
78
|
+
- `test/views/dynamic_tailwind_class_guard_test.rb` refuses a Tailwind class
|
|
79
|
+
assembled at render time.
|
|
80
|
+
- `test/integration/tailwind_entry_test.rb` runs the real
|
|
81
|
+
`Tailwindcss::Engines.bundle` and reads the entry: `host.css`, and in the
|
|
82
|
+
control without the wrap, `engine.css`.
|
|
83
|
+
|
|
7
84
|
## 0.114.1 — 2026-10-25
|
|
8
85
|
|
|
9
86
|
## 0.114.0 — 2026-10-25
|
data/README.md
CHANGED
|
@@ -45,6 +45,91 @@ resolved.
|
|
|
45
45
|
- **Booking**: a simple scheduler on Google Calendar appointment schedules, with or without the footer. With `config.booking_url`, `studio_booking_frame` embeds Google's booking page, `studio_booking_link` opens it in a popup, and `config.draw_booking_routes = true` draws `/schedule` (or name the app's own page with `config.booking_path`). `config.booking_crop` crops the frame to the slot picker at rest, from numbers `bin/booking-crop-measure` reads off the app's own schedule. See [`docs/BOOKING.md`](docs/BOOKING.md).
|
|
46
46
|
- **Surveys**: define a survey in app code (`config/surveys/*.rb`, `Studio.define_survey`) with six question types — emoji scale, 1–5 rating, single and multiple choice, short and long text — and the engine serves it at `/surveys/:slug`: one question per screen, autosave, resume, keyboard shortcuts, and a no-JavaScript fallback. `config.on_survey_completed` and `config.survey_ref_resolver` let the app fire its own goals and attribute email arrivals. An admin panel at `/admin/surveys` breaks results down per question and exports CSV. Opt in with `config.draw_survey_routes = true` and `config.draw_admin_survey_routes = true`. See [`docs/SURVEYS.md`](docs/SURVEYS.md).
|
|
47
47
|
|
|
48
|
+
## Tailwind
|
|
49
|
+
|
|
50
|
+
The engine's Tailwind 4 layer is CSS-first. A host's whole adoption is two
|
|
51
|
+
lines in `app/assets/tailwind/application.css`:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
@import "tailwindcss";
|
|
55
|
+
@import "../builds/tailwind/studio_engine";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
tailwindcss-rails writes `app/assets/builds/tailwind/studio_engine.css` from
|
|
59
|
+
Ruby on every build and watch (`bin/rails tailwindcss:engines` by hand), as an
|
|
60
|
+
absolute `@import` of the gem's `engine.css`. The engine then points that entry
|
|
61
|
+
at `app/assets/tailwind/studio_engine/host.css` (`Studio::TailwindEntry` wraps
|
|
62
|
+
the contract's `bundle`), and that one import brings everything a host used to
|
|
63
|
+
wire by hand:
|
|
64
|
+
|
|
65
|
+
| What | Where it lives | What it replaced |
|
|
66
|
+
|------|----------------|------------------|
|
|
67
|
+
| The design tokens (surfaces, inks, the primary ladder, brand palettes, `text-2xs`/`text-3xs`, Montserrat) and the class-based `dark:` variant | `theme.css`, `@theme inline reference` | `tailwind/studio.tailwind.config.js` and the host's `@config` |
|
|
68
|
+
| The scan of every engine directory that emits a class (views, components, helpers, both JavaScript trees) | `host.css`, `@source` | the host's content glob over the gem's views, found with a bundler call from Node |
|
|
69
|
+
| Every engine class, with no safelist | the scan above; no class is assembled at render time | the host's primary-colour safelist (about 240 classes) |
|
|
70
|
+
| The motion, effect and leveling primitives (`.spinner`, `.hold-btn`, `.level-badge-*`, ...) | `engine-motion.css`, imported last by `host.css` | `lib/tasks/tailwindcss_engine_motion.rake` and its `studio_engine_motion` import |
|
|
71
|
+
|
|
72
|
+
`test/integration/tailwind_probe_build_test.rb` and
|
|
73
|
+
`test/integration/component_tailwind_source_test.rb` build the engine exactly as
|
|
74
|
+
a host does and hold all four; `test/integration/tailwind_entry_test.rb` runs
|
|
75
|
+
the real `Tailwindcss::Engines.bundle` and reads the entry it leaves.
|
|
76
|
+
|
|
77
|
+
`engine.css` on its own stays the narrow layer: the theme and the component
|
|
78
|
+
`@utility` definitions, with no `@source` and no motion. A build that imports it
|
|
79
|
+
by path, such as the hub's client kit (`ClientApps::StudioKit::Css`, held to
|
|
80
|
+
100 KB), compiles only the classes it names.
|
|
81
|
+
|
|
82
|
+
### Migrating a host from `@config`
|
|
83
|
+
|
|
84
|
+
Hosts adopt on their own cards (hub, Turf Monster, Rolio, chain-ops, Rantly,
|
|
85
|
+
McRitchie Industries, Cyvasse, Moms App). Until each one does, it keeps
|
|
86
|
+
building unchanged: `tailwind/studio.tailwind.config.js` stays one release as a
|
|
87
|
+
deprecated shim, and `test/integration/tailwind_preset_parity_test.rb` holds it
|
|
88
|
+
to the same utilities as `theme.css`. A host that has bumped the gem but not
|
|
89
|
+
yet migrated gets the motion layer twice (once through `host.css`, once
|
|
90
|
+
through its own generated import). The duplicate is harmless and goes with
|
|
91
|
+
step 3.
|
|
92
|
+
|
|
93
|
+
1. **Bump** `studio-engine` to the release that carries this section.
|
|
94
|
+
2. **`application.css`**: delete the `@config` line. Keep `@import
|
|
95
|
+
"tailwindcss";` and `@import "../builds/tailwind/studio_engine";` first.
|
|
96
|
+
3. **Motion**: delete `@import "../builds/tailwind/studio_engine_motion";`,
|
|
97
|
+
`lib/tasks/tailwindcss_engine_motion.rake` and the generated
|
|
98
|
+
`app/assets/builds/tailwind/studio_engine_motion.css`.
|
|
99
|
+
4. **`config/tailwind.config.js`**: delete it, moving only what is the host's
|
|
100
|
+
own into `application.css`:
|
|
101
|
+
- **`content:`** goes. Tailwind 4 detects the app's own sources from the
|
|
102
|
+
project root and skips what `.gitignore` names; the engine scans itself.
|
|
103
|
+
If CI or Heroku installs gems into `vendor/bundle` inside the app, exclude
|
|
104
|
+
only their Markdown: `@source not "../../../vendor/bundle/**/*.md";`.
|
|
105
|
+
Never exclude the whole directory: the gem lives under it there, and the
|
|
106
|
+
hub's build once lost every engine class that way. A file the default scan
|
|
107
|
+
misses (say `config/initializers/studio.rb` carrying navbar word classes)
|
|
108
|
+
takes its own `@source` line.
|
|
109
|
+
- **`safelist`** goes. The engine's own classes no longer need it. If a host
|
|
110
|
+
class is genuinely data-driven, spell its variants whole in source; as a
|
|
111
|
+
last resort, `@source inline()` takes brace expansion. The hub's whole
|
|
112
|
+
safelist is
|
|
113
|
+
`@source inline("{bg,text,border}-primary{,-{50,{100..900..100}}}{,/{5,10,20,30,40,50}}");`
|
|
114
|
+
(231 classes).
|
|
115
|
+
- **`theme` extensions** become `@theme inline { ... }` after the engine
|
|
116
|
+
import: a screen is `--breakpoint-pill-narrow: 530px;`, a palette is
|
|
117
|
+
`--color-warning-500: #FF7C47;`, and a text-only colour is
|
|
118
|
+
`--text-color-violet-ink: var(--color-violet-ink);` (the same name
|
|
119
|
+
overrides the engine's, as Turf's `text-primary` does with
|
|
120
|
+
`--text-color-primary: rgb(var(--color-primary-ink-rgb));`).
|
|
121
|
+
- **`darkMode: 'class'`** goes; `theme.css` declares it.
|
|
122
|
+
5. **Verify** with `bin/rails tailwindcss:build`, then compare the class set of
|
|
123
|
+
`app/assets/builds/tailwind.css` before and after. It may only grow (the
|
|
124
|
+
engine now also scans its helpers and scripts). Spot-check one class from
|
|
125
|
+
each column above: `.text-heading`, `.btn-primary`, `.spinner`.
|
|
126
|
+
|
|
127
|
+
When every host has adopted, the shim and the parity test are deleted in one
|
|
128
|
+
engine change. Before that, every reader of the preset has to move, not only
|
|
129
|
+
the build: the hub's `app/services/client_apps/studio_kit/css.rb` reads it at
|
|
130
|
+
runtime to build the client kit CSS, and a few host tests read its text
|
|
131
|
+
(CHANGELOG, Deprecated).
|
|
132
|
+
|
|
48
133
|
## Configuration
|
|
49
134
|
|
|
50
135
|
Each consuming app configures the engine in `config/initializers/studio.rb`:
|
|
@@ -503,8 +588,8 @@ Two things that will bite you:
|
|
|
503
588
|
tick *after* the stack empties, so a bare `.id` throws on every close.
|
|
504
589
|
|
|
505
590
|
The scoped host takes its animations from `engine-motion.css` rather than an
|
|
506
|
-
inline copy, so
|
|
507
|
-
host.
|
|
591
|
+
inline copy, so every host (the layer rides the engine's one Tailwind import)
|
|
592
|
+
gets the same spring as the shared host.
|
|
508
593
|
|
|
509
594
|
It carries the store API below with three deliberate differences: there is no
|
|
510
595
|
`advance()`; `swap()` replaces the top entry immediately rather than running the
|
|
@@ -971,14 +1056,12 @@ config.navbar_z_tier = true # the live header at z-[var(--z-nav)], not z-50
|
|
|
971
1056
|
truncating span with the host's class and nothing else. `navbar_wordmark`
|
|
972
1057
|
wins when both are set. Rules: `lib/studio/navbar_brand.rb`.
|
|
973
1058
|
- **The word classes live in the host's initializer**
|
|
974
|
-
(`config/initializers/studio.rb`), which a host's Tailwind scan
|
|
975
|
-
read
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
rule as the engine's views glob:
|
|
981
|
-
[NEW_APP_SETUP.md, section 14](docs/NEW_APP_SETUP.md#14-css-component-classes).
|
|
1059
|
+
(`config/initializers/studio.rb`), which a host's Tailwind scan may not
|
|
1060
|
+
read (a host still on `@config` lists only its `content:` globs). A utility
|
|
1061
|
+
named only there, `text-heading` say, compiles only if some view also uses
|
|
1062
|
+
it. Add the initializer to the scan (`@source "../../../config/initializers/studio.rb";`),
|
|
1063
|
+
or give the word a class the host's CSS defines (`tm-wordmark`). See
|
|
1064
|
+
[Tailwind](#tailwind).
|
|
982
1065
|
- `navbar_root_hook` and `navbar_z_tier` are booleans, `false` by default.
|
|
983
1066
|
|
|
984
1067
|
### The account row
|
|
@@ -36,30 +36,19 @@
|
|
|
36
36
|
.seeds-bar-continuous 5-segment lockstep progress fill (ported, TM)
|
|
37
37
|
|
|
38
38
|
─────────────────────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Resolve STUDIO_ENGINE_PATH the same way config/tailwind.config.js already
|
|
54
|
-
resolves the shared preset — `bundle show studio-engine`. A one-time helper the
|
|
55
|
-
app-side de-fork task can wire (mirrors the engines contract):
|
|
56
|
-
|
|
57
|
-
# config/tailwind.config.js already computes this:
|
|
58
|
-
# const studioPath = execSync('bundle show studio-engine').toString().trim()
|
|
59
|
-
# emit an absolute @import into your app CSS (or a tiny generated entry).
|
|
60
|
-
|
|
61
|
-
App-side wiring + removing the forked local copies lands in the follow-up
|
|
62
|
-
adoption task; until then the MS/TM local copies stay canonical.
|
|
39
|
+
HOW IT REACHES A HOST — through host.css, which imports this file last.
|
|
40
|
+
|
|
41
|
+
A host's one line, `@import "../builds/tailwind/studio_engine";`, resolves to
|
|
42
|
+
host.css (Studio::TailwindEntry) and so brings this layer with the rest of
|
|
43
|
+
the engine. engine.css itself does not import it. It used to be OPT-IN: tailwindcss-rails'
|
|
44
|
+
engines contract (Tailwindcss::Engines.bundle) writes a builds entry only for
|
|
45
|
+
the file named exactly `engine.css`, so each host carried a rake task
|
|
46
|
+
(lib/tasks/tailwindcss_engine_motion.rake) that generated a second entry,
|
|
47
|
+
`builds/tailwind/studio_engine_motion.css`, plus a matching @import. Both are
|
|
48
|
+
retired: a host deletes the task and the import (README, "Tailwind"). A host
|
|
49
|
+
that keeps them for a release gets this layer twice, which is harmless —
|
|
50
|
+
every rule below is a plain class, keyframe or @property, so the duplicate
|
|
51
|
+
resolves to the same values.
|
|
63
52
|
|
|
64
53
|
─────────────────────────────────────────────────────────────────────────────
|
|
65
54
|
DESIGN RULES
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
(or manually: `bin/rails tailwindcss:engines`) because this file lives at the
|
|
12
12
|
Rails-engines contract path: app/assets/tailwind/<engine_name>/engine.css.
|
|
13
13
|
|
|
14
|
+
That line resolves to host.css, not to this file: studio-engine writes the
|
|
15
|
+
builds entry itself (Studio::TailwindEntry) so it can carry, beside this
|
|
16
|
+
component layer, the @source scan of the engine's own markup and the motion
|
|
17
|
+
layer. See host.css. This file stays the NARROW layer: the theme (theme.css,
|
|
18
|
+
imported below, emits nothing on its own) and the component @utility
|
|
19
|
+
definitions, with no sources and no motion, so a build that imports it by
|
|
20
|
+
path (the hub's client kit) compiles only the classes it names.
|
|
21
|
+
|
|
14
22
|
Requirements:
|
|
15
23
|
- Tailwind v4 (this file uses @utility, so classes sort/override correctly
|
|
16
24
|
in the utilities layer and take variants like `md:card`).
|
|
17
|
-
- The consumer's tailwind config spreads the engine preset
|
|
18
|
-
(tailwind/studio.tailwind.config.js) — it defines the surface / heading /
|
|
19
|
-
subtle / primary token utilities @apply'd below.
|
|
20
25
|
|
|
21
26
|
Colors resolve through the 7-role theme CSS custom properties (--color-cta,
|
|
22
27
|
--color-surface, ...) emitted by studio_theme_css_tag, so themed apps restyle
|
|
@@ -28,13 +33,7 @@
|
|
|
28
33
|
their local copies for this import.
|
|
29
34
|
*/
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
The engine's ViewComponent classes (app/components) carry their colour
|
|
33
|
-
classes in Ruby, and a host's Tailwind config scans only the engine's
|
|
34
|
-
app/views. This adds app/components to the scan of every host that imports
|
|
35
|
-
this file, with no host edit. The path is relative to this file.
|
|
36
|
-
test/integration/component_tailwind_source_test.rb proves it compiles. */
|
|
37
|
-
@source "../../../components";
|
|
36
|
+
@import "./theme.css";
|
|
38
37
|
|
|
39
38
|
/* -- Layer scale ------------------------------------------------------------
|
|
40
39
|
The ONE stacking order every Studio app shares. Read the ORDER, not the
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
studio-engine HOST ENTRY — what a host's one Tailwind line resolves to.
|
|
3
|
+
|
|
4
|
+
@import "tailwindcss";
|
|
5
|
+
@import "../builds/tailwind/studio_engine";
|
|
6
|
+
|
|
7
|
+
tailwindcss-rails' engines contract (Tailwindcss::Engines.bundle) writes
|
|
8
|
+
app/assets/builds/tailwind/studio_engine.css as an @import of the gem's
|
|
9
|
+
engine.css. Studio::TailwindEntry (lib/studio/tailwind_entry.rb) rewrites
|
|
10
|
+
that entry, right after the bundle runs, to import THIS file instead, so a
|
|
11
|
+
host gets, from the same line and with nothing of its own:
|
|
12
|
+
|
|
13
|
+
- engine.css: the theme (theme.css, the tokens that replaced the JS
|
|
14
|
+
preset and the host's `@config`) and the component @utility layer;
|
|
15
|
+
- the @source scan below, so no host lists the gem in a content glob or
|
|
16
|
+
safelists its classes;
|
|
17
|
+
- engine-motion.css, the motion / effect / leveling layer, which used to
|
|
18
|
+
need a per-host rake task (lib/tasks/tailwindcss_engine_motion.rake).
|
|
19
|
+
|
|
20
|
+
The path is resolved by Ruby from the gem's root, so Node never runs bundler
|
|
21
|
+
to find the gem. engine.css stays importable on its own as the narrow layer.
|
|
22
|
+
Recipe: README, "Tailwind".
|
|
23
|
+
*/
|
|
24
|
+
@import "./engine.css";
|
|
25
|
+
|
|
26
|
+
/* -- Sources: every engine directory that emits a class --------------------
|
|
27
|
+
Tailwind v4 compiles only the classes it SEES, so every directory whose
|
|
28
|
+
files put a utility class on the page is scanned here, relative to this
|
|
29
|
+
file, in every host that imports it, with no host edit: views and
|
|
30
|
+
partials, the ViewComponents (their colour classes live in Ruby), the
|
|
31
|
+
helpers that build markup (admin_model_table), and both JavaScript trees
|
|
32
|
+
(classes added at runtime).
|
|
33
|
+
|
|
34
|
+
The exclusions are NARROW on purpose: the vendored, minified third-party
|
|
35
|
+
bundles (Stimulus, Alpine, Leaflet, Sortable, canvas-confetti) are not
|
|
36
|
+
engine markup, and scanning them mints junk utilities from their
|
|
37
|
+
identifiers. Never exclude a whole directory: the hub's
|
|
38
|
+
`@source not "../../../vendor/bundle"` once dropped every one of these
|
|
39
|
+
lines in CI, because the gem itself lives under vendor/bundle there.
|
|
40
|
+
|
|
41
|
+
test/integration/component_tailwind_source_test.rb proves every class these
|
|
42
|
+
directories emit compiles, and that dropping any one line turns it red. A
|
|
43
|
+
class built by interpolation (`bg-<%= tone %>`) is invisible to the scan;
|
|
44
|
+
test/views/dynamic_tailwind_class_guard_test.rb refuses one. */
|
|
45
|
+
@source "../../../views";
|
|
46
|
+
@source "../../../components";
|
|
47
|
+
@source "../../../helpers";
|
|
48
|
+
@source "../../../javascript";
|
|
49
|
+
@source not "../../../javascript/studio/vendor";
|
|
50
|
+
@source "../../javascripts";
|
|
51
|
+
@source not "../../javascripts/studio/{alpine,canvas_confetti,leaflet,sortable}.js";
|
|
52
|
+
|
|
53
|
+
/* -- Motion / effect / leveling layer ---------------------------------------
|
|
54
|
+
Last, so the theme and component layer it reads are in scope, as when hosts
|
|
55
|
+
imported it after engine.css themselves. */
|
|
56
|
+
@import "./engine-motion.css";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
studio-engine THEME — the shared Tailwind v4 design tokens, CSS-first.
|
|
3
|
+
|
|
4
|
+
This file replaced tailwind/studio.tailwind.config.js (the JS preset every
|
|
5
|
+
consumer spread through `@config`, after shelling out to bundler from Node to
|
|
6
|
+
find the gem). engine.css imports it (and host.css imports engine.css), so a
|
|
7
|
+
consumer gets these tokens from the same one line that brings the component
|
|
8
|
+
layer:
|
|
9
|
+
|
|
10
|
+
@import "tailwindcss";
|
|
11
|
+
@import "../builds/tailwind/studio_engine";
|
|
12
|
+
|
|
13
|
+
No `@config`, no bundler call from Node, no generated motion entry.
|
|
14
|
+
|
|
15
|
+
WHY `@theme inline`. Every runtime token here (surface, heading, the primary
|
|
16
|
+
ladder, the status inks) is a CSS custom property that studio_theme_css_tag
|
|
17
|
+
writes per app and per theme. A plain `@theme` would ALSO declare
|
|
18
|
+
`--color-surface: var(--color-surface)` on :root — a self-reference, which is
|
|
19
|
+
invalid — and would put Tailwind's copy in a cascade race with the theme tag.
|
|
20
|
+
`inline` makes each utility carry the value itself (`bg-surface` compiles to
|
|
21
|
+
`background-color: var(--color-surface)`, exactly what the JS preset
|
|
22
|
+
compiled to) and declares nothing on :root.
|
|
23
|
+
|
|
24
|
+
WHY THE PER-UTILITY NAMESPACES. The status roles are deliberately NOT plain
|
|
25
|
+
colours. A bare role colour as text (text-danger, text-warning, text-success)
|
|
26
|
+
fails WCAG AA on one theme or the other, so it must stay uncompilable; the
|
|
27
|
+
text inks (*-ink, derived per theme by ThemeResolver#contrast_ink) are the
|
|
28
|
+
text spelling. Tailwind v4 reads `--text-color-*`, `--background-color-*` and
|
|
29
|
+
`--border-color-*` for ONLY that utility family, so registering a fill there
|
|
30
|
+
cannot mint the text utility, the same split the preset's textColor /
|
|
31
|
+
backgroundColor / borderColor keys made.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/* darkMode: 'class' — the theme tag toggles .dark on <html>. */
|
|
35
|
+
@custom-variant dark (&:is(.dark *));
|
|
36
|
+
|
|
37
|
+
@theme inline reference {
|
|
38
|
+
/* fontFamily — REPLACED, not extended (the preset set theme.fontFamily). */
|
|
39
|
+
--font-*: initial;
|
|
40
|
+
--font-sans: Montserrat, system-ui, sans-serif;
|
|
41
|
+
--font-mono: ui-monospace, SFMono-Regular, monospace;
|
|
42
|
+
|
|
43
|
+
/* Sub-xs type scale for dense UI (badges, meta rows, tick labels). Bare
|
|
44
|
+
sizes, no line-height, so text-2xs / text-3xs are drop-in replacements for
|
|
45
|
+
the text-[11px] / text-[10px] arbitrary values. */
|
|
46
|
+
--text-2xs: 0.6875rem; /* 11px */
|
|
47
|
+
--text-3xs: 0.625rem; /* 10px */
|
|
48
|
+
|
|
49
|
+
/* Theme-aware semantic surfaces (studio_theme_css_tag). */
|
|
50
|
+
--color-page: var(--color-page);
|
|
51
|
+
--color-surface: var(--color-surface);
|
|
52
|
+
--color-surface-alt: var(--color-surface-alt);
|
|
53
|
+
--color-inset: var(--color-inset);
|
|
54
|
+
|
|
55
|
+
/* The dynamic primary palette, from the theme's primary role. The -rgb
|
|
56
|
+
triples are space lists, so `rgb(var(--x-rgb))` is valid and Tailwind's
|
|
57
|
+
opacity modifier (bg-primary/20) mixes it. */
|
|
58
|
+
--color-primary: rgb(var(--color-primary-rgb));
|
|
59
|
+
--color-primary-50: rgb(var(--color-primary-50-rgb));
|
|
60
|
+
--color-primary-100: rgb(var(--color-primary-100-rgb));
|
|
61
|
+
--color-primary-200: rgb(var(--color-primary-200-rgb));
|
|
62
|
+
--color-primary-300: rgb(var(--color-primary-300-rgb));
|
|
63
|
+
--color-primary-400: rgb(var(--color-primary-400-rgb));
|
|
64
|
+
--color-primary-500: rgb(var(--color-primary-500-rgb));
|
|
65
|
+
--color-primary-600: rgb(var(--color-primary-600-rgb));
|
|
66
|
+
--color-primary-700: rgb(var(--color-primary-700-rgb));
|
|
67
|
+
--color-primary-800: rgb(var(--color-primary-800-rgb));
|
|
68
|
+
--color-primary-900: rgb(var(--color-primary-900-rgb));
|
|
69
|
+
|
|
70
|
+
/* Brand palettes. */
|
|
71
|
+
--color-mint: #06D6A0;
|
|
72
|
+
--color-mint-50: #e6faf4;
|
|
73
|
+
--color-mint-100: #b3f0de;
|
|
74
|
+
--color-mint-200: #80e6c8;
|
|
75
|
+
--color-mint-300: #4ddcb2;
|
|
76
|
+
--color-mint-400: #1ad29c;
|
|
77
|
+
--color-mint-500: #06D6A0;
|
|
78
|
+
--color-mint-600: #05b888;
|
|
79
|
+
--color-mint-700: #049a70;
|
|
80
|
+
--color-mint-800: #037c58;
|
|
81
|
+
--color-mint-900: #025e40;
|
|
82
|
+
|
|
83
|
+
--color-navy: #1A1535;
|
|
84
|
+
--color-navy-50: #e8e7ed;
|
|
85
|
+
--color-navy-100: #b8b5c8;
|
|
86
|
+
--color-navy-200: #8883a3;
|
|
87
|
+
--color-navy-300: #58517e;
|
|
88
|
+
--color-navy-400: #3a3359;
|
|
89
|
+
--color-navy-500: #1A1535;
|
|
90
|
+
--color-navy-600: #16122e;
|
|
91
|
+
--color-navy-700: #120f27;
|
|
92
|
+
--color-navy-800: #0e0c20;
|
|
93
|
+
--color-navy-900: #0a0919;
|
|
94
|
+
|
|
95
|
+
--color-violet: #8E82FE;
|
|
96
|
+
--color-violet-50: #f0eeff;
|
|
97
|
+
--color-violet-100: #EAE8FF;
|
|
98
|
+
--color-violet-200: #b2aafe;
|
|
99
|
+
--color-violet-300: #C5C0FE;
|
|
100
|
+
--color-violet-400: #8E82FE;
|
|
101
|
+
--color-violet-500: #8E82FE;
|
|
102
|
+
--color-violet-600: #6558e5;
|
|
103
|
+
--color-violet-700: #6558E0;
|
|
104
|
+
--color-violet-800: #3b2cb3;
|
|
105
|
+
--color-violet-900: #3D2FB5;
|
|
106
|
+
|
|
107
|
+
--color-mist: #F7F6FF;
|
|
108
|
+
--color-lavender: #E8E6F0;
|
|
109
|
+
--color-slate: #6B6580;
|
|
110
|
+
--color-charcoal: #2D2648;
|
|
111
|
+
--color-midnight: #120F28;
|
|
112
|
+
--color-ember: #FF8C69;
|
|
113
|
+
--color-gold: #FFD166;
|
|
114
|
+
--color-magenta: #F72585;
|
|
115
|
+
|
|
116
|
+
/* Text-only tokens. */
|
|
117
|
+
--text-color-heading: var(--color-text);
|
|
118
|
+
--text-color-body: var(--color-text-body);
|
|
119
|
+
--text-color-secondary: var(--color-text-secondary);
|
|
120
|
+
--text-color-muted: var(--color-text-muted);
|
|
121
|
+
/* The status roles as TEXT, held to AA on every surface. There is
|
|
122
|
+
deliberately NO text-danger, text-warning or text-success. */
|
|
123
|
+
--text-color-danger-ink: var(--color-danger-ink);
|
|
124
|
+
--text-color-warning-ink: var(--color-warning-ink);
|
|
125
|
+
--text-color-success-ink: var(--color-success-ink);
|
|
126
|
+
/* The primary role as chip text; text-primary stays the brand colour. */
|
|
127
|
+
--text-color-primary-ink: var(--color-primary-ink);
|
|
128
|
+
|
|
129
|
+
/* The status-role FILLS, for backgrounds and borders ONLY. */
|
|
130
|
+
--background-color-success: var(--color-success);
|
|
131
|
+
--background-color-warning: var(--color-warning);
|
|
132
|
+
--background-color-danger: var(--color-danger);
|
|
133
|
+
|
|
134
|
+
--border-color-subtle: var(--color-border);
|
|
135
|
+
--border-color-strong: var(--color-border-strong);
|
|
136
|
+
--border-color-success: var(--color-success);
|
|
137
|
+
--border-color-warning: var(--color-warning);
|
|
138
|
+
--border-color-danger: var(--color-danger);
|
|
139
|
+
}
|
|
@@ -12,6 +12,11 @@ module Studio
|
|
|
12
12
|
# <td class="px-4 py-3"><%= time_ago_in_words(user.created_at) %> ago</td>
|
|
13
13
|
# <% end %>
|
|
14
14
|
#
|
|
15
|
+
# min_width is an inline style, not an arbitrary min-width class: Tailwind compiles
|
|
16
|
+
# only class names it can read in source, and a width interpolated at render
|
|
17
|
+
# time is never in source, so the class painted only in a host whose own
|
|
18
|
+
# markup happened to spell the same width.
|
|
19
|
+
#
|
|
15
20
|
# The block yields each record and returns that row's <td> cells. Output matches
|
|
16
21
|
# the long-standing markup (id="models-<key>-table", overflow wrapper, hover
|
|
17
22
|
# rows, colspan empty state) so existing view assertions keep passing.
|
|
@@ -34,7 +39,7 @@ module Studio
|
|
|
34
39
|
table = content_tag(:table,
|
|
35
40
|
content_tag(:thead, header_row, class: "bg-surface-alt text-muted text-xs uppercase tracking-wide") +
|
|
36
41
|
content_tag(:tbody, body, class: "divide-y divide-subtle"),
|
|
37
|
-
id: "models-#{key}-table", class: "w-full min-
|
|
42
|
+
id: "models-#{key}-table", class: "w-full text-sm", style: "min-width: #{min_width.to_i}px")
|
|
38
43
|
|
|
39
44
|
content_tag(:div, table, class: "overflow-x-auto")
|
|
40
45
|
end
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
Animations come from the shared motion layer
|
|
31
31
|
(app/assets/tailwind/studio_engine/engine-motion.css) — the .modal-card-* and
|
|
32
32
|
.modal-backdrop-* classes — rather than an inline copy, so this stays small.
|
|
33
|
-
|
|
34
|
-
just without the spring.
|
|
33
|
+
every host's engine import brings that layer (host.css); a page built without it
|
|
34
|
+
still gets a working modal, just without the spring.
|
|
35
35
|
|
|
36
36
|
Locals:
|
|
37
37
|
store — REQUIRED. Alpine store name, e.g. "emailModals". Must be unique
|
|
@@ -34,6 +34,20 @@
|
|
|
34
34
|
%>
|
|
35
35
|
<%
|
|
36
36
|
icon_color = local_assigns[:icon_color] || 'primary'
|
|
37
|
+
# Each tone is spelled out WHOLE, never assembled from "bg-" + icon_color:
|
|
38
|
+
# Tailwind compiles only the class names it can read in this file, so an
|
|
39
|
+
# assembled one painted only in a host that happened to use or safelist it.
|
|
40
|
+
# The text half is the AA ink, not the bare role colour, which the theme
|
|
41
|
+
# leaves uncompilable on purpose (theme.css). An unknown token paints as
|
|
42
|
+
# primary. test/views/dynamic_tailwind_class_guard_test.rb refuses a
|
|
43
|
+
# rebuilt interpolation.
|
|
44
|
+
icon_tones = {
|
|
45
|
+
'primary' => { pill: 'bg-primary/15', ink: 'text-primary' },
|
|
46
|
+
'success' => { pill: 'bg-success/15', ink: 'text-success-ink' },
|
|
47
|
+
'warning' => { pill: 'bg-warning/15', ink: 'text-warning-ink' },
|
|
48
|
+
'mint' => { pill: 'bg-mint/15', ink: 'text-mint' }
|
|
49
|
+
}
|
|
50
|
+
icon_tone = icon_tones.fetch(icon_color.to_s) { icon_tones['primary'] }
|
|
37
51
|
size = local_assigns[:size] || :md
|
|
38
52
|
pill_class = size == :lg ? 'w-14 h-14' : 'w-12 h-12'
|
|
39
53
|
svg_class = size == :lg ? 'w-7 h-7' : 'w-6 h-6'
|
|
@@ -56,8 +70,8 @@
|
|
|
56
70
|
</svg>
|
|
57
71
|
</div>
|
|
58
72
|
<% elsif local_assigns[:icon] == :external_tab %>
|
|
59
|
-
<div class="mx-auto <%= pill_class %> rounded-full
|
|
60
|
-
<svg class="<%= svg_class %>
|
|
73
|
+
<div class="mx-auto <%= pill_class %> rounded-full <%= icon_tone[:pill] %> flex items-center justify-center mb-4">
|
|
74
|
+
<svg class="<%= svg_class %> <%= icon_tone[:ink] %>" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
61
75
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"/>
|
|
62
76
|
</svg>
|
|
63
77
|
</div>
|
|
@@ -66,8 +80,8 @@
|
|
|
66
80
|
<% elsif local_assigns[:icon_emoji] %>
|
|
67
81
|
<div class="text-5xl mb-4 leading-none"><%= icon_emoji %></div>
|
|
68
82
|
<% else %>
|
|
69
|
-
<div class="mx-auto <%= pill_class %> rounded-full
|
|
70
|
-
<svg class="<%= svg_class %>
|
|
83
|
+
<div class="mx-auto <%= pill_class %> rounded-full <%= icon_tone[:pill] %> flex items-center justify-center mb-4">
|
|
84
|
+
<svg class="<%= svg_class %> <%= icon_tone[:ink] %>" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
|
|
71
85
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
72
86
|
</svg>
|
|
73
87
|
</div>
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
'lg' => 'w-16 h-16 border-4'
|
|
38
38
|
}
|
|
39
39
|
spinner_class = sizes[size] || sizes['md']
|
|
40
|
+
# Each tone is spelled out WHOLE, never assembled from "border-" + color:
|
|
41
|
+
# Tailwind compiles only the class names it can read in this file. An
|
|
42
|
+
# unknown token spins in primary.
|
|
43
|
+
# test/views/dynamic_tailwind_class_guard_test.rb refuses a rebuilt one.
|
|
44
|
+
tones = {
|
|
45
|
+
'primary' => 'border-primary/30 border-t-primary',
|
|
46
|
+
'success' => 'border-success/30 border-t-success',
|
|
47
|
+
'warning' => 'border-warning/30 border-t-warning'
|
|
48
|
+
}
|
|
49
|
+
tone_class = tones.fetch(color.to_s) { tones['primary'] }
|
|
40
50
|
# min_duration mirrors StudioModals.MIN_LOAD_MS (the JS default) so the Ruby and
|
|
41
51
|
# JS sides of the convention agree on the same floor.
|
|
42
52
|
min_duration = local_assigns[:min_duration] || 1400
|
|
@@ -62,7 +72,7 @@
|
|
|
62
72
|
end
|
|
63
73
|
%>
|
|
64
74
|
<div class="text-center py-6"<%= resolve_attrs %>>
|
|
65
|
-
<div class="mx-auto <%= spinner_class %> rounded-full
|
|
75
|
+
<div class="mx-auto <%= spinner_class %> rounded-full <%= tone_class %> animate-spin mb-5"></div>
|
|
66
76
|
<% if local_assigns[:title_key] %>
|
|
67
77
|
<p class="text-base font-bold text-heading mb-1" x-text="<%= title_key %>"></p>
|
|
68
78
|
<% elsif local_assigns[:title] %>
|
|
@@ -80,6 +80,20 @@
|
|
|
80
80
|
%>
|
|
81
81
|
<%
|
|
82
82
|
icon_color = local_assigns[:icon_color] || 'primary'
|
|
83
|
+
# Each tone is spelled out WHOLE, never assembled from "bg-" + icon_color:
|
|
84
|
+
# Tailwind compiles only the class names it can read in this file, so an
|
|
85
|
+
# assembled one painted only in a host that happened to use or safelist it.
|
|
86
|
+
# The text half is the AA ink, not the bare role colour, which the theme
|
|
87
|
+
# leaves uncompilable on purpose (theme.css). An unknown token paints as
|
|
88
|
+
# primary. test/views/dynamic_tailwind_class_guard_test.rb refuses a
|
|
89
|
+
# rebuilt interpolation.
|
|
90
|
+
icon_tones = {
|
|
91
|
+
'primary' => { pill: 'bg-primary/15', ink: 'text-primary' },
|
|
92
|
+
'success' => { pill: 'bg-success/15', ink: 'text-success-ink' },
|
|
93
|
+
'warning' => { pill: 'bg-warning/15', ink: 'text-warning-ink' },
|
|
94
|
+
'mint' => { pill: 'bg-mint/15', ink: 'text-mint' }
|
|
95
|
+
}
|
|
96
|
+
icon_tone = icon_tones.fetch(icon_color.to_s) { icon_tones['primary'] }
|
|
83
97
|
redirect_secs = local_assigns[:auto_redirect_seconds] || 5
|
|
84
98
|
has_redirect = !!local_assigns[:auto_redirect_url_key]
|
|
85
99
|
fire_confetti = !!local_assigns[:confetti]
|
|
@@ -182,8 +196,8 @@
|
|
|
182
196
|
<% if local_assigns[:icon_emoji] %>
|
|
183
197
|
<div class="text-5xl mb-4 leading-none"><%= icon_emoji %></div>
|
|
184
198
|
<% else %>
|
|
185
|
-
<div class="mx-auto w-14 h-14 rounded-full
|
|
186
|
-
<svg class="w-7 h-7
|
|
199
|
+
<div class="mx-auto w-14 h-14 rounded-full <%= icon_tone[:pill] %> flex items-center justify-center mb-4">
|
|
200
|
+
<svg class="w-7 h-7 <%= icon_tone[:ink] %>" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
|
|
187
201
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
188
202
|
</svg>
|
|
189
203
|
</div>
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
Free Entry Earned reward, gated by :leveling).
|
|
29
29
|
|
|
30
30
|
The modal motion (modal-card-* / modal-backdrop-* keyframes + studio-modal-
|
|
31
|
-
drain + body.modal-open) ships in engine-motion.css,
|
|
32
|
-
|
|
31
|
+
drain + body.modal-open) ships in engine-motion.css, which every host's
|
|
32
|
+
engine import brings (host.css), so every consumer animates these for real. %>
|
|
33
33
|
<%
|
|
34
34
|
web3_on = Studio.feature?(:web3)
|
|
35
35
|
leveling_on = Studio.feature?(:leveling)
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
rendered primitive, its class name (one-click copy), and a copyable usage
|
|
5
5
|
snippet. Every class here is a REAL engine class (engine.css component layer
|
|
6
6
|
or engine-motion.css motion/effect/leveling layer), so a consumer that
|
|
7
|
-
|
|
7
|
+
imports the engine's Tailwind entry (host.css brings both) styles the
|
|
8
|
+
specimens for real.
|
|
8
9
|
|
|
9
10
|
The Leveling group renders the REAL .level-badge tiers ported into the engine
|
|
10
11
|
from Turf Monster, capability-gated by Studio.feature?(:leveling). When a host
|
|
@@ -15,15 +16,17 @@
|
|
|
15
16
|
The color tokens/swatches now live in the Theme section (the landing section
|
|
16
17
|
owns "the colors"); this section is the trick board. %>
|
|
17
18
|
<%
|
|
19
|
+
# Keyed by the WHOLE class, never "btn-" + a name: Tailwind compiles only the
|
|
20
|
+
# @utility names it can read in source.
|
|
18
21
|
button_variants = {
|
|
19
|
-
"primary" => "Primary",
|
|
20
|
-
"secondary" => "Secondary",
|
|
21
|
-
"success" => "Success",
|
|
22
|
-
"danger" => "Danger",
|
|
23
|
-
"warning" => "Warning",
|
|
24
|
-
"neutral" => "Neutral",
|
|
25
|
-
"outline" => "Outline",
|
|
26
|
-
"google" => "Continue with Google"
|
|
22
|
+
"btn-primary" => "Primary",
|
|
23
|
+
"btn-secondary" => "Secondary",
|
|
24
|
+
"btn-success" => "Success",
|
|
25
|
+
"btn-danger" => "Danger",
|
|
26
|
+
"btn-warning" => "Warning",
|
|
27
|
+
"btn-neutral" => "Neutral",
|
|
28
|
+
"btn-outline" => "Outline",
|
|
29
|
+
"btn-google" => "Continue with Google"
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
leveling_on = Studio.feature?(:leveling)
|
|
@@ -51,9 +54,9 @@
|
|
|
51
54
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
52
55
|
<% button_variants.each do |name, label| %>
|
|
53
56
|
<%= render layout: "style/specimen",
|
|
54
|
-
locals: { klass: ".btn
|
|
55
|
-
usage: %(<button class="btn
|
|
56
|
-
<button type="button" class="btn
|
|
57
|
+
locals: { klass: ".btn .#{name}",
|
|
58
|
+
usage: %(<button class="btn #{name}">#{label}</button>) } do %>
|
|
59
|
+
<button type="button" class="btn <%= name %>"><%= label %></button>
|
|
57
60
|
<% end %>
|
|
58
61
|
<% end %>
|
|
59
62
|
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
app/views/style/_host.html.erb.
|
|
22
22
|
|
|
23
23
|
Specimens use ONLY the engine's own classes (engine.css component layer plus
|
|
24
|
-
engine-motion.css motion/effect/leveling layer), so a consumer that
|
|
25
|
-
engine.css and
|
|
24
|
+
engine-motion.css motion/effect/leveling layer), so a consumer that imports
|
|
25
|
+
the engine's Tailwind entry (host.css: engine.css and engine-motion.css)
|
|
26
|
+
styles them for real. The primitive
|
|
26
27
|
CSS is NOT inlined here - the consumer's Tailwind build provides it. Page
|
|
27
28
|
chrome uses the engine's own .card / .label-upper / theme tokens. %>
|
|
28
29
|
<%
|
data/lib/studio/engine.rb
CHANGED
|
@@ -330,6 +330,13 @@ module Studio
|
|
|
330
330
|
# Studio.survey_definitions_path, loaded on boot and again on each
|
|
331
331
|
# development reload so an edited definition takes effect without a restart.
|
|
332
332
|
# Surveys defined in an initializer instead stay registered either way.
|
|
333
|
+
# The host's Tailwind entry for this engine imports host.css, not the bare
|
|
334
|
+
# engine.css (Studio::TailwindEntry says why). Rake's tailwindcss:engines
|
|
335
|
+
# runs after :environment, so every initializer has run by then.
|
|
336
|
+
initializer "studio.tailwind_entry", after: :load_config_initializers do
|
|
337
|
+
Studio::TailwindEntry.install!
|
|
338
|
+
end
|
|
339
|
+
|
|
333
340
|
config.to_prepare do
|
|
334
341
|
dir = Studio.survey_definitions_path
|
|
335
342
|
Studio::Survey.load_definitions!(Rails.root.join(dir)) if dir.present? && Rails.root
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Studio
|
|
7
|
+
# Points a host's Tailwind entry for this engine at host.css.
|
|
8
|
+
#
|
|
9
|
+
# tailwindcss-rails' engines contract (Tailwindcss::Engines.bundle, run by
|
|
10
|
+
# `tailwindcss:engines`, a prerequisite of tailwindcss:build, :watch and so
|
|
11
|
+
# of assets:precompile) writes app/assets/builds/tailwind/studio_engine.css as
|
|
12
|
+
# an @import of the gem's app/assets/tailwind/studio_engine/engine.css. That
|
|
13
|
+
# file has to stay the NARROW component layer, because a build may import it
|
|
14
|
+
# by path and want only the classes it names (the hub's client kit, held to a
|
|
15
|
+
# 100 KB budget). So right after the bundle runs, this rewrites the entry to
|
|
16
|
+
# import host.css instead: engine.css plus the engine's @source scan and the
|
|
17
|
+
# motion layer. A host's `@import "../builds/tailwind/studio_engine";` then
|
|
18
|
+
# brings all three, with no `@config`, no safelist and no rake task of its
|
|
19
|
+
# own, and the gem's path is resolved here in Ruby, never by Node running
|
|
20
|
+
# bundler.
|
|
21
|
+
#
|
|
22
|
+
# Prepended rather than added as a rake action: a second action on
|
|
23
|
+
# `tailwindcss:engines` runs in task-load order, and one that ran BEFORE the
|
|
24
|
+
# bundle would be deleted by it (the bundle removes and rewrites the file).
|
|
25
|
+
# super-then-write is ordered by construction.
|
|
26
|
+
module TailwindEntry
|
|
27
|
+
HOST_CSS = "app/assets/tailwind/studio_engine/host.css"
|
|
28
|
+
ENTRY = "app/assets/builds/tailwind/studio_engine.css"
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def entry_path(app_root) = Pathname(app_root).join(ENTRY)
|
|
33
|
+
|
|
34
|
+
def contents(engine_root)
|
|
35
|
+
<<~CSS
|
|
36
|
+
/* DO NOT MODIFY THIS FILE, it was auto-generated by studio-engine (Studio::TailwindEntry) */
|
|
37
|
+
|
|
38
|
+
@import "#{Pathname(engine_root).join(HOST_CSS)}";
|
|
39
|
+
CSS
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Writes the entry. Only when the bundle wrote one: an app whose engines
|
|
43
|
+
# contract found no engine.css gets nothing it did not ask for.
|
|
44
|
+
def write!(app_root:, engine_root:)
|
|
45
|
+
path = entry_path(app_root)
|
|
46
|
+
return false unless path.file?
|
|
47
|
+
|
|
48
|
+
File.write(path, contents(engine_root))
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Wraps Tailwindcss::Engines.bundle.
|
|
53
|
+
module Bundle
|
|
54
|
+
def bundle(...)
|
|
55
|
+
result = super
|
|
56
|
+
Studio::TailwindEntry.write!(app_root: Rails.root, engine_root: Studio::Engine.root)
|
|
57
|
+
result
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Idempotent. A host without tailwindcss-rails' engines contract is left
|
|
62
|
+
# alone: it never had the entry this rewrites.
|
|
63
|
+
def install!
|
|
64
|
+
require "tailwindcss/engines"
|
|
65
|
+
singleton = ::Tailwindcss::Engines.singleton_class
|
|
66
|
+
singleton.prepend(Bundle) unless singleton.ancestors.include?(Bundle)
|
|
67
|
+
true
|
|
68
|
+
rescue LoadError
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// DEPRECATED — kept for ONE release so consumers can adopt on their own cards.
|
|
2
|
+
//
|
|
3
|
+
// The engine's Tailwind layer is CSS-first: the tokens below now live in
|
|
4
|
+
// app/assets/tailwind/studio_engine/theme.css (@theme), and every host gets
|
|
5
|
+
// them, the engine's @source scan and the motion layer from its one
|
|
6
|
+
// `@import "../builds/tailwind/studio_engine";` line. Nothing in the engine
|
|
7
|
+
// reads this file any more. A host still on `@config` + this preset builds
|
|
8
|
+
// exactly as before; test/integration/tailwind_preset_parity_test.rb holds it
|
|
9
|
+
// to the same utilities as theme.css until it goes.
|
|
10
|
+
//
|
|
11
|
+
// Migration (README, "Tailwind"): drop the host's `@config` line,
|
|
12
|
+
// config/tailwind.config.js, its safelist and lib/tasks/tailwindcss_engine_motion.rake.
|
|
13
|
+
// This file is removed in the release after every consumer has adopted.
|
|
14
|
+
//
|
|
1
15
|
// Shared Tailwind config for all Studio apps
|
|
2
16
|
// Apps spread from this in their own tailwind.config.js
|
|
3
17
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.114.
|
|
4
|
+
version: 0.114.2.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -371,6 +371,8 @@ files:
|
|
|
371
371
|
- app/assets/stylesheets/studio/sticky_table_header.css
|
|
372
372
|
- app/assets/tailwind/studio_engine/engine-motion.css
|
|
373
373
|
- app/assets/tailwind/studio_engine/engine.css
|
|
374
|
+
- app/assets/tailwind/studio_engine/host.css
|
|
375
|
+
- app/assets/tailwind/studio_engine/theme.css
|
|
374
376
|
- app/components/previews/studio/badge_component_preview.rb
|
|
375
377
|
- app/components/previews/studio/badge_component_preview/schemes.html.erb
|
|
376
378
|
- app/components/previews/studio/badge_component_preview/tones.html.erb
|
|
@@ -790,6 +792,7 @@ files:
|
|
|
790
792
|
- lib/studio/survey.rb
|
|
791
793
|
- lib/studio/survey/breakdown.rb
|
|
792
794
|
- lib/studio/survey/export.rb
|
|
795
|
+
- lib/studio/tailwind_entry.rb
|
|
793
796
|
- lib/studio/theme_resolver.rb
|
|
794
797
|
- lib/studio/ui_primitives.rb
|
|
795
798
|
- lib/studio/username_generator.rb
|