maquina-components 0.4.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -1
- data/app/assets/stylesheets/breadcrumbs.css +21 -0
- data/app/assets/stylesheets/combobox.css +36 -1
- data/app/assets/stylesheets/drawer.css +135 -0
- data/app/assets/stylesheets/menu_button.css +155 -0
- data/app/assets/stylesheets/separator.css +20 -0
- data/app/assets/stylesheets/sidebar.css +12 -0
- data/app/assets/stylesheets/stats.css +101 -0
- data/app/assets/tailwind/maquina_components_engine/engine.css +4 -0
- data/app/helpers/maquina_components/combobox_helper.rb +2 -1
- data/app/helpers/maquina_components/components_helper.rb +33 -0
- data/app/helpers/maquina_components/drawer_helper.rb +19 -0
- data/app/helpers/maquina_components/icons_helper.rb +68 -1
- data/app/javascript/controllers/breadcrumb_controller.js +39 -30
- data/app/javascript/controllers/combobox_controller.js +9 -0
- data/app/javascript/controllers/drawer_controller.js +287 -0
- data/app/javascript/controllers/drawer_trigger_controller.js +58 -0
- data/app/javascript/controllers/menu_button_controller.js +107 -29
- data/app/javascript/controllers/sidebar_controller.js +6 -8
- data/app/javascript/controllers/sidebar_trigger_controller.js +33 -4
- data/app/javascript/controllers/toaster_controller.js +12 -0
- data/app/views/components/_alert.html.erb +3 -1
- data/app/views/components/_badge.html.erb +7 -1
- data/app/views/components/_breadcrumbs.html.erb +1 -1
- data/app/views/components/_calendar.html.erb +1 -1
- data/app/views/components/_card.html.erb +1 -1
- data/app/views/components/_combobox.html.erb +3 -2
- data/app/views/components/_date_picker.html.erb +4 -3
- data/app/views/components/_drawer.html.erb +37 -0
- data/app/views/components/_dropdown.html.erb +21 -24
- data/app/views/components/_dropdown_menu.html.erb +1 -1
- data/app/views/components/_empty.html.erb +1 -1
- data/app/views/components/_header.html.erb +1 -1
- data/app/views/components/_menu_button.html.erb +29 -34
- data/app/views/components/_pagination.html.erb +1 -1
- data/app/views/components/_separator.html.erb +9 -10
- data/app/views/components/_sidebar.html.erb +1 -1
- data/app/views/components/_simple_table.html.erb +3 -1
- data/app/views/components/_table.html.erb +1 -1
- data/app/views/components/_toast.html.erb +6 -1
- data/app/views/components/_toaster.html.erb +1 -1
- data/app/views/components/_toggle_group.html.erb +1 -1
- data/app/views/components/alert/_description.html.erb +1 -1
- data/app/views/components/alert/_title.html.erb +1 -1
- data/app/views/components/breadcrumbs/_ellipsis.html.erb +1 -1
- data/app/views/components/breadcrumbs/_item.html.erb +1 -1
- data/app/views/components/breadcrumbs/_link.html.erb +1 -1
- data/app/views/components/breadcrumbs/_list.html.erb +1 -1
- data/app/views/components/breadcrumbs/_page.html.erb +1 -1
- data/app/views/components/breadcrumbs/_separator.html.erb +1 -1
- data/app/views/components/calendar/_header.html.erb +1 -1
- data/app/views/components/calendar/_week.html.erb +1 -1
- data/app/views/components/card/_action.html.erb +1 -1
- data/app/views/components/card/_content.html.erb +1 -1
- data/app/views/components/card/_description.html.erb +1 -1
- data/app/views/components/card/_footer.html.erb +1 -1
- data/app/views/components/card/_header.html.erb +1 -1
- data/app/views/components/card/_title.html.erb +1 -1
- data/app/views/components/combobox/_content.html.erb +1 -1
- data/app/views/components/combobox/_empty.html.erb +3 -3
- data/app/views/components/combobox/_group.html.erb +1 -1
- data/app/views/components/combobox/_input.html.erb +1 -1
- data/app/views/components/combobox/_label.html.erb +1 -1
- data/app/views/components/combobox/_list.html.erb +1 -1
- data/app/views/components/combobox/_option.html.erb +1 -1
- data/app/views/components/combobox/_separator.html.erb +1 -1
- data/app/views/components/combobox/_trigger.html.erb +1 -1
- data/app/views/components/drawer/_close.html.erb +14 -0
- data/app/views/components/drawer/_content.html.erb +7 -0
- data/app/views/components/drawer/_footer.html.erb +7 -0
- data/app/views/components/drawer/_header.html.erb +12 -0
- data/app/views/components/drawer/_provider.html.erb +14 -0
- data/app/views/components/drawer/_trigger.html.erb +14 -0
- data/app/views/components/dropdown_menu/_content.html.erb +1 -1
- data/app/views/components/dropdown_menu/_group.html.erb +1 -1
- data/app/views/components/dropdown_menu/_item.html.erb +1 -1
- data/app/views/components/dropdown_menu/_label.html.erb +3 -7
- data/app/views/components/dropdown_menu/_separator.html.erb +1 -1
- data/app/views/components/dropdown_menu/_shortcut.html.erb +3 -7
- data/app/views/components/dropdown_menu/_trigger.html.erb +1 -1
- data/app/views/components/empty/_content.html.erb +1 -1
- data/app/views/components/empty/_description.html.erb +3 -7
- data/app/views/components/empty/_header.html.erb +1 -1
- data/app/views/components/empty/_media.html.erb +5 -3
- data/app/views/components/empty/_title.html.erb +3 -7
- data/app/views/components/pagination/_content.html.erb +1 -1
- data/app/views/components/pagination/_item.html.erb +1 -1
- data/app/views/components/pagination/_link.html.erb +1 -1
- data/app/views/components/pagination/_next.html.erb +1 -1
- data/app/views/components/pagination/_previous.html.erb +1 -1
- data/app/views/components/sidebar/_content.html.erb +1 -1
- data/app/views/components/sidebar/_footer.html.erb +1 -1
- data/app/views/components/sidebar/_group.html.erb +1 -1
- data/app/views/components/sidebar/_header.html.erb +1 -1
- data/app/views/components/sidebar/_inset.html.erb +1 -1
- data/app/views/components/sidebar/_menu.html.erb +1 -1
- data/app/views/components/sidebar/_menu_button.html.erb +1 -1
- data/app/views/components/sidebar/_menu_item.html.erb +1 -1
- data/app/views/components/sidebar/_menu_link.html.erb +4 -7
- data/app/views/components/sidebar/_provider.html.erb +1 -1
- data/app/views/components/sidebar/_trigger.html.erb +1 -1
- data/app/views/components/stats/_stats_card.html.erb +32 -92
- data/app/views/components/stats/_stats_grid.html.erb +18 -24
- data/app/views/components/table/_body.html.erb +1 -1
- data/app/views/components/table/_caption.html.erb +3 -3
- data/app/views/components/table/_cell.html.erb +3 -3
- data/app/views/components/table/_footer.html.erb +1 -1
- data/app/views/components/table/_head.html.erb +3 -3
- data/app/views/components/table/_header.html.erb +1 -1
- data/app/views/components/table/_row.html.erb +1 -1
- data/app/views/components/toast/_action.html.erb +1 -1
- data/app/views/components/toast/_description.html.erb +1 -1
- data/app/views/components/toast/_title.html.erb +1 -1
- data/app/views/components/toggle_group/_item.html.erb +1 -1
- data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +8 -0
- data/lib/generators/maquina_components/scaffold_templates/scaffold_templates_generator.rb +40 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/_form.html.erb.tt +49 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/edit.html.erb.tt +29 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/index.html.erb.tt +58 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/new.html.erb.tt +26 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/partial.html.erb.tt +17 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/show.html.erb.tt +53 -0
- data/lib/maquina-components.rb +5 -0
- data/lib/maquina_components/engine.rb +7 -3
- data/lib/maquina_components/version.rb +1 -1
- metadata +52 -3
- data/app/assets/stylesheets/maquina_components.css +0 -178
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9159362a034d89b9abf1b6a5a636851942f69c32491eea74c438c9d8dc678b57
|
|
4
|
+
data.tar.gz: 9ab23823a833fdfb8d02727fea268539ea0dd26d974b3212aedf90c87d2c5963
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b7d4230c490e4a98db049a2a527300b060873b6befb6925c2825455923e2179f353ffadbca0f90ebda98779ef711083b819c60a13763a5d982be3d22b73ab4e
|
|
7
|
+
data.tar.gz: c0671e469d43f1fbc265638755f453365748f2cf3d39e435ffa5218d81f9453a55f87cd1babe8acdfb2ccfdfb21a4e2415366a82388ddabb74f9a127a3bdc821
|
data/README.md
CHANGED
|
@@ -76,6 +76,8 @@ This will:
|
|
|
76
76
|
- Add theme variables (light + dark mode)
|
|
77
77
|
- Create a helper file for icon customization
|
|
78
78
|
|
|
79
|
+
All engine helpers (`icon_for`, `sidebar_open?`, `toast_flash_messages`, etc.) are automatically available in your views without any additional configuration.
|
|
80
|
+
|
|
79
81
|
### 3. Use Components
|
|
80
82
|
|
|
81
83
|
```erb
|
|
@@ -117,6 +119,19 @@ bin/rails generate maquina_components:install --skip-theme
|
|
|
117
119
|
bin/rails generate maquina_components:install --skip-helper
|
|
118
120
|
```
|
|
119
121
|
|
|
122
|
+
### Scaffold Templates
|
|
123
|
+
|
|
124
|
+
Copy ERB scaffold templates into your application so Rails' built-in `scaffold`
|
|
125
|
+
generator produces maquina_components-styled views:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
bin/rails generate maquina_components:scaffold_templates
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This installs `index`, `show`, `new`, `edit`, `_form`, and partial templates to
|
|
132
|
+
`lib/templates/erb/scaffold/`. After running it, `rails generate scaffold ModelName field:type`
|
|
133
|
+
generates views built with maquina_components. Customize the templates as needed.
|
|
134
|
+
|
|
120
135
|
**Prerequisite:** [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) must be installed first.
|
|
121
136
|
|
|
122
137
|
---
|
|
@@ -137,8 +152,10 @@ bin/rails generate maquina_components:install --skip-helper
|
|
|
137
152
|
| **Card** | Content container with header, content, footer | [Card](https://maquina.app/documentation/components/card/) |
|
|
138
153
|
| **Alert** | Callout messages (info, warning, error) | [Alert](https://maquina.app/documentation/components/alert/) |
|
|
139
154
|
| **Badge** | Status indicators and labels | [Badge](https://maquina.app/documentation/components/badge/) |
|
|
140
|
-
| **Table** | Data tables with
|
|
155
|
+
| **Table** | Data tables with striped and bordered variants | [Table](https://maquina.app/documentation/components/table/) |
|
|
156
|
+
| **Separator** | Horizontal or vertical divider | [Separator](https://maquina.app/documentation/components/separator/) |
|
|
141
157
|
| **Empty State** | Placeholder for empty lists | [Empty State](https://maquina.app/documentation/components/empty/) |
|
|
158
|
+
| **Stats** | Metric cards in a responsive grid | [Stats](https://maquina.app/documentation/components/stats/) |
|
|
142
159
|
|
|
143
160
|
### Navigation Components
|
|
144
161
|
|
|
@@ -155,6 +172,7 @@ bin/rails generate maquina_components:install --skip-helper
|
|
|
155
172
|
| **Calendar** | Inline date picker with single/range selection | [Calendar](https://maquina.app/documentation/components/calendar/) |
|
|
156
173
|
| **Combobox** | Searchable dropdown with keyboard navigation | [Combobox](https://maquina.app/documentation/components/combobox/) |
|
|
157
174
|
| **Date Picker** | Popover-based date selection | [Date Picker](https://maquina.app/documentation/components/date-picker/) |
|
|
175
|
+
| **Drawer** | Slide-out panel with overlay and keyboard shortcut | [Drawer](https://maquina.app/documentation/components/drawer/) |
|
|
158
176
|
| **Toggle Group** | Single/multiple selection button group | [Toggle Group](https://maquina.app/documentation/components/toggle-group/) |
|
|
159
177
|
|
|
160
178
|
### Feedback Components
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
[data-breadcrumb-part="ellipsis"] {
|
|
95
95
|
@apply inline-flex size-9 items-center justify-center;
|
|
96
96
|
color: var(--muted-foreground);
|
|
97
|
+
cursor: pointer;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
[data-breadcrumb-part="ellipsis"] svg {
|
|
@@ -106,6 +107,26 @@
|
|
|
106
107
|
color: var(--foreground);
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
/* ===== Overflow Dropdown (top-layer popover) ===== */
|
|
111
|
+
/* Visual styling comes from dropdown_menu.css via data-dropdown-menu-part.
|
|
112
|
+
Anchor positioning places the popover in modern browsers;
|
|
113
|
+
breadcrumb_controller falls back to measured coordinates elsewhere.
|
|
114
|
+
The doubled attribute selector outranks dropdown_menu.css's
|
|
115
|
+
position: absolute regardless of import order. */
|
|
116
|
+
@supports (anchor-name: --a) and (position-area: block-end) {
|
|
117
|
+
[data-breadcrumb-part="ellipsis"] {
|
|
118
|
+
anchor-name: --maquina-breadcrumbs;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
[data-breadcrumb-part="dropdown"][data-dropdown-menu-part="content"] {
|
|
122
|
+
position: fixed;
|
|
123
|
+
position-anchor: --maquina-breadcrumbs;
|
|
124
|
+
position-area: block-end span-inline-end;
|
|
125
|
+
margin-block-start: 4px;
|
|
126
|
+
position-try-fallbacks: flip-block;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
/* ===== Responsive Behavior ===== */
|
|
110
131
|
/* Force nowrap when responsive controller is active so scrollWidth detects overflow */
|
|
111
132
|
[data-controller="breadcrumb"] [data-breadcrumb-part="list"] {
|
|
@@ -53,12 +53,47 @@
|
|
|
53
53
|
@apply rounded-md border p-0 shadow-md outline-none;
|
|
54
54
|
@apply min-w-[200px];
|
|
55
55
|
|
|
56
|
-
/* Reset popover defaults - positioning
|
|
56
|
+
/* Reset popover defaults - positioning below (anchor CSS) or in JS fallback */
|
|
57
57
|
margin: 0;
|
|
58
58
|
padding: 0;
|
|
59
59
|
inset: unset;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/* ===== Anchor Positioning (modern browsers) ===== */
|
|
63
|
+
/* Places the top-layer popover declaratively; combobox_controller skips its
|
|
64
|
+
measured fallback when these features are supported. flip-block flips the
|
|
65
|
+
popover above the trigger when there is no room below — something the JS
|
|
66
|
+
fallback never did. */
|
|
67
|
+
@supports (anchor-name: --a) and (position-area: block-end) {
|
|
68
|
+
[data-combobox-part="trigger"] {
|
|
69
|
+
anchor-name: --maquina-combobox;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
[data-combobox-part="content"] {
|
|
73
|
+
position: fixed;
|
|
74
|
+
position-anchor: --maquina-combobox;
|
|
75
|
+
margin-block-start: 4px;
|
|
76
|
+
position-try-fallbacks: flip-block;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[data-combobox-part="content"][data-align="start"] {
|
|
80
|
+
position-area: block-end span-inline-end;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
[data-combobox-part="content"][data-align="center"] {
|
|
84
|
+
position-area: block-end;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[data-combobox-part="content"][data-align="end"] {
|
|
88
|
+
position-area: block-end span-inline-start;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[data-combobox-part="content"][data-width="default"] {
|
|
92
|
+
width: auto;
|
|
93
|
+
min-width: max(200px, anchor-size(inline));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
/* Width variants */
|
|
63
98
|
[data-combobox-part="content"][data-width="sm"] {
|
|
64
99
|
@apply w-40;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/* ===== Drawer Component Styles ===== */
|
|
2
|
+
|
|
3
|
+
/* ===== Provider (Wrapper) ===== */
|
|
4
|
+
[data-component="drawer"] {
|
|
5
|
+
@apply flex w-full;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* ===== Drawer Root ===== */
|
|
9
|
+
[data-drawer-part="root"] {
|
|
10
|
+
@apply block text-foreground;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Prevent transitions on initial load */
|
|
14
|
+
[data-drawer-part="root"].drawer-loading,
|
|
15
|
+
[data-drawer-part="root"].drawer-loading * {
|
|
16
|
+
transition: none !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* ===== Drawer Backdrop (overlay) ===== */
|
|
20
|
+
[data-drawer-part="backdrop"] {
|
|
21
|
+
@apply fixed inset-0 z-40 hidden bg-black opacity-0;
|
|
22
|
+
transition: opacity 300ms;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-drawer-part="backdrop"][data-state="visible"] {
|
|
26
|
+
@apply block opacity-60;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ===== Drawer Panel (fixed positioned) ===== */
|
|
30
|
+
[data-drawer-part="panel"] {
|
|
31
|
+
@apply fixed inset-y-0 z-50 flex h-full w-80 flex-col;
|
|
32
|
+
@apply bg-background shadow-lg;
|
|
33
|
+
transition: right 300ms ease-in-out, left 300ms ease-in-out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[data-drawer-part="root"][data-side="left"] [data-drawer-part="panel"] {
|
|
37
|
+
@apply left-0;
|
|
38
|
+
right: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[data-drawer-part="root"][data-side="right"] [data-drawer-part="panel"] {
|
|
42
|
+
@apply right-0;
|
|
43
|
+
left: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Closed state - slide out */
|
|
47
|
+
[data-drawer-part="root"][data-side="left"][data-state="closed"] [data-drawer-part="panel"] {
|
|
48
|
+
@apply -translate-x-full;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[data-drawer-part="root"][data-side="right"][data-state="closed"] [data-drawer-part="panel"] {
|
|
52
|
+
@apply translate-x-full;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ===== Drawer Inner Content Wrapper ===== */
|
|
56
|
+
[data-drawer-part="inner"] {
|
|
57
|
+
@apply flex h-full w-full flex-col;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ===== Body Scroll Lock ===== */
|
|
61
|
+
/* Shared by drawer_controller and sidebar_controller: the controllers set
|
|
62
|
+
the data attribute and the --maquina-scroll-lock-top offset on <body>;
|
|
63
|
+
all styling lives here. */
|
|
64
|
+
body[data-maquina-scroll-locked] {
|
|
65
|
+
position: fixed;
|
|
66
|
+
top: var(--maquina-scroll-lock-top, 0px);
|
|
67
|
+
width: 100%;
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ===== Drawer Header ===== */
|
|
72
|
+
[data-drawer-part="header"] {
|
|
73
|
+
@apply flex shrink-0 items-center justify-between gap-2 border-b px-4 py-3;
|
|
74
|
+
border-color: var(--border);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[data-drawer-part="header-content"] {
|
|
78
|
+
@apply flex-1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ===== Drawer Content (scrollable area) ===== */
|
|
82
|
+
[data-drawer-part="content"] {
|
|
83
|
+
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-auto p-4;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ===== Drawer Footer ===== */
|
|
87
|
+
[data-drawer-part="footer"] {
|
|
88
|
+
@apply flex shrink-0 flex-col gap-2 border-t p-4;
|
|
89
|
+
border-color: var(--border);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ===== Drawer Trigger ===== */
|
|
93
|
+
|
|
94
|
+
[data-drawer-part="trigger"]:hover {
|
|
95
|
+
background-color: var(--accent);
|
|
96
|
+
color: var(--accent-foreground);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ===== Drawer Title ===== */
|
|
100
|
+
[data-drawer-part="title"] {
|
|
101
|
+
@apply text-lg font-semibold;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ===== Drawer Description ===== */
|
|
105
|
+
[data-drawer-part="description"] {
|
|
106
|
+
@apply text-sm text-muted-foreground;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ===== Drawer Close Button ===== */
|
|
110
|
+
[data-drawer-part="close"] {
|
|
111
|
+
@apply inline-flex items-center justify-center rounded-md p-1;
|
|
112
|
+
@apply outline-hidden ring-ring focus-visible:ring-2;
|
|
113
|
+
@apply transition-colors;
|
|
114
|
+
color: var(--foreground);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
[data-drawer-part="close"]:hover {
|
|
118
|
+
background-color: var(--accent);
|
|
119
|
+
color: var(--accent-foreground);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
[data-drawer-part="close"] svg {
|
|
123
|
+
@apply size-4;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ===== Drawer Section ===== */
|
|
127
|
+
[data-drawer-part="section"] {
|
|
128
|
+
@apply flex flex-col gap-2;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ===== Drawer Separator ===== */
|
|
132
|
+
[data-drawer-part="separator"] {
|
|
133
|
+
@apply mx-4 my-2 w-auto;
|
|
134
|
+
background-color: var(--border);
|
|
135
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* ===== Menu Button Component Styles ===== */
|
|
2
|
+
/*
|
|
3
|
+
* Sidebar-flavored button that opens a dropdown panel.
|
|
4
|
+
* Pairs _menu_button.html.erb (trigger) with _dropdown.html.erb (content)
|
|
5
|
+
* and menu_button_controller.js.
|
|
6
|
+
*
|
|
7
|
+
* Structure:
|
|
8
|
+
* - menu-button (root ul)
|
|
9
|
+
* - item (li with Stimulus controller)
|
|
10
|
+
* - trigger (button)
|
|
11
|
+
* - avatar (text icon square)
|
|
12
|
+
* - text (title + subtitle)
|
|
13
|
+
* - content (positioned dropdown panel)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* ===== Root Container ===== */
|
|
17
|
+
[data-component="menu-button"] {
|
|
18
|
+
@apply flex w-full min-w-0 flex-col gap-1;
|
|
19
|
+
margin: 0;
|
|
20
|
+
padding: 0;
|
|
21
|
+
list-style: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[data-menu-button-part="item"] {
|
|
25
|
+
position: relative;
|
|
26
|
+
list-style: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ===== Trigger Button ===== */
|
|
30
|
+
[data-menu-button-part="trigger"] {
|
|
31
|
+
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm;
|
|
32
|
+
@apply transition-[width,height,padding] duration-150;
|
|
33
|
+
height: 3rem;
|
|
34
|
+
background-color: transparent;
|
|
35
|
+
color: inherit;
|
|
36
|
+
border: none;
|
|
37
|
+
outline: none;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[data-menu-button-part="trigger"]:hover,
|
|
42
|
+
[data-menu-button-part="trigger"]:active,
|
|
43
|
+
[data-menu-button-part="trigger"][data-state="open"] {
|
|
44
|
+
background-color: var(--sidebar-accent);
|
|
45
|
+
color: var(--sidebar-accent-foreground);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[data-menu-button-part="trigger"]:focus-visible {
|
|
49
|
+
box-shadow: 0 0 0 2px var(--sidebar-ring);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[data-menu-button-part="trigger"]:disabled,
|
|
53
|
+
[data-menu-button-part="trigger"][aria-disabled="true"] {
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
opacity: 0.5;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[data-menu-button-part="trigger"] svg {
|
|
59
|
+
@apply size-4 shrink-0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-menu-button-part="trigger"] img {
|
|
63
|
+
@apply shrink-0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Icon-collapsed sidebar squeezes the trigger to an icon square */
|
|
67
|
+
[data-sidebar-part="root"][data-collapsible="icon"] [data-menu-button-part="trigger"] {
|
|
68
|
+
width: 2rem;
|
|
69
|
+
height: 2rem;
|
|
70
|
+
padding: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ===== Avatar (text icon) ===== */
|
|
74
|
+
[data-menu-button-part="avatar"] {
|
|
75
|
+
@apply flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg;
|
|
76
|
+
background-color: var(--sidebar-primary);
|
|
77
|
+
color: var(--sidebar-primary-foreground);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ===== Text (title + subtitle) ===== */
|
|
81
|
+
[data-menu-button-part="text"] {
|
|
82
|
+
@apply grid flex-1 text-left text-sm leading-tight;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
[data-menu-button-part="title"] {
|
|
86
|
+
@apply truncate font-semibold;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[data-menu-button-part="subtitle"] {
|
|
90
|
+
@apply truncate text-xs;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ===== Content Panel ===== */
|
|
94
|
+
[data-menu-button-part="content"] {
|
|
95
|
+
position: absolute;
|
|
96
|
+
z-index: 50;
|
|
97
|
+
display: none;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
min-width: 14rem;
|
|
101
|
+
max-width: 14rem;
|
|
102
|
+
background-color: var(--popover, var(--background));
|
|
103
|
+
color: var(--popover-foreground, var(--foreground));
|
|
104
|
+
@apply rounded-lg border p-1 shadow-md;
|
|
105
|
+
border-color: var(--border);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
[data-menu-button-part="content"][data-side="top"] {
|
|
109
|
+
bottom: calc(100% + 4px);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
[data-menu-button-part="content"][data-side="bottom"] {
|
|
113
|
+
top: calc(100% + 4px);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[data-menu-button-part="content"][data-align="start"] {
|
|
117
|
+
left: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
[data-menu-button-part="content"][data-align="end"] {
|
|
121
|
+
right: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Animation states */
|
|
125
|
+
[data-menu-button-part="content"][data-state="open"] {
|
|
126
|
+
display: flex;
|
|
127
|
+
animation: menu-button-in 150ms ease-out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[data-menu-button-part="content"][data-state="closing"] {
|
|
131
|
+
display: flex;
|
|
132
|
+
animation: menu-button-out 100ms ease-in forwards;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@keyframes menu-button-in {
|
|
136
|
+
from {
|
|
137
|
+
opacity: 0;
|
|
138
|
+
transform: scale(0.95) translateY(4px);
|
|
139
|
+
}
|
|
140
|
+
to {
|
|
141
|
+
opacity: 1;
|
|
142
|
+
transform: scale(1) translateY(0);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@keyframes menu-button-out {
|
|
147
|
+
from {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
transform: scale(1) translateY(0);
|
|
150
|
+
}
|
|
151
|
+
to {
|
|
152
|
+
opacity: 0;
|
|
153
|
+
transform: scale(0.95) translateY(4px);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* ===== Separator Component Styles ===== */
|
|
2
|
+
/*
|
|
3
|
+
* Decorative divider, horizontal or vertical.
|
|
4
|
+
* Styled via data attributes; colors come from theme variables.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
[data-component="separator"] {
|
|
8
|
+
@apply mx-2 shrink-0;
|
|
9
|
+
background-color: var(--border);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
[data-component="separator"][data-orientation="horizontal"] {
|
|
13
|
+
height: 1px;
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[data-component="separator"][data-orientation="vertical"] {
|
|
18
|
+
width: 1px;
|
|
19
|
+
height: 1rem;
|
|
20
|
+
}
|
|
@@ -290,6 +290,18 @@
|
|
|
290
290
|
@apply truncate;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
[data-sidebar-part="menu-link-text"] {
|
|
294
|
+
@apply flex flex-col gap-0.5 leading-none;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
[data-sidebar-part="menu-link-title"] {
|
|
298
|
+
@apply font-medium;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
[data-sidebar-part="menu-link-subtitle"] {
|
|
302
|
+
@apply text-xs text-sidebar-foreground/70;
|
|
303
|
+
}
|
|
304
|
+
|
|
293
305
|
/* Has menu action - add padding */
|
|
294
306
|
.group-has-data-\[sidebar-part\=menu-action\]\/menu-item [data-sidebar-part="menu-link"],
|
|
295
307
|
[data-sidebar-part="menu-item"]:has([data-sidebar-part="menu-action"]) [data-sidebar-part="menu-link"] {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* ===== Stats Component Styles ===== */
|
|
2
|
+
/*
|
|
3
|
+
* Dashboard stat cards and their responsive grid.
|
|
4
|
+
*
|
|
5
|
+
* Structure:
|
|
6
|
+
* - stats-grid (root, optional action slot)
|
|
7
|
+
* - action (leading or trailing action area)
|
|
8
|
+
* - grid (responsive card grid, columns via data-columns)
|
|
9
|
+
* - stats-card
|
|
10
|
+
* - body
|
|
11
|
+
* - text (title, value, subtitle)
|
|
12
|
+
* - icon
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/* ===== Card ===== */
|
|
16
|
+
[data-component="stats-card"] {
|
|
17
|
+
@apply rounded-lg border p-4 shadow-sm;
|
|
18
|
+
background-color: var(--card);
|
|
19
|
+
border-color: var(--border);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[data-component="stats-card"] [data-stats-part="body"] {
|
|
23
|
+
@apply flex items-center justify-between;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[data-component="stats-card"] [data-stats-part="title"] {
|
|
27
|
+
@apply text-sm;
|
|
28
|
+
color: var(--muted-foreground);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[data-component="stats-card"] [data-stats-part="value"] {
|
|
32
|
+
@apply text-2xl font-bold;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[data-component="stats-card"] [data-stats-part="subtitle"] {
|
|
36
|
+
@apply mt-1 text-xs;
|
|
37
|
+
color: var(--muted-foreground);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[data-component="stats-card"] [data-stats-part="icon"] svg {
|
|
41
|
+
@apply size-8 shrink-0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Color defaults live in the components layer so caller utility classes
|
|
45
|
+
(value_classes / icon_classes, e.g. text-green-600) always win — the
|
|
46
|
+
utilities layer is defined to beat the components layer, and unlayered
|
|
47
|
+
rules would beat utilities regardless of specificity */
|
|
48
|
+
@layer components {
|
|
49
|
+
[data-component="stats-card"] [data-stats-part="value"] {
|
|
50
|
+
color: var(--foreground);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-component="stats-card"] [data-stats-part="icon"] {
|
|
54
|
+
color: var(--muted-foreground);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ===== Grid ===== */
|
|
59
|
+
[data-component="stats-grid"][data-with-action="true"] {
|
|
60
|
+
@apply flex flex-col gap-4 lg:flex-row lg:items-center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
[data-component="stats-grid"] [data-stats-part="grid"] {
|
|
64
|
+
@apply grid grid-cols-1 gap-4;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
[data-component="stats-grid"][data-with-action="true"] [data-stats-part="grid"] {
|
|
68
|
+
@apply flex-1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
[data-component="stats-grid"][data-columns="2"] [data-stats-part="grid"] {
|
|
72
|
+
@apply sm:grid-cols-2;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[data-component="stats-grid"][data-columns="3"] [data-stats-part="grid"] {
|
|
76
|
+
@apply sm:grid-cols-3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[data-component="stats-grid"][data-columns="4"] [data-stats-part="grid"] {
|
|
80
|
+
@apply sm:grid-cols-4;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
[data-component="stats-grid"][data-columns="5"] [data-stats-part="grid"] {
|
|
84
|
+
@apply sm:grid-cols-5;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[data-component="stats-grid"][data-columns="6"] [data-stats-part="grid"] {
|
|
88
|
+
@apply sm:grid-cols-6;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[data-component="stats-grid"] [data-stats-part="action"] {
|
|
92
|
+
@apply flex justify-center;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
[data-component="stats-grid"] [data-stats-part="action"][data-position="start"] {
|
|
96
|
+
@apply lg:justify-start;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
[data-component="stats-grid"] [data-stats-part="action"][data-position="end"] {
|
|
100
|
+
@apply lg:justify-end;
|
|
101
|
+
}
|
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
@import "../../stylesheets/card.css";
|
|
8
8
|
@import "../../stylesheets/combobox.css";
|
|
9
9
|
@import "../../stylesheets/date_picker.css";
|
|
10
|
+
@import "../../stylesheets/drawer.css";
|
|
10
11
|
@import "../../stylesheets/dropdown_menu.css";
|
|
11
12
|
@import "../../stylesheets/empty.css";
|
|
12
13
|
@import "../../stylesheets/form.css";
|
|
13
14
|
@import "../../stylesheets/header.css";
|
|
15
|
+
@import "../../stylesheets/menu_button.css";
|
|
14
16
|
@import "../../stylesheets/pagination.css";
|
|
17
|
+
@import "../../stylesheets/separator.css";
|
|
15
18
|
@import "../../stylesheets/sidebar.css";
|
|
19
|
+
@import "../../stylesheets/stats.css";
|
|
16
20
|
@import "../../stylesheets/table.css";
|
|
17
21
|
@import "../../stylesheets/toast.css";
|
|
18
22
|
@import "../../stylesheets/toggle_group.css";
|
|
@@ -59,7 +59,8 @@ module MaquinaComponents
|
|
|
59
59
|
# @return [String] Rendered HTML
|
|
60
60
|
def combobox(id: nil, name: nil, value: nil, placeholder: "Select...", css_classes: "", **html_options, &block)
|
|
61
61
|
builder = ComboboxBuilder.new(self, placeholder: placeholder)
|
|
62
|
-
|
|
62
|
+
# Deterministic fallback id: random ids break idiomorph matching across Turbo morphs
|
|
63
|
+
combobox_id = id || "combobox-#{name.to_s.parameterize.presence || "default"}"
|
|
63
64
|
builder.combobox_id = combobox_id
|
|
64
65
|
|
|
65
66
|
capture(builder, &block)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module MaquinaComponents
|
|
2
|
+
# Shared plumbing for component partials.
|
|
3
|
+
module ComponentsHelper
|
|
4
|
+
# Merges a caller's data: hash with a component's own data attributes.
|
|
5
|
+
#
|
|
6
|
+
# Component values win for identity keys (component, variant, size, ...)
|
|
7
|
+
# so callers can't accidentally break a component's styling hooks. The
|
|
8
|
+
# Stimulus token keys :controller and :action concatenate instead, so a
|
|
9
|
+
# caller can attach extra behavior without losing the component's:
|
|
10
|
+
#
|
|
11
|
+
# <%= render "components/combobox", name: "country",
|
|
12
|
+
# data: { controller: "analytics", action: "change->analytics#track" } %>
|
|
13
|
+
# # => data-controller="combobox analytics"
|
|
14
|
+
#
|
|
15
|
+
# @param html_options [Hash] the partial's **html_options (data: is consumed)
|
|
16
|
+
# @param component_data [Hash] the component's own data attributes
|
|
17
|
+
# @return [Hash] the merged data hash
|
|
18
|
+
def merge_component_data(html_options, **component_data)
|
|
19
|
+
user_data = html_options.delete(:data) || {}
|
|
20
|
+
merged = user_data.merge(component_data)
|
|
21
|
+
|
|
22
|
+
[:controller, :action].each do |key|
|
|
23
|
+
user_value = user_data[key] || user_data[key.to_s]
|
|
24
|
+
next if user_value.blank? || component_data[key].blank?
|
|
25
|
+
|
|
26
|
+
merged.delete(key.to_s)
|
|
27
|
+
merged[key] = "#{component_data[key]} #{user_value}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
merged
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module MaquinaComponents
|
|
2
|
+
module DrawerHelper
|
|
3
|
+
def drawer_state(cookie_name = "drawer_state")
|
|
4
|
+
cookie_value = cookies[cookie_name]
|
|
5
|
+
|
|
6
|
+
return :open if cookie_value.nil?
|
|
7
|
+
|
|
8
|
+
(cookie_value == "true") ? :open : :closed
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def drawer_open?(cookie_name = "drawer_state")
|
|
12
|
+
drawer_state(cookie_name) == :open
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def drawer_closed?(cookie_name = "drawer_state")
|
|
16
|
+
drawer_state(cookie_name) == :closed
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|