maquina-components 0.5.1 → 0.6.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -2
  3. data/app/assets/stylesheets/alert.css +169 -135
  4. data/app/assets/stylesheets/badge.css +143 -138
  5. data/app/assets/stylesheets/breadcrumbs.css +183 -166
  6. data/app/assets/stylesheets/calendar.css +219 -206
  7. data/app/assets/stylesheets/card.css +126 -120
  8. data/app/assets/stylesheets/combobox.css +208 -191
  9. data/app/assets/stylesheets/date_picker.css +144 -134
  10. data/app/assets/stylesheets/drawer.css +158 -125
  11. data/app/assets/stylesheets/dropdown_menu.css +203 -190
  12. data/app/assets/stylesheets/empty.css +125 -117
  13. data/app/assets/stylesheets/form.css +625 -525
  14. data/app/assets/stylesheets/header.css +63 -47
  15. data/app/assets/stylesheets/menu_button.css +121 -98
  16. data/app/assets/stylesheets/pagination.css +130 -125
  17. data/app/assets/stylesheets/separator.css +23 -11
  18. data/app/assets/stylesheets/sidebar.css +447 -401
  19. data/app/assets/stylesheets/stats.css +77 -67
  20. data/app/assets/stylesheets/table.css +184 -166
  21. data/app/assets/stylesheets/toast.css +287 -278
  22. data/app/assets/stylesheets/toggle_group.css +132 -139
  23. data/app/assets/tailwind/maquina_components_engine/engine.css +54 -15
  24. data/app/assets/tailwind/maquina_components_engine/tokens.css +101 -0
  25. data/app/helpers/maquina_components/combobox_helper.rb +32 -24
  26. data/app/helpers/maquina_components/components_helper.rb +30 -6
  27. data/app/helpers/maquina_components/dropdown_menu_helper.rb +12 -12
  28. data/app/helpers/maquina_components/icons_helper.rb +26 -2
  29. data/app/javascript/controllers/drawer_trigger_controller.js +24 -3
  30. data/app/views/components/_label.html.erb +14 -0
  31. data/app/views/components/_table.html.erb +2 -2
  32. data/app/views/components/calendar/_week.html.erb +4 -12
  33. data/app/views/components/drawer/_close.html.erb +1 -1
  34. data/app/views/components/drawer/_description.html.erb +6 -0
  35. data/app/views/components/drawer/_header.html.erb +1 -1
  36. data/app/views/components/drawer/_provider.html.erb +13 -4
  37. data/app/views/components/drawer/_section.html.erb +7 -0
  38. data/app/views/components/drawer/_separator.html.erb +11 -0
  39. data/app/views/components/drawer/_title.html.erb +6 -0
  40. data/app/views/components/drawer/_trigger.html.erb +24 -7
  41. data/app/views/components/sidebar/_group_action.html.erb +26 -0
  42. data/app/views/components/sidebar/_menu_action.html.erb +28 -0
  43. data/app/views/components/sidebar/_menu_badge.html.erb +10 -0
  44. data/app/views/components/sidebar/_menu_button.html.erb +3 -2
  45. data/app/views/components/sidebar/_menu_link.html.erb +3 -2
  46. data/app/views/components/sidebar/_separator.html.erb +12 -0
  47. data/lib/generators/maquina_components/install/install_generator.rb +36 -1
  48. data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +5 -0
  49. data/lib/generators/maquina_components/install/templates/shape_state_tokens.css.tt +76 -0
  50. data/lib/generators/maquina_components/install/templates/theme.css.tt +10 -3
  51. data/lib/maquina-components.rb +29 -1
  52. data/lib/maquina_components/doctor.rb +280 -0
  53. data/lib/maquina_components/version.rb +1 -1
  54. data/lib/tasks/maquina_components_tasks.rake +16 -4
  55. metadata +14 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9159362a034d89b9abf1b6a5a636851942f69c32491eea74c438c9d8dc678b57
4
- data.tar.gz: 9ab23823a833fdfb8d02727fea268539ea0dd26d974b3212aedf90c87d2c5963
3
+ metadata.gz: 0a445c4fe008c07069fd7d0e9fd4249c9a169410e9a53fa6cdaa6c2455351b92
4
+ data.tar.gz: c0964a27ecfdbd92797ee8c03a24d7c9e6db2b5e41147d2d81bf18ff2e38540d
5
5
  SHA512:
6
- metadata.gz: 2b7d4230c490e4a98db049a2a527300b060873b6befb6925c2825455923e2179f353ffadbca0f90ebda98779ef711083b819c60a13763a5d982be3d22b73ab4e
7
- data.tar.gz: c0671e469d43f1fbc265638755f453365748f2cf3d39e435ffa5218d81f9453a55f87cd1babe8acdfb2ccfdfb21a4e2415366a82388ddabb74f9a127a3bdc821
6
+ metadata.gz: 7c59d1819376a7242cbac12758ceffdb6e221184a29150b6f70364b275545f84d8da7c3235a5d9bdc3a1044e08622dba5b379615e2bc7053e8647506f83b6b7c
7
+ data.tar.gz: 340d7fcd15e476d17e2244cb7e7b29563b27e7fff360c09f9427d2ccc0491379b4dc5c412e4b8be9447159bbb75fd9d47bade573fbfb9497284d3d9ada629931
data/README.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  UI components for Ruby on Rails, built with ERB, TailwindCSS 4.0, and Stimulus.
4
4
 
5
+ > [!IMPORTANT]
6
+ > **0.6.0 has breaking changes.** Engine CSS moved into `@layer components`,
7
+ > which changes what wins in your app. Most notably, the `* { border-color }`
8
+ > shim in the `theme.css` you already installed must be wrapped in
9
+ > `@layer base` — unlayered, it now outranks every engine rule and flattens
10
+ > the tinted alert, toast and badge borders. That one affects every existing
11
+ > app and fails silently.
12
+ >
13
+ > Before upgrading, read [docs/upgrading.md](docs/upgrading.md). After
14
+ > upgrading, run `bin/rails maquina:doctor` — it prints file:line for every
15
+ > pattern the release changes and never edits anything. An appendix in the
16
+ > upgrading guide restores the 0.5.1 look with one token block.
17
+
5
18
  ---
6
19
 
7
20
  ## Why This Exists
@@ -43,6 +56,7 @@ If you're open to trying maquina_components and providing feedback, you're welco
43
56
  - **Stimulus controllers** only used where interactivity is needed
44
57
  - **Dark mode** support via CSS variables
45
58
  - **shadcn/ui theming** convention (works with their color system)
59
+ - **Design tokens** for shape, focus rings, elevation and weight — [theming guide](docs/theming.md)
46
60
  - **Composable** — small partials you can combine freely
47
61
 
48
62
  ![Test dummy app with light mode](/imgs/light.png)
@@ -136,6 +150,28 @@ generates views built with maquina_components. Customize the templates as needed
136
150
 
137
151
  ---
138
152
 
153
+ ## Upgrading
154
+
155
+ **0.5.1 → 0.6.0 is a breaking upgrade** — see the note at the top of this
156
+ README and [docs/upgrading.md](docs/upgrading.md) for the seven changes, each
157
+ with measurements, plus an appendix that reverts every visual change with one
158
+ token block.
159
+
160
+ Re-running the install generator is safe — it is idempotent and never rewrites
161
+ your palette. After upgrading, run the doctor:
162
+
163
+ ```bash
164
+ bin/rails maquina:doctor
165
+ ```
166
+
167
+ It scans your CSS, views and JavaScript and prints file:line for every pattern
168
+ the new release changes, grouped `BREAKING` / `REVIEW` / `CLEANUP`. It is
169
+ advisory: it never edits anything and never fails a build. See
170
+ [docs/upgrading.md](docs/upgrading.md) for what changed and how to keep the
171
+ previous look.
172
+
173
+ ---
174
+
139
175
  ## Available Components
140
176
 
141
177
  ### Layout Components
@@ -381,6 +417,22 @@ flash[:success] = "Changes saved successfully!"
381
417
 
382
418
  Components use CSS variables following the [shadcn/ui theming convention](https://ui.shadcn.com/docs/theming).
383
419
 
420
+ Colors are not the only variables: shape, focus rings, elevation and weight are
421
+ design tokens too, so reshaping the whole library is a short list of
422
+ declarations rather than override CSS fighting the cascade.
423
+
424
+ ```css
425
+ :root {
426
+ --control-radius: 0; /* buttons, inputs, badges, menu items */
427
+ --surface-radius: 0; /* cards, popovers, toasts, alerts */
428
+ --elevation-raised: none;
429
+ }
430
+ ```
431
+
432
+ **[Theming guide](docs/theming.md)** — the full token list, flat and brutalist
433
+ themes in a dozen lines each, recoloring the control marks, dark mode, and how
434
+ to pin a single component without redefining a role.
435
+
384
436
  The install generator adds default theme variables. Customize them in `app/assets/tailwind/application.css`:
385
437
 
386
438
  ```css
@@ -469,12 +521,54 @@ cd test/dummy
469
521
  bin/rails server
470
522
  ```
471
523
 
472
- Run tests:
524
+ Run tests (build the dummy app's CSS first — the stylesheet guards assert on the
525
+ compiled output):
526
+
527
+ ```bash
528
+ cd test/dummy && bin/rails tailwindcss:build && cd -
529
+ bin/test
530
+ ```
531
+
532
+ ### Running against the published branch
533
+
534
+ Both commands above run against the source tree. To exercise the gem the way a
535
+ consuming app resolves it — from GitHub rather than from `lib/` and `app/` —
536
+ use `Gemfile.branch`:
473
537
 
474
538
  ```bash
475
- bin/rails test
539
+ BUNDLE_GEMFILE=Gemfile.branch bundle install
540
+ BUNDLE_GEMFILE=Gemfile.branch bin/test
541
+
542
+ # or drive the app by hand
543
+ cd test/dummy
544
+ BUNDLE_GEMFILE=../../Gemfile.branch bin/rails tailwindcss:build
545
+ BUNDLE_GEMFILE=../../Gemfile.branch bin/rails server
476
546
  ```
477
547
 
548
+ It defaults to the current release branch; override with `MAQUINA_BRANCH`.
549
+
550
+ `test/dummy/config/boot.rb` skips its `$LOAD_PATH` unshift under
551
+ `Gemfile.branch`, so the source tree cannot shadow the resolved gem — without
552
+ that the check would pass no matter what was on the branch. `Gemfile.common`
553
+ holds the app gems both Gemfiles share, so the two cannot drift.
554
+
555
+ Two limits worth knowing. It resolves the **pushed** commit, so local work you
556
+ have not pushed is not what you are testing — `bundle list | grep maquina`
557
+ prints the SHA. And Bundler resolves a git source from a checkout, so every
558
+ file is present whether or not `spec.files` lists it: this catches a bad
559
+ dependency or a broken require path, but a file missing from the packaged gem
560
+ stays invisible. For that, build the gem and install what comes out:
561
+
562
+ ```bash
563
+ gem build maquina-components.gemspec
564
+ gem contents --show-install-dir maquina-components # after installing it
565
+ ```
566
+
567
+ Worth confirming either way: `bin/rails -T | grep maquina` (the rake task
568
+ ships), `bin/rails generate --help` (the generators are found), and that the
569
+ previews render — those exercise `lib/tasks`, `lib/generators/**/templates` and
570
+ `app/**`, which are the parts a packaging mistake tends to drop.
571
+
478
572
  ---
479
573
 
480
574
  ## Claude Code Skill
@@ -3,141 +3,175 @@
3
3
  * Alert component for displaying callouts, messages, and notifications.
4
4
  * Uses data attributes for styling to maintain consistency with other components.
5
5
  * Fully compatible with dark mode via CSS variables.
6
+ *
7
+ * Cascade order in this file (full contract documented in form.css):
8
+ * 1. base [data-component="alert"], [data-alert-part="…"]
9
+ * 2. sizes — none
10
+ * 3. variants [data-component="alert"]:where([data-variant="…"], [data-has-icon])
11
+ * 4. states — none
12
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
13
+ * Every rule is flattened to 0,1,0 with :where() and lives in
14
+ * @layer components, so a caller's utility class wins and source order is
15
+ * the only tie-breaker. (Rules whose target is a bare element read 0,1,1 — an
16
+ * element selector cannot be zeroed away.)
6
17
  */
7
18
 
8
- /* ===== Base Alert Styles ===== */
9
- [data-component="alert"] {
10
- position: relative;
11
- display: grid;
12
- grid-template-columns: 1fr;
13
- @apply w-full rounded-lg border p-4 text-sm;
14
-
15
- /* Default colors */
16
- background-color: var(--background);
17
- color: var(--foreground);
18
- border-color: var(--border);
19
+ @layer components {
20
+ /* ===== Base Alert Styles ===== */
21
+ [data-component="alert"] {
22
+ position: relative;
23
+ display: grid;
24
+ grid-template-columns: 1fr;
25
+ @apply w-full border p-4 text-sm;
26
+ border-radius: var(--alert-radius, var(--surface-radius, 0.5rem));
27
+
28
+ /* Default colors — an alert sits *on* a surface, so it paints --card, not
29
+ the page background (painting --background measures ΔL 0.00 inside a card) */
30
+ background-color: var(--card, var(--background));
31
+ color: var(--foreground);
32
+ border-color: var(--border);
33
+ }
34
+
35
+ /* Alert with icon - add left padding for icon space */
36
+ [data-component="alert"]:where([data-has-icon="true"]) {
37
+ grid-template-columns: auto 1fr;
38
+ @apply gap-3;
39
+ }
40
+
41
+ /* ===== Icon Support ===== */
42
+ [data-component="alert"] > svg:where(:first-child),
43
+ :where([data-component="alert"]) [data-alert-part="icon"] {
44
+ @apply size-4 shrink-0;
45
+ color: var(--foreground);
46
+ /* Align with first line of text */
47
+ margin-top: 0.125rem;
48
+ }
49
+
50
+ /* ===== Alert Title ===== */
51
+ :where([data-component="alert"]) [data-alert-part="title"] {
52
+ @apply leading-none tracking-tight;
53
+ font-weight: var(--label-weight, 500);
54
+ color: var(--foreground);
55
+ }
56
+
57
+ /* Title followed by description needs margin */
58
+ :where([data-component="alert"]) [data-alert-part="title"]:where(:has(+ [data-alert-part="description"])) {
59
+ @apply mb-1;
60
+ }
61
+
62
+ /* ===== Alert Description ===== */
63
+ :where([data-component="alert"]) [data-alert-part="description"] {
64
+ @apply text-sm;
65
+ color: var(--muted-foreground);
66
+ }
67
+
68
+ /* Nested paragraphs */
69
+ :where([data-component="alert"]) [data-alert-part="description"] p {
70
+ @apply leading-relaxed;
71
+ }
72
+
73
+ /* Lists inside description */
74
+ :where([data-component="alert"]) [data-alert-part="description"] ul {
75
+ @apply mt-2 list-inside list-disc;
76
+ }
77
+
78
+ /* ===== Variant: Default ===== */
79
+ [data-component="alert"]:where([data-variant="default"]) {
80
+ background-color: var(--card, var(--background));
81
+ color: var(--foreground);
82
+ border-color: var(--border);
83
+ }
84
+
85
+ [data-component="alert"]:where([data-variant="default"]) > svg:where(:first-child),
86
+ :where([data-component="alert"][data-variant="default"]) [data-alert-part="icon"] {
87
+ color: var(--foreground);
88
+ }
89
+
90
+ /* ===== Variant: Destructive ===== */
91
+ [data-component="alert"]:where([data-variant="destructive"]) {
92
+ background-color: var(--destructive);
93
+ color: var(--destructive-foreground);
94
+ border-color: var(--destructive);
95
+ }
96
+
97
+ :where([data-component="alert"][data-variant="destructive"]) [data-alert-part="title"] {
98
+ color: var(--destructive-foreground);
99
+ }
100
+
101
+ :where([data-component="alert"][data-variant="destructive"]) [data-alert-part="description"] {
102
+ color: var(--destructive-foreground);
103
+ opacity: 0.9;
104
+ }
105
+
106
+ [data-component="alert"]:where([data-variant="destructive"]) > svg:where(:first-child),
107
+ :where([data-component="alert"][data-variant="destructive"]) [data-alert-part="icon"] {
108
+ color: var(--destructive-foreground);
109
+ }
110
+
111
+ /* ===== Variant: Success ===== */
112
+ [data-component="alert"]:where([data-variant="success"]) {
113
+ background-color: var(--success, oklch(0.627 0.194 149.214));
114
+ color: var(--success-foreground, oklch(0.985 0 0));
115
+ border-color: var(--success, oklch(0.627 0.194 149.214));
116
+ }
117
+
118
+ :where([data-component="alert"][data-variant="success"]) [data-alert-part="title"] {
119
+ color: var(--success-foreground, oklch(0.985 0 0));
120
+ }
121
+
122
+ :where([data-component="alert"][data-variant="success"]) [data-alert-part="description"] {
123
+ color: var(--success-foreground, oklch(0.985 0 0));
124
+ opacity: 0.9;
125
+ }
126
+
127
+ [data-component="alert"]:where([data-variant="success"]) > svg:where(:first-child),
128
+ :where([data-component="alert"][data-variant="success"]) [data-alert-part="icon"] {
129
+ color: var(--success-foreground, oklch(0.985 0 0));
130
+ }
131
+
132
+ /* ===== Variant: Warning ===== */
133
+ [data-component="alert"]:where([data-variant="warning"]) {
134
+ background-color: var(--warning, oklch(0.769 0.188 70.08));
135
+ color: var(--warning-foreground, oklch(0.216 0.006 56.043));
136
+ border-color: var(--warning, oklch(0.769 0.188 70.08));
137
+ }
138
+
139
+ :where([data-component="alert"][data-variant="warning"]) [data-alert-part="title"] {
140
+ color: var(--warning-foreground, oklch(0.216 0.006 56.043));
141
+ }
142
+
143
+ :where([data-component="alert"][data-variant="warning"]) [data-alert-part="description"] {
144
+ color: var(--warning-foreground, oklch(0.216 0.006 56.043));
145
+ opacity: 0.9;
146
+ }
147
+
148
+ [data-component="alert"]:where([data-variant="warning"]) > svg:where(:first-child),
149
+ :where([data-component="alert"][data-variant="warning"]) [data-alert-part="icon"] {
150
+ color: var(--warning-foreground, oklch(0.216 0.006 56.043));
151
+ }
152
+
153
+ /* ===== Variant: Info ===== */
154
+ /*
155
+ * --info has no engine default on purpose: an undefined --info must fall back
156
+ * to the neutral default treatment, never to --primary (that renders an
157
+ * informational alert in the brand accent, so it reads as a call to action).
158
+ */
159
+ [data-component="alert"]:where([data-variant="info"]) {
160
+ background-color: var(--info, var(--card, var(--background)));
161
+ color: var(--info-foreground, var(--foreground));
162
+ border-color: var(--info, var(--border));
163
+ }
164
+
165
+ :where([data-component="alert"][data-variant="info"]) [data-alert-part="title"] {
166
+ color: var(--info-foreground, var(--foreground));
167
+ }
168
+
169
+ :where([data-component="alert"][data-variant="info"]) [data-alert-part="description"] {
170
+ color: var(--info-foreground, var(--muted-foreground));
171
+ }
172
+
173
+ [data-component="alert"]:where([data-variant="info"]) > svg:where(:first-child),
174
+ :where([data-component="alert"][data-variant="info"]) [data-alert-part="icon"] {
175
+ color: var(--info-foreground, var(--foreground));
176
+ }
19
177
  }
20
-
21
- /* Alert with icon - add left padding for icon space */
22
- [data-component="alert"][data-has-icon="true"] {
23
- grid-template-columns: auto 1fr;
24
- @apply gap-3;
25
- }
26
-
27
- /* ===== Icon Support ===== */
28
- [data-component="alert"] > svg:first-child,
29
- [data-component="alert"] [data-alert-part="icon"] {
30
- @apply size-4 shrink-0;
31
- color: var(--foreground);
32
- /* Align with first line of text */
33
- margin-top: 0.125rem;
34
- }
35
-
36
- /* ===== Alert Title ===== */
37
- [data-component="alert"] [data-alert-part="title"] {
38
- @apply font-medium leading-none tracking-tight;
39
- color: var(--foreground);
40
- }
41
-
42
- /* Title followed by description needs margin */
43
- [data-component="alert"] [data-alert-part="title"]:has(+ [data-alert-part="description"]) {
44
- @apply mb-1;
45
- }
46
-
47
- /* ===== Alert Description ===== */
48
- [data-component="alert"] [data-alert-part="description"] {
49
- @apply text-sm;
50
- color: var(--muted-foreground);
51
- }
52
-
53
- /* Nested paragraphs */
54
- [data-component="alert"] [data-alert-part="description"] p {
55
- @apply leading-relaxed;
56
- }
57
-
58
- /* Lists inside description */
59
- [data-component="alert"] [data-alert-part="description"] ul {
60
- @apply mt-2 list-inside list-disc;
61
- }
62
-
63
- /* ===== Variant: Default ===== */
64
- [data-component="alert"][data-variant="default"] {
65
- background-color: var(--background);
66
- color: var(--foreground);
67
- border-color: var(--border);
68
- }
69
-
70
- [data-component="alert"][data-variant="default"] > svg:first-child,
71
- [data-component="alert"][data-variant="default"] [data-alert-part="icon"] {
72
- color: var(--foreground);
73
- }
74
-
75
- /* ===== Variant: Destructive ===== */
76
- [data-component="alert"][data-variant="destructive"] {
77
- background-color: var(--destructive);
78
- color: var(--destructive-foreground);
79
- border-color: var(--destructive);
80
- }
81
-
82
- [data-component="alert"][data-variant="destructive"] [data-alert-part="title"] {
83
- color: var(--destructive-foreground);
84
- }
85
-
86
- [data-component="alert"][data-variant="destructive"] [data-alert-part="description"] {
87
- color: var(--destructive-foreground);
88
- opacity: 0.9;
89
- }
90
-
91
- [data-component="alert"][data-variant="destructive"] > svg:first-child,
92
- [data-component="alert"][data-variant="destructive"] [data-alert-part="icon"] {
93
- color: var(--destructive-foreground);
94
- }
95
-
96
- /* ===== Variant: Success ===== */
97
- [data-component="alert"][data-variant="success"] {
98
- background-color: var(--success);
99
- color: var(--success-foreground);
100
- border-color: var(--success);
101
- }
102
-
103
- [data-component="alert"][data-variant="success"] [data-alert-part="title"] {
104
- color: var(--success-foreground);
105
- }
106
-
107
- [data-component="alert"][data-variant="success"] [data-alert-part="description"] {
108
- color: var(--success-foreground);
109
- opacity: 0.9;
110
- }
111
-
112
- [data-component="alert"][data-variant="success"] > svg:first-child,
113
- [data-component="alert"][data-variant="success"] [data-alert-part="icon"] {
114
- color: var(--success-foreground);
115
- }
116
-
117
- /* ===== Variant: Warning ===== */
118
- [data-component="alert"][data-variant="warning"] {
119
- background-color: var(--warning);
120
- color: var(--warning-foreground);
121
- border-color: var(--warning);
122
- }
123
-
124
- [data-component="alert"][data-variant="warning"] [data-alert-part="title"] {
125
- color: var(--warning-foreground);
126
- }
127
-
128
- [data-component="alert"][data-variant="warning"] [data-alert-part="description"] {
129
- color: var(--warning-foreground);
130
- opacity: 0.9;
131
- }
132
-
133
- [data-component="alert"][data-variant="warning"] > svg:first-child,
134
- [data-component="alert"][data-variant="warning"] [data-alert-part="icon"] {
135
- color: var(--warning-foreground);
136
- }
137
-
138
- /* ===== Dark Mode ===== */
139
- /*
140
- * Dark mode is handled automatically through CSS variables.
141
- * The theme variables change based on the .dark class on html/body.
142
- * No additional dark mode styles needed here.
143
- */