maquina-components 0.1.2 → 0.2.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +349 -138
  3. data/app/assets/images/maquina.svg +1 -0
  4. data/app/assets/stylesheets/alert.css +143 -0
  5. data/app/assets/stylesheets/badge.css +145 -0
  6. data/app/assets/stylesheets/breadcrumbs.css +163 -0
  7. data/app/assets/stylesheets/card.css +128 -0
  8. data/app/assets/stylesheets/dropdown_menu.css +248 -0
  9. data/app/assets/stylesheets/empty.css +133 -0
  10. data/app/assets/stylesheets/form.css +617 -0
  11. data/app/assets/stylesheets/header.css +61 -0
  12. data/app/assets/stylesheets/maquina_components.css +143 -64
  13. data/app/assets/stylesheets/pagination.css +154 -0
  14. data/app/assets/stylesheets/sidebar.css +477 -0
  15. data/app/assets/stylesheets/table.css +205 -0
  16. data/app/assets/stylesheets/toggle_group.css +151 -0
  17. data/app/assets/tailwind/maquina_components_engine/engine.css +16 -0
  18. data/app/helpers/maquina_components/breadcrumbs_helper.rb +118 -0
  19. data/app/helpers/maquina_components/dropdown_menu_helper.rb +249 -0
  20. data/app/helpers/maquina_components/empty_helper.rb +102 -0
  21. data/app/helpers/{components → maquina_components}/icons_helper.rb +40 -3
  22. data/app/helpers/maquina_components/pagination_helper.rb +153 -0
  23. data/app/helpers/maquina_components/sidebar_helper.rb +63 -0
  24. data/app/helpers/maquina_components/table_helper.rb +144 -0
  25. data/app/helpers/maquina_components/toggle_group_helper.rb +172 -0
  26. data/app/javascript/controllers/breadcrumb_controller.js +71 -0
  27. data/app/javascript/controllers/dropdown_menu_controller.js +203 -0
  28. data/app/javascript/controllers/menu_button_controller.js +59 -0
  29. data/app/javascript/controllers/sidebar_controller.js +316 -0
  30. data/app/javascript/controllers/sidebar_trigger_controller.js +32 -0
  31. data/app/javascript/controllers/toggle_group_controller.js +178 -0
  32. data/app/views/components/_alert.html.erb +11 -10
  33. data/app/views/components/_badge.html.erb +10 -0
  34. data/app/views/components/_breadcrumbs.html.erb +16 -0
  35. data/app/views/components/_card.html.erb +4 -8
  36. data/app/views/components/_dropdown.html.erb +25 -0
  37. data/app/views/components/_dropdown_menu.html.erb +9 -0
  38. data/app/views/components/_empty.html.erb +10 -0
  39. data/app/views/components/_header.html.erb +8 -0
  40. data/app/views/components/_menu_button.html.erb +44 -0
  41. data/app/views/components/_pagination.html.erb +12 -33
  42. data/app/views/components/_separator.html.erb +11 -0
  43. data/app/views/components/_sidebar.html.erb +30 -20
  44. data/app/views/components/_simple_table.html.erb +49 -0
  45. data/app/views/components/_table.html.erb +21 -0
  46. data/app/views/components/_toggle_group.html.erb +24 -0
  47. data/app/views/components/alert/_description.html.erb +6 -0
  48. data/app/views/components/alert/_title.html.erb +6 -0
  49. data/app/views/components/breadcrumbs/_ellipsis.html.erb +9 -0
  50. data/app/views/components/breadcrumbs/_item.html.erb +8 -0
  51. data/app/views/components/breadcrumbs/_link.html.erb +8 -0
  52. data/app/views/components/breadcrumbs/_list.html.erb +8 -0
  53. data/app/views/components/breadcrumbs/_page.html.erb +8 -0
  54. data/app/views/components/breadcrumbs/_separator.html.erb +17 -0
  55. data/app/views/components/card/_action.html.erb +6 -0
  56. data/app/views/components/card/_content.html.erb +9 -0
  57. data/app/views/components/card/_description.html.erb +6 -0
  58. data/app/views/components/card/_footer.html.erb +17 -0
  59. data/app/views/components/card/_header.html.erb +9 -0
  60. data/app/views/components/card/_title.html.erb +9 -0
  61. data/app/views/components/dropdown_menu/_content.html.erb +20 -0
  62. data/app/views/components/dropdown_menu/_group.html.erb +12 -0
  63. data/app/views/components/dropdown_menu/_item.html.erb +29 -0
  64. data/app/views/components/dropdown_menu/_label.html.erb +13 -0
  65. data/app/views/components/dropdown_menu/_separator.html.erb +11 -0
  66. data/app/views/components/dropdown_menu/_shortcut.html.erb +12 -0
  67. data/app/views/components/dropdown_menu/_trigger.html.erb +24 -0
  68. data/app/views/components/empty/_content.html.erb +8 -0
  69. data/app/views/components/empty/_description.html.erb +12 -0
  70. data/app/views/components/empty/_header.html.erb +8 -0
  71. data/app/views/components/empty/_media.html.erb +13 -0
  72. data/app/views/components/empty/_title.html.erb +12 -0
  73. data/app/views/components/pagination/_content.html.erb +8 -0
  74. data/app/views/components/pagination/_ellipsis.html.erb +28 -0
  75. data/app/views/components/pagination/_item.html.erb +8 -0
  76. data/app/views/components/pagination/_link.html.erb +23 -0
  77. data/app/views/components/pagination/_next.html.erb +57 -0
  78. data/app/views/components/pagination/_previous.html.erb +57 -0
  79. data/app/views/components/sidebar/_content.html.erb +8 -0
  80. data/app/views/components/sidebar/_footer.html.erb +8 -0
  81. data/app/views/components/sidebar/_group.html.erb +12 -0
  82. data/app/views/components/sidebar/_header.html.erb +8 -0
  83. data/app/views/components/sidebar/_inset.html.erb +8 -0
  84. data/app/views/components/sidebar/_menu.html.erb +8 -0
  85. data/app/views/components/sidebar/_menu_button.html.erb +14 -0
  86. data/app/views/components/sidebar/_menu_item.html.erb +7 -0
  87. data/app/views/components/sidebar/_menu_link.html.erb +32 -0
  88. data/app/views/components/sidebar/_provider.html.erb +16 -0
  89. data/app/views/components/sidebar/_trigger.html.erb +12 -0
  90. data/app/views/components/stats/_stats_card.html.erb +100 -0
  91. data/app/views/components/stats/_stats_grid.html.erb +38 -0
  92. data/app/views/components/table/_body.html.erb +5 -0
  93. data/app/views/components/table/_caption.html.erb +5 -0
  94. data/app/views/components/table/_cell.html.erb +5 -0
  95. data/app/views/components/table/_footer.html.erb +5 -0
  96. data/app/views/components/table/_head.html.erb +8 -0
  97. data/app/views/components/table/_header.html.erb +8 -0
  98. data/app/views/components/table/_row.html.erb +8 -0
  99. data/app/views/components/toggle_group/_item.html.erb +19 -0
  100. data/config/importmap.rb +1 -0
  101. data/lib/generators/maquina_components/install/USAGE +39 -0
  102. data/lib/generators/maquina_components/install/install_generator.rb +123 -0
  103. data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +68 -0
  104. data/lib/generators/maquina_components/install/templates/theme.css.tt +179 -0
  105. data/lib/maquina_components/engine.rb +10 -0
  106. data/lib/maquina_components/version.rb +1 -1
  107. metadata +116 -12
  108. data/app/helpers/components/pagination_helper.rb +0 -15
  109. data/app/views/components/_card_content.html.erb +0 -5
  110. data/app/views/components/_card_header.html.erb +0 -8
  111. data/app/views/components/_sidebar_content.html.erb +0 -8
  112. data/app/views/components/_sidebar_group.html.erb +0 -42
  113. data/app/views/components/_sidebar_header.html.erb +0 -3
@@ -0,0 +1,143 @@
1
+ /* ===== Alert Component Styles ===== */
2
+ /*
3
+ * Alert component for displaying callouts, messages, and notifications.
4
+ * Uses data attributes for styling to maintain consistency with other components.
5
+ * Fully compatible with dark mode via CSS variables.
6
+ */
7
+
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
+ }
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
+ */
@@ -0,0 +1,145 @@
1
+ /* ===== Badge Component Styles ===== */
2
+ /*
3
+ * Badge component for status indicators, tags, counts, and labels.
4
+ * Uses data attributes for styling to avoid inline utility classes.
5
+ * Fully compatible with dark mode and supports icons.
6
+ */
7
+
8
+ /* ===== Base Badge Styles ===== */
9
+ [data-component="badge"] {
10
+ /* Layout */
11
+ display: inline-flex;
12
+ align-items: center;
13
+ white-space: nowrap;
14
+ @apply gap-1 rounded-md border border-transparent font-medium leading-none;
15
+
16
+ /* Transitions */
17
+ @apply transition-[background-color,border-color,color,opacity] duration-150;
18
+ }
19
+
20
+ /* ===== Icon Support ===== */
21
+ [data-component="badge"] svg {
22
+ /* Scales with font size using em */
23
+ width: 0.875em;
24
+ height: 0.875em;
25
+ @apply shrink-0 pointer-events-none;
26
+ }
27
+
28
+ /* ===== Size Variants ===== */
29
+
30
+ /* Small size - compact badges for inline use */
31
+ [data-component="badge"][data-size="sm"] {
32
+ @apply text-xs px-2 py-0.5 h-5;
33
+ }
34
+
35
+ /* Medium size - default, balanced size */
36
+ [data-component="badge"][data-size="md"] {
37
+ @apply text-sm px-2.5 py-1 h-6;
38
+ }
39
+
40
+ /* Large size - prominent badges */
41
+ [data-component="badge"][data-size="lg"] {
42
+ @apply text-sm px-3 py-1.5 h-7;
43
+ }
44
+
45
+ /* ===== Visual Variants ===== */
46
+
47
+ /* Default variant - muted appearance */
48
+ [data-component="badge"][data-variant="default"] {
49
+ background-color: var(--muted);
50
+ color: var(--muted-foreground);
51
+ }
52
+
53
+ [data-component="badge"][data-variant="default"]:hover {
54
+ @apply opacity-90;
55
+ }
56
+
57
+ /* Primary variant - brand color */
58
+ [data-component="badge"][data-variant="primary"] {
59
+ background-color: var(--primary-color);
60
+ color: var(--primary-foreground-color);
61
+ }
62
+
63
+ [data-component="badge"][data-variant="primary"]:hover {
64
+ @apply opacity-90;
65
+ }
66
+
67
+ /* Secondary variant - subtle appearance */
68
+ [data-component="badge"][data-variant="secondary"] {
69
+ background-color: var(--secondary);
70
+ color: var(--secondary-foreground);
71
+ }
72
+
73
+ [data-component="badge"][data-variant="secondary"]:hover {
74
+ @apply opacity-90;
75
+ }
76
+
77
+ /* Destructive variant - errors, deletions */
78
+ [data-component="badge"][data-variant="destructive"] {
79
+ background-color: var(--destructive);
80
+ color: var(--destructive-foreground);
81
+ }
82
+
83
+ [data-component="badge"][data-variant="destructive"]:hover {
84
+ @apply opacity-90;
85
+ }
86
+
87
+ /* Success variant - positive status */
88
+ [data-component="badge"][data-variant="success"] {
89
+ background-color: var(--success);
90
+ color: var(--success-foreground);
91
+ }
92
+
93
+ [data-component="badge"][data-variant="success"]:hover {
94
+ @apply opacity-90;
95
+ }
96
+
97
+ /* Warning variant - caution status */
98
+ [data-component="badge"][data-variant="warning"] {
99
+ background-color: var(--warning);
100
+ color: var(--warning-foreground);
101
+ }
102
+
103
+ [data-component="badge"][data-variant="warning"]:hover {
104
+ @apply opacity-90;
105
+ }
106
+
107
+ /* Outline variant - transparent with border */
108
+ [data-component="badge"][data-variant="outline"] {
109
+ @apply bg-transparent;
110
+ color: var(--foreground);
111
+ border-color: var(--border);
112
+ }
113
+
114
+ [data-component="badge"][data-variant="outline"]:hover {
115
+ background-color: var(--accent);
116
+ color: var(--accent-foreground);
117
+ }
118
+
119
+ /* ===== Interactive States (when used as link/button) ===== */
120
+
121
+ /* Focus state for keyboard navigation */
122
+ a[data-component="badge"]:focus-visible,
123
+ button[data-component="badge"]:focus-visible {
124
+ @apply outline-none;
125
+ box-shadow: 0 0 0 2px var(--background),
126
+ 0 0 0 4px var(--border);
127
+ }
128
+
129
+ /* Active/pressed state */
130
+ a[data-component="badge"]:active,
131
+ button[data-component="badge"]:active {
132
+ @apply scale-[0.98];
133
+ }
134
+
135
+ /* Disabled state (if used as button) */
136
+ button[data-component="badge"]:disabled {
137
+ @apply opacity-50 cursor-not-allowed pointer-events-none;
138
+ }
139
+
140
+ /* ===== Dark Mode ===== */
141
+ /*
142
+ * Dark mode is handled automatically through CSS variables.
143
+ * The theme variables change based on the .dark class on html/body.
144
+ * No additional dark mode styles needed here.
145
+ */
@@ -0,0 +1,163 @@
1
+ /* ===== Breadcrumbs Component Styles ===== */
2
+ /* Comprehensive CSS using data-attribute selectors */
3
+ /* Uses @apply for theme-customizable properties */
4
+
5
+ /* ===== Breadcrumbs Container (nav) ===== */
6
+ [data-component="breadcrumbs"] {
7
+ /* Container has no default styles - allows flexible placement */
8
+ }
9
+
10
+ /* ===== Breadcrumbs List ===== */
11
+ [data-breadcrumb-part="list"] {
12
+ @apply flex flex-wrap items-center gap-1.5;
13
+ @apply text-sm break-words;
14
+ list-style: none;
15
+ padding: 0;
16
+ margin: 0;
17
+ }
18
+
19
+ /* Compact variant */
20
+ [data-component="breadcrumbs"][data-size="sm"] [data-breadcrumb-part="list"] {
21
+ @apply gap-1 text-xs;
22
+ }
23
+
24
+ /* ===== Breadcrumb Item ===== */
25
+ [data-breadcrumb-part="item"] {
26
+ @apply inline-flex items-center;
27
+ }
28
+
29
+ /* Hidden state for responsive collapsing */
30
+ [data-breadcrumb-part="item"].hidden {
31
+ @apply hidden;
32
+ }
33
+
34
+ /* ===== Breadcrumb Link ===== */
35
+ [data-breadcrumb-part="link"] {
36
+ @apply inline-flex items-center;
37
+ @apply text-sm font-medium;
38
+ @apply underline-offset-4;
39
+ @apply transition-colors;
40
+ color: var(--muted-foreground);
41
+ }
42
+
43
+ [data-breadcrumb-part="link"]:hover {
44
+ color: var(--foreground);
45
+ @apply underline;
46
+ }
47
+
48
+ [data-breadcrumb-part="link"]:focus-visible {
49
+ @apply outline-none underline;
50
+ color: var(--foreground);
51
+ }
52
+
53
+ /* Link with icon */
54
+ [data-breadcrumb-part="link"] svg {
55
+ @apply size-3.5 shrink-0;
56
+ }
57
+
58
+ [data-breadcrumb-part="link"] svg:first-child {
59
+ @apply mr-1.5;
60
+ }
61
+
62
+ /* ===== Breadcrumb Page (current) ===== */
63
+ [data-breadcrumb-part="page"] {
64
+ @apply inline-flex items-center;
65
+ @apply text-sm font-medium;
66
+ color: var(--foreground);
67
+ }
68
+
69
+ /* Page with icon */
70
+ [data-breadcrumb-part="page"] svg {
71
+ @apply size-3.5 shrink-0;
72
+ }
73
+
74
+ [data-breadcrumb-part="page"] svg:first-child {
75
+ @apply mr-1.5;
76
+ }
77
+
78
+ /* ===== Breadcrumb Separator ===== */
79
+ [data-breadcrumb-part="separator"] {
80
+ @apply inline-flex items-center;
81
+ color: var(--muted-foreground);
82
+ }
83
+
84
+ [data-breadcrumb-part="separator"] svg {
85
+ @apply size-3.5 shrink-0;
86
+ }
87
+
88
+ /* Hidden state for responsive collapsing */
89
+ [data-breadcrumb-part="separator"].hidden {
90
+ @apply hidden;
91
+ }
92
+
93
+ /* ===== Breadcrumb Ellipsis ===== */
94
+ [data-breadcrumb-part="ellipsis"] {
95
+ @apply inline-flex size-9 items-center justify-center;
96
+ color: var(--muted-foreground);
97
+ }
98
+
99
+ [data-breadcrumb-part="ellipsis"] svg {
100
+ @apply size-4 shrink-0;
101
+ }
102
+
103
+ /* Ellipsis as dropdown trigger */
104
+ [data-breadcrumb-part="ellipsis"][data-state="open"],
105
+ [data-breadcrumb-part="ellipsis"]:hover {
106
+ color: var(--foreground);
107
+ }
108
+
109
+ /* ===== Responsive Behavior ===== */
110
+ /* Items hidden by Stimulus controller */
111
+ [data-controller="breadcrumb"] [data-breadcrumb-target="item"].hidden,
112
+ [data-controller="breadcrumb"] [data-breadcrumb-target="ellipsisSeparator"].hidden {
113
+ @apply hidden;
114
+ }
115
+
116
+ /* Ellipsis shown by Stimulus controller */
117
+ [data-controller="breadcrumb"] [data-breadcrumb-target="ellipsis"]:not(.hidden) {
118
+ @apply inline-flex;
119
+ }
120
+
121
+ /* ===== Mobile Adjustments ===== */
122
+ @media (max-width: 640px) {
123
+ [data-breadcrumb-part="list"] {
124
+ @apply gap-1;
125
+ }
126
+
127
+ /* Hide middle items on very small screens by default */
128
+ [data-component="breadcrumbs"][data-auto-collapse] [data-breadcrumb-part="item"]:not(:first-child):not(:last-child) {
129
+ @apply hidden sm:inline-flex;
130
+ }
131
+ }
132
+
133
+ /* ===== Variants ===== */
134
+
135
+ /* Slash separator variant */
136
+ [data-component="breadcrumbs"][data-separator="slash"] [data-breadcrumb-part="separator"]::before {
137
+ content: "/";
138
+ @apply px-1;
139
+ }
140
+
141
+ [data-component="breadcrumbs"][data-separator="slash"] [data-breadcrumb-part="separator"] svg {
142
+ @apply hidden;
143
+ }
144
+
145
+ /* Dot separator variant */
146
+ [data-component="breadcrumbs"][data-separator="dot"] [data-breadcrumb-part="separator"]::before {
147
+ content: "•";
148
+ @apply px-1;
149
+ }
150
+
151
+ [data-component="breadcrumbs"][data-separator="dot"] [data-breadcrumb-part="separator"] svg {
152
+ @apply hidden;
153
+ }
154
+
155
+ /* Arrow separator variant */
156
+ [data-component="breadcrumbs"][data-separator="arrow"] [data-breadcrumb-part="separator"]::before {
157
+ content: "→";
158
+ @apply px-1;
159
+ }
160
+
161
+ [data-component="breadcrumbs"][data-separator="arrow"] [data-breadcrumb-part="separator"] svg {
162
+ @apply hidden;
163
+ }
@@ -0,0 +1,128 @@
1
+ /* ===== Card Component Styles ===== */
2
+ /*
3
+ * Card component for grouping related content with header, body, and footer.
4
+ * Uses data attributes for styling to maintain consistency with other components.
5
+ * Fully compatible with dark mode via CSS variables.
6
+ */
7
+
8
+ /* ===== Base Card Styles ===== */
9
+ [data-component="card"] {
10
+ display: flex;
11
+ flex-direction: column;
12
+
13
+ /* Border & Radius */
14
+ @apply rounded-xl border shadow;
15
+ border-color: var(--border);
16
+
17
+ /* Colors */
18
+ background-color: var(--card);
19
+ color: var(--card-foreground);
20
+ }
21
+
22
+ /* ===== Card Header ===== */
23
+ [data-component="card"] [data-card-part="header"] {
24
+ display: flex;
25
+ flex-direction: column;
26
+ @apply gap-1.5 p-6;
27
+ }
28
+
29
+ /* Header with row layout (when using action) */
30
+ [data-component="card"] [data-card-part="header"][data-layout="row"] {
31
+ flex-direction: row;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ @apply gap-4;
35
+ }
36
+
37
+ /* ===== Card Title ===== */
38
+ [data-component="card"] [data-card-part="title"] {
39
+ @apply text-lg font-semibold leading-none tracking-tight;
40
+ }
41
+
42
+ /* Small title variant (for compact headers) */
43
+ [data-component="card"] [data-card-part="title"][data-size="sm"] {
44
+ @apply text-sm font-medium;
45
+ }
46
+
47
+ /* ===== Card Description ===== */
48
+ [data-component="card"] [data-card-part="description"] {
49
+ @apply text-sm;
50
+ color: var(--muted-foreground);
51
+ }
52
+
53
+ /* ===== Card Action ===== */
54
+ [data-component="card"] [data-card-part="action"] {
55
+ display: flex;
56
+ align-items: center;
57
+ flex-shrink: 0;
58
+ @apply gap-2;
59
+ }
60
+
61
+ /* ===== Card Content ===== */
62
+ [data-component="card"] [data-card-part="content"] {
63
+ @apply p-6 pt-0;
64
+ }
65
+
66
+ /* Content with top padding (when no header) */
67
+ [data-component="card"] [data-card-part="content"][data-spacing="full"] {
68
+ @apply pt-6;
69
+ }
70
+
71
+ /* ===== Card Footer ===== */
72
+ [data-component="card"] [data-card-part="footer"] {
73
+ display: flex;
74
+ align-items: center;
75
+ @apply p-6 pt-0;
76
+ }
77
+
78
+ /* Footer with top padding (when no content) */
79
+ [data-component="card"] [data-card-part="footer"][data-spacing="full"] {
80
+ @apply pt-6;
81
+ }
82
+
83
+ /* Footer alignment variants */
84
+ [data-component="card"] [data-card-part="footer"][data-align="between"] {
85
+ justify-content: space-between;
86
+ }
87
+
88
+ [data-component="card"] [data-card-part="footer"][data-align="end"] {
89
+ justify-content: flex-end;
90
+ }
91
+
92
+ [data-component="card"] [data-card-part="footer"][data-align="center"] {
93
+ justify-content: center;
94
+ }
95
+
96
+ /* ===== Icon Support ===== */
97
+ [data-component="card"] [data-card-part="header"] svg,
98
+ [data-component="card"] [data-card-part="action"] svg {
99
+ @apply size-4 shrink-0;
100
+ color: var(--muted-foreground);
101
+ }
102
+
103
+ /* ===== Interactive Card (when used as link/button) ===== */
104
+ a[data-component="card"],
105
+ button[data-component="card"] {
106
+ text-decoration: none;
107
+ cursor: pointer;
108
+ @apply transition-[border-color,box-shadow] duration-150;
109
+ }
110
+
111
+ a[data-component="card"]:hover,
112
+ button[data-component="card"]:hover {
113
+ border-color: var(--accent);
114
+ }
115
+
116
+ a[data-component="card"]:focus-visible,
117
+ button[data-component="card"]:focus-visible {
118
+ outline: none;
119
+ box-shadow: 0 0 0 2px var(--background),
120
+ 0 0 0 4px var(--border);
121
+ }
122
+
123
+ /* ===== Dark Mode ===== */
124
+ /*
125
+ * Dark mode is handled automatically through CSS variables.
126
+ * The theme variables change based on the .dark class on html/body.
127
+ * No additional dark mode styles needed here.
128
+ */