maquina-components 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -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: a849035735227db855acba5bb371ba284e6b1e8887b10c7072e1bcf007601944
4
+ data.tar.gz: 00a1f783571ac6ad7dffdc722256eb8a6c42831dd1a716786af3639775fef05a
5
5
  SHA512:
6
- metadata.gz: 2b7d4230c490e4a98db049a2a527300b060873b6befb6925c2825455923e2179f353ffadbca0f90ebda98779ef711083b819c60a13763a5d982be3d22b73ab4e
7
- data.tar.gz: c0671e469d43f1fbc265638755f453365748f2cf3d39e435ffa5218d81f9453a55f87cd1babe8acdfb2ccfdfb21a4e2415366a82388ddabb74f9a127a3bdc821
6
+ metadata.gz: d9e8e0d25d4d228d673ece1ab3c215f51c6ec8b05125b27952951922ce2ea4acbb3b399cb54efdbcaad90b6ab6357c58c24bb3330e47804ff647a8f8fa347961
7
+ data.tar.gz: b57bb6e7179f1c97a0c4a25f84e22fc9defa09c0f73b97b150df29f733c9fb56ddc5e3a950da816c92d661bdfa6ffd62b676240c4a83f70feafa7ffe554a595c
data/README.md CHANGED
@@ -43,6 +43,7 @@ If you're open to trying maquina_components and providing feedback, you're welco
43
43
  - **Stimulus controllers** only used where interactivity is needed
44
44
  - **Dark mode** support via CSS variables
45
45
  - **shadcn/ui theming** convention (works with their color system)
46
+ - **Design tokens** for shape, focus rings, elevation and weight — [theming guide](docs/theming.md)
46
47
  - **Composable** — small partials you can combine freely
47
48
 
48
49
  ![Test dummy app with light mode](/imgs/light.png)
@@ -134,6 +135,21 @@ generates views built with maquina_components. Customize the templates as needed
134
135
 
135
136
  **Prerequisite:** [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) must be installed first.
136
137
 
138
+ ### Upgrading
139
+
140
+ Re-running the install generator is safe — it is idempotent and never rewrites
141
+ your palette. After upgrading, run the doctor:
142
+
143
+ ```bash
144
+ bin/rails maquina:doctor
145
+ ```
146
+
147
+ It scans your CSS, views and JavaScript and prints file:line for every pattern
148
+ the new release changes, grouped `BREAKING` / `REVIEW` / `CLEANUP`. It is
149
+ advisory: it never edits anything and never fails a build. See
150
+ [docs/upgrading.md](docs/upgrading.md) for what changed and how to keep the
151
+ previous look.
152
+
137
153
  ---
138
154
 
139
155
  ## Available Components
@@ -381,6 +397,22 @@ flash[:success] = "Changes saved successfully!"
381
397
 
382
398
  Components use CSS variables following the [shadcn/ui theming convention](https://ui.shadcn.com/docs/theming).
383
399
 
400
+ Colors are not the only variables: shape, focus rings, elevation and weight are
401
+ design tokens too, so reshaping the whole library is a short list of
402
+ declarations rather than override CSS fighting the cascade.
403
+
404
+ ```css
405
+ :root {
406
+ --control-radius: 0; /* buttons, inputs, badges, menu items */
407
+ --surface-radius: 0; /* cards, popovers, toasts, alerts */
408
+ --elevation-raised: none;
409
+ }
410
+ ```
411
+
412
+ **[Theming guide](docs/theming.md)** — the full token list, flat and brutalist
413
+ themes in a dozen lines each, recoloring the control marks, dark mode, and how
414
+ to pin a single component without redefining a role.
415
+
384
416
  The install generator adds default theme variables. Customize them in `app/assets/tailwind/application.css`:
385
417
 
386
418
  ```css
@@ -469,12 +501,54 @@ cd test/dummy
469
501
  bin/rails server
470
502
  ```
471
503
 
472
- Run tests:
504
+ Run tests (build the dummy app's CSS first — the stylesheet guards assert on the
505
+ compiled output):
473
506
 
474
507
  ```bash
475
- bin/rails test
508
+ cd test/dummy && bin/rails tailwindcss:build && cd -
509
+ bin/test
476
510
  ```
477
511
 
512
+ ### Running against the published branch
513
+
514
+ Both commands above run against the source tree. To exercise the gem the way a
515
+ consuming app resolves it — from GitHub rather than from `lib/` and `app/` —
516
+ use `Gemfile.branch`:
517
+
518
+ ```bash
519
+ BUNDLE_GEMFILE=Gemfile.branch bundle install
520
+ BUNDLE_GEMFILE=Gemfile.branch bin/test
521
+
522
+ # or drive the app by hand
523
+ cd test/dummy
524
+ BUNDLE_GEMFILE=../../Gemfile.branch bin/rails tailwindcss:build
525
+ BUNDLE_GEMFILE=../../Gemfile.branch bin/rails server
526
+ ```
527
+
528
+ It defaults to the current release branch; override with `MAQUINA_BRANCH`.
529
+
530
+ `test/dummy/config/boot.rb` skips its `$LOAD_PATH` unshift under
531
+ `Gemfile.branch`, so the source tree cannot shadow the resolved gem — without
532
+ that the check would pass no matter what was on the branch. `Gemfile.common`
533
+ holds the app gems both Gemfiles share, so the two cannot drift.
534
+
535
+ Two limits worth knowing. It resolves the **pushed** commit, so local work you
536
+ have not pushed is not what you are testing — `bundle list | grep maquina`
537
+ prints the SHA. And Bundler resolves a git source from a checkout, so every
538
+ file is present whether or not `spec.files` lists it: this catches a bad
539
+ dependency or a broken require path, but a file missing from the packaged gem
540
+ stays invisible. For that, build the gem and install what comes out:
541
+
542
+ ```bash
543
+ gem build maquina-components.gemspec
544
+ gem contents --show-install-dir maquina-components # after installing it
545
+ ```
546
+
547
+ Worth confirming either way: `bin/rails -T | grep maquina` (the rake task
548
+ ships), `bin/rails generate --help` (the generators are found), and that the
549
+ previews render — those exercise `lib/tasks`, `lib/generators/**/templates` and
550
+ `app/**`, which are the parts a packaging mistake tends to drop.
551
+
478
552
  ---
479
553
 
480
554
  ## 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
- */