maquina-components 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdbbf854a700d7b0fa55c3b91f1975d7fd53282ddeb0a14db55011d055db96a3
4
- data.tar.gz: 1b69e2f23f390efa573e3192410125e8945ec182001ca8f2db431fee72523efc
3
+ metadata.gz: e04564783a082eb56e58395e1de24dc21deb1312c49ebc47e48428a4230e6925
4
+ data.tar.gz: 78630e11ca92ae6a964c2bc100a7357722d7618df3a5a0a7d0619bbc48eeb5d3
5
5
  SHA512:
6
- metadata.gz: 07a7b92b85cdaf2987c2a816133bd174e1d838ff9af4ce16166ea218729c0896f7f4bbe71c7c8caaa3f175196dbe097d6f426c28db96e0cdd703054a788ba4f9
7
- data.tar.gz: 53c9a63b128ed2cef878c3935185ed559d3e01274f1354ab34f438926a8e06cdac3980eabdd4c962e9a02765f7102c1694a106c9775cc05ce7f7358ed89add10
6
+ metadata.gz: 225bbf4a0100ac2f80a03e3bcf38f6080365db13228fade762fa5c8378631b2f0aa5d45d71654e2ecfa6577fe88f9c317163a8c29b44f003657146870bd3c44f
7
+ data.tar.gz: f3b563c5a789cb8039fc5e30e5461114556ca37289bd2b9859b0516973f1c4d7f54204745ad48628271ae24574522784cae4e8865f83e596326b5707d903a4ea
data/README.md CHANGED
@@ -152,8 +152,15 @@ bin/rails generate maquina_components:install --skip-helper
152
152
 
153
153
  | Component | Description | Documentation |
154
154
  |-----------|-------------|---------------|
155
+ | **Combobox** | Searchable dropdown with keyboard navigation | [Combobox](https://maquina.app/documentation/components/combobox/) |
155
156
  | **Toggle Group** | Single/multiple selection button group | [Toggle Group](https://maquina.app/documentation/components/toggle-group/) |
156
157
 
158
+ ### Feedback Components
159
+
160
+ | Component | Description | Documentation |
161
+ |-----------|-------------|---------------|
162
+ | **Toast** | Non-intrusive notifications with auto-dismiss | [Toast](https://maquina.app/documentation/components/toast/) |
163
+
157
164
  ### Form Components
158
165
 
159
166
  | Component | Data Attribute | Variants |
@@ -252,6 +259,41 @@ bin/rails generate maquina_components:install --skip-helper
252
259
  <%= pagination_nav(@pagy, :users_path) %>
253
260
  ```
254
261
 
262
+ ### Combobox
263
+
264
+ ```erb
265
+ <%= render "components/combobox", placeholder: "Select framework..." do |combobox_id| %>
266
+ <%= render "components/combobox/trigger", for_id: combobox_id, placeholder: "Select framework..." %>
267
+ <%= render "components/combobox/content", id: combobox_id do %>
268
+ <%= render "components/combobox/input", placeholder: "Search..." %>
269
+ <%= render "components/combobox/list" do %>
270
+ <%= render "components/combobox/option", value: "rails" do %>Ruby on Rails<% end %>
271
+ <%= render "components/combobox/option", value: "hanami" do %>Hanami<% end %>
272
+ <%= render "components/combobox/option", value: "sinatra" do %>Sinatra<% end %>
273
+ <% end %>
274
+ <%= render "components/combobox/empty" %>
275
+ <% end %>
276
+ <% end %>
277
+ ```
278
+
279
+ ### Toast Notifications
280
+
281
+ ```erb
282
+ <%# Add toaster to your layout %>
283
+ <%= render "components/toaster", position: :bottom_right do %>
284
+ <%= toast_flash_messages %>
285
+ <% end %>
286
+
287
+ <%# In your controller %>
288
+ flash[:success] = "Changes saved successfully!"
289
+
290
+ <%# Or use the JavaScript API %>
291
+ <script>
292
+ Toast.success("Profile updated!")
293
+ Toast.error("Something went wrong", { description: "Please try again." })
294
+ </script>
295
+ ```
296
+
255
297
  ### Tables
256
298
 
257
299
  ```erb
@@ -349,6 +391,10 @@ The install generator adds default theme variables. Customize them in `app/asset
349
391
  | `sidebar_open?(cookie_name)` | Check if the sidebar is expanded |
350
392
  | `pagination_nav(pagy, route)` | Render pagination from Pagy object |
351
393
  | `pagination_simple(pagy, route)` | Render simple Previous/Next pagination |
394
+ | `toast_flash_messages` | Render all flash messages as toasts |
395
+ | `toast(variant, title, **options)` | Render a single toast notification |
396
+ | `combobox(placeholder:, **options, &block)` | Builder pattern for combobox |
397
+ | `combobox_simple(options:, **options)` | Data-driven simple combobox |
352
398
 
353
399
  ---
354
400
 
@@ -379,8 +425,13 @@ The install generator adds default theme variables. Customize them in `app/asset
379
425
 
380
426
  ### Interactive
381
427
 
428
+ - **[Combobox](https://maquina.app/documentation/components/combobox/)** — Searchable dropdown selection
382
429
  - **[Toggle Group](https://maquina.app/documentation/components/toggle-group/)** — Toggle button groups
383
430
 
431
+ ### Feedback
432
+
433
+ - **[Toast](https://maquina.app/documentation/components/toast/)** — Toast notifications
434
+
384
435
  ### Forms
385
436
 
386
437
  - **[Form Components](https://maquina.app/documentation/components/form/)** — Buttons, inputs, and form styling
@@ -0,0 +1,218 @@
1
+ /* ===== Combobox Component Styles ===== */
2
+ /*
3
+ * Combobox component for autocomplete/search with selection.
4
+ * Uses HTML5 Popover API for positioning and light-dismiss.
5
+ * Uses data attributes for styling to avoid inline utility classes.
6
+ * Fully compatible with dark mode via CSS variables.
7
+ *
8
+ * Structure:
9
+ * - combobox (root with Stimulus controller)
10
+ * - trigger (button that opens popover)
11
+ * - content (popover container)
12
+ * - input (search field)
13
+ * - list (scrollable options)
14
+ * - option (selectable item)
15
+ * - group (logical grouping)
16
+ * - label (section heading)
17
+ * - separator (divider)
18
+ * - empty (no results message)
19
+ */
20
+
21
+ /* ===== Root Container ===== */
22
+ [data-component="combobox"] {
23
+ position: relative;
24
+ display: inline-block;
25
+ }
26
+
27
+ /* ===== Trigger Button ===== */
28
+ /* Trigger uses button component styles via data-component="button" */
29
+ [data-combobox-part="trigger"] {
30
+ @apply justify-between gap-2;
31
+ min-width: 200px;
32
+ }
33
+
34
+ [data-combobox-part="trigger"] [data-combobox-target="label"] {
35
+ @apply truncate text-left flex-1;
36
+ }
37
+
38
+ /* Placeholder state */
39
+ [data-combobox-part="trigger"][data-has-value="false"] [data-combobox-target="label"] {
40
+ color: var(--muted-foreground);
41
+ }
42
+
43
+ /* Icon styling */
44
+ [data-combobox-part="trigger"] > svg {
45
+ @apply size-4 shrink-0 opacity-50;
46
+ }
47
+
48
+ /* ===== Content (Popover) ===== */
49
+ [data-combobox-part="content"] {
50
+ background-color: var(--popover, var(--background));
51
+ color: var(--popover-foreground, var(--foreground));
52
+ border-color: var(--border);
53
+ @apply rounded-md border p-0 shadow-md outline-none;
54
+ @apply min-w-[200px];
55
+
56
+ /* Reset popover defaults - positioning handled by JS */
57
+ margin: 0;
58
+ padding: 0;
59
+ inset: unset;
60
+ }
61
+
62
+ /* Width variants */
63
+ [data-combobox-part="content"][data-width="sm"] {
64
+ @apply w-40;
65
+ }
66
+
67
+ [data-combobox-part="content"][data-width="default"] {
68
+ @apply w-[200px];
69
+ }
70
+
71
+ [data-combobox-part="content"][data-width="md"] {
72
+ @apply w-56;
73
+ }
74
+
75
+ [data-combobox-part="content"][data-width="lg"] {
76
+ @apply w-72;
77
+ }
78
+
79
+ [data-combobox-part="content"][data-width="full"] {
80
+ @apply w-full;
81
+ }
82
+
83
+ /* Alignment is handled by JavaScript for proper popover positioning */
84
+
85
+ /* Animation */
86
+ [data-combobox-part="content"]:popover-open {
87
+ animation: combobox-in 150ms ease-out;
88
+ }
89
+
90
+ @keyframes combobox-in {
91
+ from {
92
+ opacity: 0;
93
+ transform: scale(0.95) translateY(-4px);
94
+ }
95
+ to {
96
+ opacity: 1;
97
+ transform: scale(1) translateY(0);
98
+ }
99
+ }
100
+
101
+ /* ===== Search Input ===== */
102
+ [data-combobox-part="input-wrapper"] {
103
+ display: flex;
104
+ align-items: center;
105
+ border-bottom: 1px solid var(--border);
106
+ @apply px-3 gap-2;
107
+ }
108
+
109
+ [data-combobox-part="input-wrapper"] > svg {
110
+ @apply size-4 shrink-0 opacity-50;
111
+ }
112
+
113
+ [data-combobox-part="input"] {
114
+ background-color: transparent;
115
+ color: var(--foreground);
116
+ @apply flex h-10 w-full py-3 text-sm outline-none;
117
+ @apply placeholder:text-muted-foreground;
118
+ @apply disabled:cursor-not-allowed disabled:opacity-50;
119
+ }
120
+
121
+ /* ===== Options List ===== */
122
+ [data-combobox-part="list"] {
123
+ @apply max-h-[300px] overflow-y-auto overflow-x-hidden p-1;
124
+ }
125
+
126
+ /* ===== Option Item ===== */
127
+ [data-combobox-part="option"] {
128
+ position: relative;
129
+ display: flex;
130
+ align-items: center;
131
+ cursor: default;
132
+ user-select: none;
133
+ color: var(--popover-foreground, var(--foreground));
134
+ @apply gap-2 rounded-sm py-1.5 text-sm outline-none transition-colors;
135
+ @apply pl-8 pr-2; /* Space for check icon on left */
136
+ }
137
+
138
+ /* Hover state */
139
+ [data-combobox-part="option"]:hover:not([aria-disabled="true"]) {
140
+ background-color: var(--accent);
141
+ color: var(--accent-foreground);
142
+ cursor: pointer;
143
+ }
144
+
145
+ /* Focus state (keyboard navigation) */
146
+ [data-combobox-part="option"]:focus {
147
+ background-color: var(--accent);
148
+ color: var(--accent-foreground);
149
+ }
150
+
151
+ /* Selected state */
152
+ [data-combobox-part="option"][data-selected="true"] {
153
+ background-color: var(--accent);
154
+ color: var(--accent-foreground);
155
+ }
156
+
157
+ /* Disabled state */
158
+ [data-combobox-part="option"][aria-disabled="true"] {
159
+ @apply opacity-50 cursor-not-allowed pointer-events-none;
160
+ }
161
+
162
+ /* Hidden state (filtered out) */
163
+ [data-combobox-part="option"][hidden] {
164
+ display: none;
165
+ }
166
+
167
+ /* Check indicator */
168
+ [data-combobox-part="check"] {
169
+ position: absolute;
170
+ left: 8px;
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ @apply size-4;
175
+ }
176
+
177
+ [data-combobox-part="check"].invisible {
178
+ visibility: hidden;
179
+ }
180
+
181
+ [data-combobox-part="check"] svg {
182
+ @apply size-4;
183
+ }
184
+
185
+ /* ===== Empty State ===== */
186
+ [data-combobox-part="empty"] {
187
+ @apply py-6 text-center text-sm;
188
+ color: var(--muted-foreground);
189
+ }
190
+
191
+ [data-combobox-part="empty"][hidden] {
192
+ display: none;
193
+ }
194
+
195
+ /* ===== Group ===== */
196
+ [data-combobox-part="group"] {
197
+ display: flex;
198
+ flex-direction: column;
199
+ }
200
+
201
+ /* ===== Label ===== */
202
+ [data-combobox-part="label"] {
203
+ @apply px-2 py-1.5 text-xs font-medium;
204
+ color: var(--muted-foreground);
205
+ }
206
+
207
+ /* ===== Separator ===== */
208
+ [data-combobox-part="separator"] {
209
+ @apply -mx-1 my-1 h-px;
210
+ background-color: var(--border);
211
+ }
212
+
213
+ /* ===== Dark Mode ===== */
214
+ /*
215
+ * Dark mode is handled automatically through CSS variables.
216
+ * The theme variables change based on the .dark class on html/body.
217
+ * No additional dark mode styles needed here.
218
+ */