kozenet_ui 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/app/assets/images/kozenet_ui/icons/cart.svg +1 -0
- data/app/assets/images/kozenet_ui/icons/heart.svg +1 -0
- data/app/assets/javascripts/kozenet_ui/controllers/dropdown_controller.js +55 -0
- data/app/assets/javascripts/kozenet_ui/controllers/header_controller.js +32 -0
- data/app/assets/javascripts/kozenet_ui/controllers/mobile_nav_controller.js +43 -0
- data/app/assets/javascripts/kozenet_ui/controllers/user_menu_controller.js +60 -0
- data/app/assets/javascripts/kozenet_ui/index.js +23 -0
- data/app/assets/stylesheets/kozenet_ui/base.css +69 -0
- data/app/assets/stylesheets/kozenet_ui/components/avatar.css +88 -0
- data/app/assets/stylesheets/kozenet_ui/components/badge.css +101 -0
- data/app/assets/stylesheets/kozenet_ui/components/button.css +230 -0
- data/app/assets/stylesheets/kozenet_ui/components/header.css +389 -0
- data/app/assets/stylesheets/kozenet_ui/components/utilities.css +270 -0
- data/app/assets/stylesheets/kozenet_ui/components.css +8 -0
- data/app/assets/stylesheets/kozenet_ui/tokens.css +168 -0
- data/app/components/kozenet_ui/avatar_component.rb +72 -0
- data/app/components/kozenet_ui/badge_component.rb +62 -0
- data/app/components/kozenet_ui/base_component.rb +84 -0
- data/app/components/kozenet_ui/button_component.rb +156 -0
- data/app/components/kozenet_ui/header_component/action_button_component.html.erb +11 -0
- data/app/components/kozenet_ui/header_component/action_button_component.rb +29 -0
- data/app/components/kozenet_ui/header_component/brand_component.rb +32 -0
- data/app/components/kozenet_ui/header_component/cta_component.html.erb +5 -0
- data/app/components/kozenet_ui/header_component/cta_component.rb +23 -0
- data/app/components/kozenet_ui/header_component/nav_item_component.html.erb +8 -0
- data/app/components/kozenet_ui/header_component/nav_item_component.rb +28 -0
- data/app/components/kozenet_ui/header_component/search_component.html.erb +17 -0
- data/app/components/kozenet_ui/header_component/search_component.rb +29 -0
- data/app/components/kozenet_ui/header_component/user_menu_component.html.erb +18 -0
- data/app/components/kozenet_ui/header_component/user_menu_component.rb +21 -0
- data/app/components/kozenet_ui/header_component.html.erb +81 -0
- data/app/components/kozenet_ui/header_component.rb +40 -0
- data/app/helpers/kozenet_ui/component_helper.rb +59 -0
- data/app/helpers/kozenet_ui/icon_helper.rb +16 -0
- data/lib/generators/kozenet_ui/install/install_generator.rb +67 -0
- data/lib/generators/kozenet_ui/install/templates/kozenet_ui.rb +39 -0
- data/lib/generators/kozenet_ui/install/templates/tailwind.config.js +19 -0
- data/lib/kozenet_ui/configuration.rb +21 -0
- data/lib/kozenet_ui/engine.rb +94 -0
- data/lib/kozenet_ui/theme/palette.rb +132 -0
- data/lib/kozenet_ui/theme/tokens.rb +100 -0
- data/lib/kozenet_ui/theme/variants.rb +51 -0
- data/lib/kozenet_ui/version.rb +5 -0
- data/lib/kozenet_ui.rb +30 -0
- metadata +308 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Layout utilities */
|
|
3
|
+
.container-fluid {
|
|
4
|
+
width: 100%;
|
|
5
|
+
margin-left: auto;
|
|
6
|
+
margin-right: auto;
|
|
7
|
+
padding-left: 1rem;
|
|
8
|
+
padding-right: 1rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (min-width:640px) {
|
|
12
|
+
.container-fluid {
|
|
13
|
+
padding-left: 1.5rem;
|
|
14
|
+
padding-right: 1.5rem;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (min-width:1024px) {
|
|
19
|
+
.container-fluid {
|
|
20
|
+
padding-left: 2rem;
|
|
21
|
+
padding-right: 2rem;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Card components */
|
|
26
|
+
.card {
|
|
27
|
+
border-radius: var(--kz-radius-lg);
|
|
28
|
+
background: color-mix(in srgb, var(--kz-bg-elevated) 80%, transparent);
|
|
29
|
+
backdrop-filter: blur(8px);
|
|
30
|
+
border: 1px solid var(--kz-border-default);
|
|
31
|
+
box-shadow: var(--kz-shadow-sm);
|
|
32
|
+
padding: 1.25rem;
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: 1rem;
|
|
36
|
+
transition: box-shadow var(--kz-transition-base);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.card:hover {
|
|
40
|
+
box-shadow: var(--kz-shadow-md);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.card-muted {
|
|
44
|
+
background: color-mix(in srgb, var(--kz-bg-muted) 70%, transparent);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Stats display */
|
|
48
|
+
.stat {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 0.25rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.stat-label {
|
|
55
|
+
font-size: var(--kz-font-size-xs);
|
|
56
|
+
font-weight: var(--kz-font-weight-medium);
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
letter-spacing: 0.05em;
|
|
59
|
+
color: var(--kz-text-muted);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.stat-value {
|
|
63
|
+
font-size: var(--kz-font-size-2xl);
|
|
64
|
+
font-weight: var(--kz-font-weight-semibold);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Simple button utilities (use kz-btn for full featured buttons) */
|
|
68
|
+
.btn {
|
|
69
|
+
display: inline-flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
gap: 0.5rem;
|
|
73
|
+
font-weight: var(--kz-font-weight-medium);
|
|
74
|
+
font-size: var(--kz-font-size-sm);
|
|
75
|
+
border-radius: var(--kz-radius-md);
|
|
76
|
+
padding: 0.5rem 1rem;
|
|
77
|
+
user-select: none;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
transition: all var(--kz-transition-fast);
|
|
80
|
+
position: relative;
|
|
81
|
+
border: 0;
|
|
82
|
+
background: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.btn:focus-visible {
|
|
86
|
+
outline: 2px solid rgb(99 102 241 / 0.5);
|
|
87
|
+
outline-offset: 2px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.btn:disabled {
|
|
91
|
+
opacity: 0.4;
|
|
92
|
+
cursor: not-allowed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.btn-primary {
|
|
96
|
+
background: linear-gradient(110deg, #6366f1, #0ea5e9 55%, #06b6d4);
|
|
97
|
+
color: #fff;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.btn-primary:hover:not(:disabled) {
|
|
101
|
+
transform: translateY(-2px);
|
|
102
|
+
box-shadow: var(--kz-shadow-md);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn-outline {
|
|
106
|
+
background: transparent;
|
|
107
|
+
border: 1px solid var(--kz-border-default);
|
|
108
|
+
color: var(--kz-text-default);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.btn-outline:hover:not(:disabled) {
|
|
112
|
+
background: var(--kz-bg-muted);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.btn-ghost {
|
|
116
|
+
background: transparent;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.btn-ghost:hover:not(:disabled) {
|
|
120
|
+
background: var(--kz-bg-muted);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.btn-xs {
|
|
124
|
+
font-size: var(--kz-font-size-xs);
|
|
125
|
+
padding: 0.35rem 0.6rem;
|
|
126
|
+
border-radius: var(--kz-radius-sm);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Badge utility (simple version) */
|
|
130
|
+
.badge {
|
|
131
|
+
display: inline-flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
border-radius: var(--kz-radius-full);
|
|
134
|
+
padding: 0 0.5rem;
|
|
135
|
+
height: 1.5rem;
|
|
136
|
+
font-size: var(--kz-font-size-xs);
|
|
137
|
+
font-weight: var(--kz-font-weight-semibold);
|
|
138
|
+
background: linear-gradient(120deg, rgba(99,102,241,.15), rgba(14,165,233,.18));
|
|
139
|
+
color: var(--kz-text-muted);
|
|
140
|
+
letter-spacing: 0.05em;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Input utility */
|
|
144
|
+
.input {
|
|
145
|
+
width: 100%;
|
|
146
|
+
border-radius: var(--kz-radius-md);
|
|
147
|
+
border: 1px solid var(--kz-border-default);
|
|
148
|
+
background: var(--kz-bg-elevated);
|
|
149
|
+
padding: 0.5rem 0.75rem;
|
|
150
|
+
font-size: var(--kz-font-size-sm);
|
|
151
|
+
line-height: 1.25rem;
|
|
152
|
+
transition: all var(--kz-transition-fast);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.input::placeholder {
|
|
156
|
+
color: var(--kz-text-muted);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.input:focus {
|
|
160
|
+
outline: 2px solid rgb(99 102 241 / 0.5);
|
|
161
|
+
outline-offset: 2px;
|
|
162
|
+
border-color: transparent;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Divider */
|
|
166
|
+
.divider {
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 1px;
|
|
169
|
+
background: var(--kz-border-default);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Navigation section label */
|
|
173
|
+
.nav-section-label {
|
|
174
|
+
font-size: 0.625rem;
|
|
175
|
+
font-weight: var(--kz-font-weight-semibold);
|
|
176
|
+
letter-spacing: 0.1em;
|
|
177
|
+
text-transform: uppercase;
|
|
178
|
+
color: var(--kz-text-muted);
|
|
179
|
+
padding-left: 0.75rem;
|
|
180
|
+
padding-right: 0.75rem;
|
|
181
|
+
margin-top: 1.5rem;
|
|
182
|
+
margin-bottom: 0.5rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Glass morphism effect */
|
|
186
|
+
.glass-panel {
|
|
187
|
+
background: rgba(255, 255, 255, 0.1);
|
|
188
|
+
backdrop-filter: blur(20px);
|
|
189
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
190
|
+
border-radius: var(--kz-radius-lg);
|
|
191
|
+
box-shadow: var(--kz-shadow-lg);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.dark .glass-panel {
|
|
195
|
+
background: rgba(0, 0, 0, 0.2);
|
|
196
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Play surface (gradient background) */
|
|
200
|
+
.play-surface {
|
|
201
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
202
|
+
min-height: 100vh;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Custom slider */
|
|
206
|
+
.slider {
|
|
207
|
+
-webkit-appearance: none;
|
|
208
|
+
appearance: none;
|
|
209
|
+
background: linear-gradient(to right, #8b5cf6 0%, #8b5cf6 50%, #e5e7eb 50%, #e5e7eb 100%);
|
|
210
|
+
outline: none;
|
|
211
|
+
border-radius: var(--kz-radius-md);
|
|
212
|
+
height: 8px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.slider::-webkit-slider-thumb {
|
|
216
|
+
-webkit-appearance: none;
|
|
217
|
+
appearance: none;
|
|
218
|
+
height: 20px;
|
|
219
|
+
width: 20px;
|
|
220
|
+
border-radius: 50%;
|
|
221
|
+
background: #8b5cf6;
|
|
222
|
+
cursor: pointer;
|
|
223
|
+
box-shadow: var(--kz-shadow-sm);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.slider::-moz-range-thumb {
|
|
227
|
+
height: 20px;
|
|
228
|
+
width: 20px;
|
|
229
|
+
border-radius: 50%;
|
|
230
|
+
background: #8b5cf6;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
border: none;
|
|
233
|
+
box-shadow: var(--kz-shadow-sm);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Animations */
|
|
237
|
+
@keyframes fadeIn {
|
|
238
|
+
from {
|
|
239
|
+
opacity: 0;
|
|
240
|
+
transform: translateY(4px);
|
|
241
|
+
}
|
|
242
|
+
to {
|
|
243
|
+
opacity: 1;
|
|
244
|
+
transform: translateY(0);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.animate-fadeIn {
|
|
249
|
+
animation: fadeIn 0.3s ease-out;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Theme icon utilities */
|
|
254
|
+
[data-theme] .theme-icon-light,
|
|
255
|
+
[data-theme] .theme-icon-dark,
|
|
256
|
+
[data-theme] .theme-icon-system {
|
|
257
|
+
display: none;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
[data-theme='light'] .theme-icon-dark {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
[data-theme='dark'] .theme-icon-light {
|
|
265
|
+
display: inline-flex;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
[data-theme='system'] .theme-icon-system {
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/* Kozenet UI Design Tokens */
|
|
2
|
+
/* Auto-generated from Ruby tokens - Can be overridden */
|
|
3
|
+
|
|
4
|
+
@layer base {
|
|
5
|
+
:root {
|
|
6
|
+
/* Spacing */
|
|
7
|
+
--kz-spacing-xs: 0.25rem;
|
|
8
|
+
--kz-spacing-sm: 0.5rem;
|
|
9
|
+
--kz-spacing-md: 1rem;
|
|
10
|
+
--kz-spacing-lg: 1.5rem;
|
|
11
|
+
--kz-spacing-xl: 2rem;
|
|
12
|
+
--kz-spacing-2xl: 3rem;
|
|
13
|
+
--kz-spacing-3xl: 4rem;
|
|
14
|
+
|
|
15
|
+
/* Border Radius */
|
|
16
|
+
--kz-radius-sm: 12px;
|
|
17
|
+
--kz-radius-md: 16px;
|
|
18
|
+
--kz-radius-lg: 20px;
|
|
19
|
+
--kz-radius-xl: 24px;
|
|
20
|
+
--kz-radius-2xl: 28px;
|
|
21
|
+
--kz-radius-full: 9999px;
|
|
22
|
+
|
|
23
|
+
/* Typography */
|
|
24
|
+
--kz-font-size-xs: 0.65rem;
|
|
25
|
+
--kz-font-size-sm: 0.75rem;
|
|
26
|
+
--kz-font-size-base: 0.875rem;
|
|
27
|
+
--kz-font-size-lg: 1rem;
|
|
28
|
+
--kz-font-size-xl: 1.25rem;
|
|
29
|
+
--kz-font-size-2xl: 1.5rem;
|
|
30
|
+
--kz-font-size-3xl: 2rem;
|
|
31
|
+
|
|
32
|
+
--kz-font-weight-normal: 400;
|
|
33
|
+
--kz-font-weight-medium: 500;
|
|
34
|
+
--kz-font-weight-semibold: 600;
|
|
35
|
+
--kz-font-weight-bold: 700;
|
|
36
|
+
|
|
37
|
+
/* Shadows */
|
|
38
|
+
--kz-shadow-sm: 0 1px 2px rgba(0,0,0,.05), 0 2px 6px -2px rgba(0,0,0,.08);
|
|
39
|
+
--kz-shadow-md: 0 4px 14px -6px rgba(0,0,0,.25);
|
|
40
|
+
--kz-shadow-lg: 0 10px 30px -12px rgba(0,0,0,.45);
|
|
41
|
+
--kz-shadow-xl: 0 20px 60px -26px rgba(0,0,0,.28);
|
|
42
|
+
|
|
43
|
+
/* Transitions */
|
|
44
|
+
--kz-transition-fast: 0.15s ease;
|
|
45
|
+
--kz-transition-base: 0.25s ease;
|
|
46
|
+
--kz-transition-slow: 0.35s ease;
|
|
47
|
+
|
|
48
|
+
/* Z-index */
|
|
49
|
+
--kz-z-dropdown: 50;
|
|
50
|
+
--kz-z-sticky: 60;
|
|
51
|
+
--kz-z-modal: 70;
|
|
52
|
+
--kz-z-popover: 80;
|
|
53
|
+
--kz-z-toast: 90;
|
|
54
|
+
|
|
55
|
+
/* Default Light Mode Colors */
|
|
56
|
+
--kz-bg-base: #ffffff;
|
|
57
|
+
--kz-bg-elevated: #f8fafc;
|
|
58
|
+
--kz-bg-muted: #f1f5f9;
|
|
59
|
+
--kz-text-default: #0f172a;
|
|
60
|
+
--kz-text-muted: #64748b;
|
|
61
|
+
--kz-border-default: #e2e8f0;
|
|
62
|
+
--kz-border-muted: #f1f5f9;
|
|
63
|
+
|
|
64
|
+
--kz-primary-500: 99, 102, 241; /* #6366f1 */
|
|
65
|
+
--kz-accent-500: 6, 182, 212; /* #06b6d4 */
|
|
66
|
+
--kz-accent-400: 14, 165, 233; /* #0ea5e9 */
|
|
67
|
+
--kz-cta-gradient: linear-gradient(110deg, #6366f1, #0ea5e9 55%, #06b6d4);
|
|
68
|
+
--kz-cta-gradient-dark: linear-gradient(110deg, #818cf8, #38bdf8 55%, #06b6d4);
|
|
69
|
+
--kz-cta-text: #fff;
|
|
70
|
+
--kz-cta-text-dark: #fff;
|
|
71
|
+
|
|
72
|
+
--bg-default: #ffffff;
|
|
73
|
+
--bg-muted: #f8fafc;
|
|
74
|
+
--bg-elevated: #ffffff;
|
|
75
|
+
--bg-accent: #0ea5e9;
|
|
76
|
+
--bg-accent-hover: #0284c7;
|
|
77
|
+
--text-default: #0f172a;
|
|
78
|
+
--text-muted: #64748b;
|
|
79
|
+
--border-default: #e2e8f0;
|
|
80
|
+
--border-strong: #cbd5e1;
|
|
81
|
+
--focus-ring: #0ea5e9;
|
|
82
|
+
--radius-xs: 2px;
|
|
83
|
+
--radius-sm: 4px;
|
|
84
|
+
--radius-md: 6px;
|
|
85
|
+
--radius-lg: 8px;
|
|
86
|
+
--radius-xl: 12px;
|
|
87
|
+
--radius-pill: 999px;
|
|
88
|
+
--shadow-color: 210 40% 2%;
|
|
89
|
+
--gradient-base-from: #f0f9ff;
|
|
90
|
+
--gradient-base-to: #e0f2fe;
|
|
91
|
+
--gradient-accent-from: #6366f1;
|
|
92
|
+
--gradient-accent-via: #0ea5e9;
|
|
93
|
+
--gradient-accent-to: #06b6d4;
|
|
94
|
+
--gradient-spot-1: rgba(99,102,241,0.35);
|
|
95
|
+
--gradient-spot-2: rgba(14,165,233,0.30);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Dark Mode */
|
|
99
|
+
[data-theme="dark"],
|
|
100
|
+
.dark {
|
|
101
|
+
--kz-bg-base: #0f172a;
|
|
102
|
+
--kz-bg-elevated: #1e293b;
|
|
103
|
+
--kz-bg-muted: #334155;
|
|
104
|
+
--kz-text-default: #f1f5f9;
|
|
105
|
+
--kz-text-muted: #94a3b8;
|
|
106
|
+
--kz-border-default: #334155;
|
|
107
|
+
--kz-border-muted: #1e293b;
|
|
108
|
+
|
|
109
|
+
--kz-cta-gradient: var(--kz-cta-gradient-dark);
|
|
110
|
+
--kz-cta-text: var(--kz-cta-text-dark);
|
|
111
|
+
|
|
112
|
+
--bg-default: #0b0d11;
|
|
113
|
+
--bg-muted: #11151c;
|
|
114
|
+
--bg-elevated: rgba(23,28,36,0.9);
|
|
115
|
+
--bg-accent: #0284c7;
|
|
116
|
+
--bg-accent-hover: #0369a1;
|
|
117
|
+
--text-default: #f1f5f9;
|
|
118
|
+
--text-muted: #64748b;
|
|
119
|
+
--border-default: #1f242c;
|
|
120
|
+
--border-strong: #2a3039;
|
|
121
|
+
--focus-ring: #0ea5e9;
|
|
122
|
+
--shadow-color: 210 40% 2%;
|
|
123
|
+
--gradient-base-from: #0b0d11;
|
|
124
|
+
--gradient-base-to: #11151c;
|
|
125
|
+
--gradient-accent-from: #1e40af;
|
|
126
|
+
--gradient-accent-via: #0369a1;
|
|
127
|
+
--gradient-accent-to: #0891b2;
|
|
128
|
+
--gradient-spot-1: rgba(56,189,248,0.20);
|
|
129
|
+
--gradient-spot-2: rgba(99,102,241,0.18);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
133
|
+
body { background: var(--bg-default); color: var(--text-default); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }
|
|
134
|
+
* { border-color: var(--border-default); }
|
|
135
|
+
::selection { background: var(--bg-accent); color: #fff; }
|
|
136
|
+
.app-bg {
|
|
137
|
+
position: relative;
|
|
138
|
+
min-height: 100vh;
|
|
139
|
+
overflow-x: hidden;
|
|
140
|
+
isolation: isolate;
|
|
141
|
+
}
|
|
142
|
+
.app-bg:not(:first-of-type) { background: none !important; }
|
|
143
|
+
.app-bg::before {
|
|
144
|
+
content: "";
|
|
145
|
+
position: fixed; inset:0; pointer-events:none; z-index:-1;
|
|
146
|
+
background:
|
|
147
|
+
radial-gradient(circle at 18% 22%, var(--gradient-spot-1) 0, transparent 60%),
|
|
148
|
+
radial-gradient(circle at 82% 18%, var(--gradient-spot-2) 0, transparent 62%),
|
|
149
|
+
linear-gradient(135deg, var(--gradient-base-from) 0%, var(--gradient-base-to) 100%);
|
|
150
|
+
background-attachment: fixed;
|
|
151
|
+
}
|
|
152
|
+
.dark .app-bg::before {
|
|
153
|
+
background:
|
|
154
|
+
radial-gradient(circle at 18% 22%, var(--gradient-spot-1) 0, transparent 60%),
|
|
155
|
+
radial-gradient(circle at 82% 18%, var(--gradient-spot-2) 0, transparent 62%),
|
|
156
|
+
linear-gradient(140deg, var(--gradient-base-from) 0%, var(--gradient-base-to) 100%);
|
|
157
|
+
}
|
|
158
|
+
.app-bg::after {
|
|
159
|
+
content: "";
|
|
160
|
+
position: fixed; inset:0; pointer-events:none; z-index:-1; mix-blend-mode:overlay;
|
|
161
|
+
background: linear-gradient(120deg, var(--gradient-accent-from), var(--gradient-accent-via) 55%, var(--gradient-accent-to));
|
|
162
|
+
opacity: .07; border-radius: inherit;
|
|
163
|
+
}
|
|
164
|
+
.dark .app-bg::after { opacity:.10; }
|
|
165
|
+
@supports (-webkit-touch-callout: none) {
|
|
166
|
+
.app-bg::before, .app-bg::after { position: absolute; }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KozenetUi
|
|
4
|
+
# Avatar component for user profiles and images
|
|
5
|
+
# Supports images, initials, and icons with multiple sizes
|
|
6
|
+
#
|
|
7
|
+
# @example With image
|
|
8
|
+
# <%= kz_avatar(src: user.avatar_url, alt: user.name) %>
|
|
9
|
+
#
|
|
10
|
+
# @example With initials
|
|
11
|
+
# <%= kz_avatar(initials: "JD", variant: :primary) %>
|
|
12
|
+
#
|
|
13
|
+
# @example With custom size
|
|
14
|
+
# <%= kz_avatar(src: url, size: :lg) %>
|
|
15
|
+
class AvatarComponent < BaseComponent
|
|
16
|
+
# rubocop:disable Metrics/ParameterLists
|
|
17
|
+
def initialize(
|
|
18
|
+
src: nil,
|
|
19
|
+
alt: "Avatar",
|
|
20
|
+
initials: nil,
|
|
21
|
+
variant: :primary,
|
|
22
|
+
size: :md,
|
|
23
|
+
html_options: {}
|
|
24
|
+
)
|
|
25
|
+
super(variant: variant, size: size, **html_options)
|
|
26
|
+
@src = src
|
|
27
|
+
@alt = alt
|
|
28
|
+
@initials = initials
|
|
29
|
+
end
|
|
30
|
+
# rubocop:enable Metrics/ParameterLists
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
tag.div(**html_attrs) do
|
|
34
|
+
avatar_content
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def base_classes
|
|
41
|
+
"kz-avatar inline-flex items-center justify-center overflow-hidden"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def avatar_content
|
|
45
|
+
if @src.present?
|
|
46
|
+
tag.img(src: @src, alt: @alt, class: "w-full h-full object-cover")
|
|
47
|
+
elsif @initials.present?
|
|
48
|
+
tag.span(class: "kz-avatar-initials") { @initials }
|
|
49
|
+
else
|
|
50
|
+
default_icon
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# rubocop:disable Metrics/MethodLength
|
|
55
|
+
def default_icon
|
|
56
|
+
tag.svg(
|
|
57
|
+
width: "20",
|
|
58
|
+
height: "20",
|
|
59
|
+
viewBox: "0 0 24 24",
|
|
60
|
+
fill: "none",
|
|
61
|
+
stroke: "currentColor",
|
|
62
|
+
stroke_width: "2"
|
|
63
|
+
) do
|
|
64
|
+
safe_join([
|
|
65
|
+
tag.circle(cx: "12", cy: "8", r: "5"),
|
|
66
|
+
tag.path(d: "M20 21a8 8 0 1 0-16 0")
|
|
67
|
+
])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
# rubocop:enable Metrics/MethodLength
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KozenetUi
|
|
4
|
+
# Badge component for labels, statuses, and counts
|
|
5
|
+
# Supports multiple variants and sizes
|
|
6
|
+
#
|
|
7
|
+
# @example Status badge
|
|
8
|
+
# <%= kz_badge(variant: :success) { "Active" } %>
|
|
9
|
+
#
|
|
10
|
+
# @example Count badge
|
|
11
|
+
# <%= kz_badge(variant: :primary, size: :sm) { "99+" } %>
|
|
12
|
+
#
|
|
13
|
+
# @example With icon
|
|
14
|
+
# <%= kz_badge(variant: :warning) do |badge| %>
|
|
15
|
+
# <% badge.with_icon do %>
|
|
16
|
+
# <svg>...</svg>
|
|
17
|
+
# <% end %>
|
|
18
|
+
# Pending
|
|
19
|
+
# <% end %>
|
|
20
|
+
class BadgeComponent < BaseComponent
|
|
21
|
+
renders_one :icon
|
|
22
|
+
|
|
23
|
+
def initialize(
|
|
24
|
+
variant: :primary,
|
|
25
|
+
size: :md,
|
|
26
|
+
pill: true,
|
|
27
|
+
**html_options
|
|
28
|
+
)
|
|
29
|
+
super(variant: variant, size: size, **html_options)
|
|
30
|
+
@pill = pill
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call
|
|
34
|
+
tag.span(**html_attrs) do
|
|
35
|
+
badge_content
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def base_classes
|
|
42
|
+
classes = [
|
|
43
|
+
"kz-badge",
|
|
44
|
+
"inline-flex items-center gap-1",
|
|
45
|
+
"font-semibold uppercase tracking-wider"
|
|
46
|
+
]
|
|
47
|
+
classes << "rounded-full" if @pill
|
|
48
|
+
classes.join(" ")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def badge_content
|
|
52
|
+
if icon?
|
|
53
|
+
safe_join([
|
|
54
|
+
tag.span(class: "kz-badge-icon") { icon },
|
|
55
|
+
tag.span { content }
|
|
56
|
+
])
|
|
57
|
+
else
|
|
58
|
+
content
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KozenetUi
|
|
4
|
+
# Base component that all Kozenet UI components inherit from
|
|
5
|
+
# Provides common functionality for variant handling, class merging, etc.
|
|
6
|
+
class BaseComponent < ViewComponent::Base
|
|
7
|
+
attr_reader :variant, :size, :html_options
|
|
8
|
+
|
|
9
|
+
def initialize(variant: nil, size: nil, class: nil, **html_options)
|
|
10
|
+
super()
|
|
11
|
+
@variant = variant || KozenetUi.configuration.default_variant
|
|
12
|
+
@size = size || KozenetUi.configuration.default_size
|
|
13
|
+
@custom_class = binding.local_variable_get(:class)
|
|
14
|
+
@html_options = html_options
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# Build final CSS classes
|
|
20
|
+
def component_classes
|
|
21
|
+
[
|
|
22
|
+
base_classes,
|
|
23
|
+
variant_class,
|
|
24
|
+
size_class,
|
|
25
|
+
@custom_class
|
|
26
|
+
].compact.join(" ")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Override in subclasses
|
|
30
|
+
def base_classes
|
|
31
|
+
"kz-component"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get variant class from Variants helper
|
|
35
|
+
def variant_class
|
|
36
|
+
return nil unless @variant
|
|
37
|
+
|
|
38
|
+
component_type = self.class.name.demodulize.underscore.gsub("_component", "")
|
|
39
|
+
begin
|
|
40
|
+
KozenetUi::Theme::Variants.public_send(component_type, @variant)
|
|
41
|
+
rescue StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get size class from Variants helper
|
|
47
|
+
def size_class
|
|
48
|
+
return nil unless @size
|
|
49
|
+
|
|
50
|
+
KozenetUi::Theme::Variants.size(@size)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Merge HTML attributes safely
|
|
54
|
+
def html_attrs
|
|
55
|
+
@html_options.merge(class: component_classes)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Helper for rendering slots with fallback
|
|
59
|
+
def render_slot_or_content(slot, &fallback)
|
|
60
|
+
if slot?
|
|
61
|
+
slot
|
|
62
|
+
elsif fallback
|
|
63
|
+
capture(&fallback)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if running in dark mode (from request or config)
|
|
68
|
+
def dark_mode?
|
|
69
|
+
helpers.cookies[:theme] == "dark"
|
|
70
|
+
rescue StandardError
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get current theme palette
|
|
75
|
+
def theme_palette
|
|
76
|
+
KozenetUi.configuration.palette
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Stimulus controller name with prefix
|
|
80
|
+
def stimulus_controller(name)
|
|
81
|
+
"#{KozenetUi.configuration.stimulus_prefix}-#{name}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|