brainzlab-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.
@@ -0,0 +1,262 @@
1
+ /* ============================================
2
+ BRAINZLAB UI - UTILITIES
3
+ ============================================ */
4
+
5
+ /* ----------------------------------------
6
+ FOCUS RING
7
+ ---------------------------------------- */
8
+ .focus-ring:focus-visible {
9
+ outline: 2px solid var(--color-primary-500);
10
+ outline-offset: 2px;
11
+ }
12
+
13
+ /* ----------------------------------------
14
+ GRADIENTS
15
+ ---------------------------------------- */
16
+ .bg-gradient-warm {
17
+ background: linear-gradient(135deg, var(--color-cream-50) 0%, var(--color-cream-100) 100%);
18
+ }
19
+
20
+ .bg-gradient-primary {
21
+ background: linear-gradient(135deg, var(--color-primary-500) 0%, var(--color-primary-600) 100%);
22
+ }
23
+
24
+ /* Gradient Mesh */
25
+ .gradient-mesh {
26
+ position: absolute;
27
+ inset: 0;
28
+ overflow: hidden;
29
+ pointer-events: none;
30
+ }
31
+
32
+ .gradient-blob {
33
+ position: absolute;
34
+ border-radius: 50%;
35
+ filter: blur(80px);
36
+ opacity: 0.5;
37
+ }
38
+
39
+ .gradient-blob-primary { background: var(--color-primary-300); }
40
+ .gradient-blob-success { background: var(--color-success-300); }
41
+ .gradient-blob-error { background: var(--color-error-300); }
42
+ .gradient-blob-info { background: var(--color-info-300); }
43
+
44
+ /* ----------------------------------------
45
+ TERMINAL/CODE
46
+ ---------------------------------------- */
47
+ .terminal {
48
+ background: var(--color-ink-950);
49
+ border-radius: 16px;
50
+ overflow: hidden;
51
+ }
52
+
53
+ .terminal-header {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ padding: 16px 20px;
58
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
59
+ }
60
+
61
+ .terminal-dot {
62
+ width: 12px;
63
+ height: 12px;
64
+ border-radius: 50%;
65
+ }
66
+
67
+ .terminal-dot-red { background: #ff5f56; }
68
+ .terminal-dot-yellow { background: #ffbd2e; }
69
+ .terminal-dot-green { background: #27ca40; }
70
+
71
+ .terminal-body {
72
+ padding: 20px;
73
+ font-family: var(--font-mono);
74
+ font-size: 14px;
75
+ line-height: 1.6;
76
+ color: var(--color-cream-200);
77
+ }
78
+
79
+ /* Log Entries */
80
+ .log-entry {
81
+ animation: log-slide-in 0.3s var(--ease-out-expo);
82
+ }
83
+
84
+ .log-level {
85
+ display: inline-block;
86
+ padding: 2px 8px;
87
+ border-radius: 4px;
88
+ font-size: 11px;
89
+ font-weight: 600;
90
+ text-transform: uppercase;
91
+ }
92
+
93
+ .log-level-info { background: rgba(90, 154, 110, 0.2); color: var(--color-success-400); }
94
+ .log-level-error { background: rgba(199, 85, 85, 0.2); color: var(--color-error-400); }
95
+ .log-level-debug { background: rgba(90, 138, 180, 0.2); color: var(--color-info-400); }
96
+ .log-level-warn { background: rgba(238, 142, 109, 0.2); color: var(--color-primary-400); }
97
+
98
+ /* ----------------------------------------
99
+ TOOLTIPS
100
+ ---------------------------------------- */
101
+ .tooltip {
102
+ position: relative;
103
+ }
104
+
105
+ .tooltip-content {
106
+ position: absolute;
107
+ bottom: 100%;
108
+ left: 50%;
109
+ transform: translateX(-50%);
110
+ padding: 0.5rem 0.75rem;
111
+ background: var(--color-ink-950);
112
+ color: white;
113
+ font-size: 0.75rem;
114
+ font-weight: 500;
115
+ border-radius: 0.5rem;
116
+ white-space: nowrap;
117
+ opacity: 0;
118
+ visibility: hidden;
119
+ transition: all 0.15s ease;
120
+ margin-bottom: 0.5rem;
121
+ z-index: 50;
122
+ }
123
+
124
+ .tooltip:hover .tooltip-content {
125
+ opacity: 1;
126
+ visibility: visible;
127
+ }
128
+
129
+ /* ----------------------------------------
130
+ SKELETON LOADING
131
+ ---------------------------------------- */
132
+ .skeleton {
133
+ background: linear-gradient(90deg, var(--color-cream-100) 25%, var(--color-cream-200) 50%, var(--color-cream-100) 75%);
134
+ background-size: 200% 100%;
135
+ animation: shimmer 1.5s infinite;
136
+ border-radius: 0.5rem;
137
+ }
138
+
139
+ .skeleton-text { height: 1rem; width: 100%; }
140
+ .skeleton-title { height: 1.5rem; width: 60%; }
141
+ .skeleton-avatar { width: 2.5rem; height: 2.5rem; border-radius: 50%; }
142
+ .skeleton-button { height: 2.5rem; width: 6rem; border-radius: 9999px; }
143
+
144
+ /* ----------------------------------------
145
+ MODAL OVERLAY
146
+ ---------------------------------------- */
147
+ .modal-overlay {
148
+ position: fixed;
149
+ inset: 0;
150
+ background: rgba(0, 0, 0, 0.5);
151
+ backdrop-filter: blur(4px);
152
+ z-index: 40;
153
+ }
154
+
155
+ .modal {
156
+ position: fixed;
157
+ top: 50%;
158
+ left: 50%;
159
+ transform: translate(-50%, -50%);
160
+ background: white;
161
+ border-radius: 1rem;
162
+ box-shadow: var(--shadow-large);
163
+ z-index: 50;
164
+ max-width: 90vw;
165
+ max-height: 90vh;
166
+ overflow: auto;
167
+ }
168
+
169
+ .modal-sm { width: 24rem; }
170
+ .modal-md { width: 32rem; }
171
+ .modal-lg { width: 48rem; }
172
+ .modal-xl { width: 64rem; }
173
+
174
+ .modal-header {
175
+ padding: 1.5rem;
176
+ border-bottom: 1px solid var(--color-cream-200);
177
+ }
178
+
179
+ .modal-body { padding: 1.5rem; }
180
+
181
+ .modal-footer {
182
+ padding: 1rem 1.5rem;
183
+ border-top: 1px solid var(--color-cream-200);
184
+ display: flex;
185
+ justify-content: flex-end;
186
+ gap: 0.75rem;
187
+ }
188
+
189
+ /* ----------------------------------------
190
+ DROPDOWN
191
+ ---------------------------------------- */
192
+ .dropdown {
193
+ position: relative;
194
+ }
195
+
196
+ .dropdown-menu {
197
+ position: absolute;
198
+ top: 100%;
199
+ right: 0;
200
+ min-width: 12rem;
201
+ margin-top: 0.5rem;
202
+ background: white;
203
+ border: 1px solid var(--color-cream-200);
204
+ border-radius: 0.75rem;
205
+ box-shadow: var(--shadow-medium);
206
+ z-index: 30;
207
+ opacity: 0;
208
+ visibility: hidden;
209
+ transform: translateY(-8px);
210
+ transition: all 0.15s ease;
211
+ }
212
+
213
+ .dropdown.open .dropdown-menu,
214
+ .dropdown:focus-within .dropdown-menu {
215
+ opacity: 1;
216
+ visibility: visible;
217
+ transform: translateY(0);
218
+ }
219
+
220
+ .dropdown-item {
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 0.5rem;
224
+ padding: 0.625rem 1rem;
225
+ font-size: 0.875rem;
226
+ color: var(--color-ink-700);
227
+ transition: all 0.15s ease;
228
+ }
229
+
230
+ .dropdown-item:hover {
231
+ background: var(--color-cream-50);
232
+ color: var(--color-ink-900);
233
+ }
234
+
235
+ .dropdown-item:first-child { border-radius: 0.75rem 0.75rem 0 0; }
236
+ .dropdown-item:last-child { border-radius: 0 0 0.75rem 0.75rem; }
237
+
238
+ .dropdown-divider {
239
+ height: 1px;
240
+ background: var(--color-cream-200);
241
+ margin: 0.25rem 0;
242
+ }
243
+
244
+ /* ----------------------------------------
245
+ SPACING UTILITIES
246
+ ---------------------------------------- */
247
+ .space-y-1 > * + * { margin-top: 0.25rem; }
248
+ .space-y-2 > * + * { margin-top: 0.5rem; }
249
+ .space-y-3 > * + * { margin-top: 0.75rem; }
250
+ .space-y-4 > * + * { margin-top: 1rem; }
251
+ .space-y-6 > * + * { margin-top: 1.5rem; }
252
+ .space-y-8 > * + * { margin-top: 2rem; }
253
+
254
+ /* ----------------------------------------
255
+ TEXT UTILITIES
256
+ ---------------------------------------- */
257
+ .text-primary { color: var(--color-primary-600); }
258
+ .text-success { color: var(--color-success-600); }
259
+ .text-error { color: var(--color-error-600); }
260
+ .text-warning { color: var(--color-warning-600); }
261
+ .text-info { color: var(--color-info-600); }
262
+ .text-muted { color: var(--color-ink-500); }
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Alert < Base
6
+ TYPES = {
7
+ success: "alert-success",
8
+ error: "alert-error",
9
+ warning: "alert-warning",
10
+ info: "alert-info"
11
+ }.freeze
12
+
13
+ def initialize(type: :info, **attrs)
14
+ @type = type
15
+ @attrs = attrs
16
+ end
17
+
18
+ def view_template(&)
19
+ div(class: alert_classes, role: "alert", **@attrs, &)
20
+ end
21
+
22
+ private
23
+
24
+ def alert_classes
25
+ classes("alert", TYPES[@type], @attrs[:class])
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Avatar < Base
6
+ SIZES = {
7
+ sm: "avatar-sm",
8
+ md: "avatar-md",
9
+ lg: "avatar-lg",
10
+ xl: "avatar-xl"
11
+ }.freeze
12
+
13
+ VARIANTS = {
14
+ primary: "avatar-primary",
15
+ neutral: "avatar-neutral"
16
+ }.freeze
17
+
18
+ def initialize(name: nil, src: nil, size: :md, variant: :primary, **attrs)
19
+ @name = name
20
+ @src = src
21
+ @size = size
22
+ @variant = variant
23
+ @attrs = attrs
24
+ end
25
+
26
+ def view_template
27
+ if @src
28
+ img(
29
+ src: @src,
30
+ alt: @name || "Avatar",
31
+ class: avatar_classes
32
+ )
33
+ else
34
+ div(class: avatar_classes) do
35
+ initials
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def avatar_classes
43
+ classes("avatar", SIZES[@size], VARIANTS[@variant], @attrs[:class])
44
+ end
45
+
46
+ def initials
47
+ return "" unless @name
48
+ @name.split.map { |part| part[0] }.take(2).join.upcase
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Badge < Base
6
+ VARIANTS = {
7
+ primary: "badge-primary",
8
+ success: "badge-success",
9
+ error: "badge-error",
10
+ warning: "badge-warning",
11
+ info: "badge-info",
12
+ neutral: "badge-neutral"
13
+ }.freeze
14
+
15
+ def initialize(variant: :neutral, **attrs)
16
+ @variant = variant
17
+ @attrs = attrs
18
+ end
19
+
20
+ def view_template(&)
21
+ span(class: badge_classes, **@attrs, &)
22
+ end
23
+
24
+ private
25
+
26
+ def badge_classes
27
+ classes("badge", VARIANTS[@variant], @attrs[:class])
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Brainzlab
6
+ module Components
7
+ class Base < Phlex::HTML
8
+ include Phlex::Rails::Helpers::Routes if defined?(Phlex::Rails::Helpers::Routes)
9
+
10
+ def classes(*args)
11
+ args.flatten.compact.join(" ")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Button < Base
6
+ VARIANTS = {
7
+ primary: "btn-primary",
8
+ secondary: "btn-secondary",
9
+ ghost: "btn-ghost",
10
+ danger: "btn-danger"
11
+ }.freeze
12
+
13
+ SIZES = {
14
+ sm: "btn-sm",
15
+ md: nil,
16
+ lg: "btn-lg"
17
+ }.freeze
18
+
19
+ def initialize(variant: :primary, size: :md, lift: false, disabled: false, type: "button", **attrs)
20
+ @variant = variant
21
+ @size = size
22
+ @lift = lift
23
+ @disabled = disabled
24
+ @type = type
25
+ @attrs = attrs
26
+ end
27
+
28
+ def view_template(&)
29
+ button(
30
+ type: @type,
31
+ class: button_classes,
32
+ disabled: @disabled,
33
+ **@attrs,
34
+ &
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ def button_classes
41
+ classes(
42
+ VARIANTS[@variant],
43
+ SIZES[@size],
44
+ @lift ? "btn-lift" : nil,
45
+ @attrs[:class]
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Card < Base
6
+ def initialize(interactive: false, lift: false, **attrs)
7
+ @interactive = interactive
8
+ @lift = lift
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template(&)
13
+ div(class: card_classes, **@attrs, &)
14
+ end
15
+
16
+ private
17
+
18
+ def card_classes
19
+ classes(
20
+ "card",
21
+ @interactive ? "card-interactive" : nil,
22
+ @lift ? "card-lift" : nil,
23
+ @attrs[:class]
24
+ )
25
+ end
26
+ end
27
+
28
+ class CardHeader < Base
29
+ def initialize(**attrs)
30
+ @attrs = attrs
31
+ end
32
+
33
+ def view_template(&)
34
+ div(class: classes("card-header", @attrs[:class]), **@attrs.except(:class), &)
35
+ end
36
+ end
37
+
38
+ class CardBody < Base
39
+ def initialize(**attrs)
40
+ @attrs = attrs
41
+ end
42
+
43
+ def view_template(&)
44
+ div(class: classes("card-body", @attrs[:class]), **@attrs.except(:class), &)
45
+ end
46
+ end
47
+
48
+ class CardFooter < Base
49
+ def initialize(**attrs)
50
+ @attrs = attrs
51
+ end
52
+
53
+ def view_template(&)
54
+ div(class: classes("card-footer", @attrs[:class]), **@attrs.except(:class), &)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class EmptyState < Base
6
+ def initialize(title:, description: nil, icon: nil, **attrs)
7
+ @title = title
8
+ @description = description
9
+ @icon = icon
10
+ @attrs = attrs
11
+ end
12
+
13
+ def view_template(&)
14
+ div(class: classes("empty-state", @attrs[:class]), **@attrs.except(:class)) do
15
+ if @icon
16
+ div(class: "empty-state-icon") { @icon }
17
+ end
18
+
19
+ h3(class: "empty-state-title") { @title }
20
+
21
+ if @description
22
+ p(class: "empty-state-description") { @description }
23
+ end
24
+
25
+ if block_given?
26
+ div(class: "mt-4") { yield }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Input < Base
6
+ def initialize(type: "text", error: false, **attrs)
7
+ @type = type
8
+ @error = error
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template
13
+ input(
14
+ type: @type,
15
+ class: input_classes,
16
+ **@attrs
17
+ )
18
+ end
19
+
20
+ private
21
+
22
+ def input_classes
23
+ classes(
24
+ "input",
25
+ @error ? "input-error" : nil,
26
+ @attrs[:class]
27
+ )
28
+ end
29
+ end
30
+
31
+ class Label < Base
32
+ def initialize(for_input: nil, **attrs)
33
+ @for_input = for_input
34
+ @attrs = attrs
35
+ end
36
+
37
+ def view_template(&)
38
+ label(
39
+ for: @for_input,
40
+ class: classes("label", @attrs[:class]),
41
+ **@attrs.except(:class),
42
+ &
43
+ )
44
+ end
45
+ end
46
+
47
+ class Select < Base
48
+ def initialize(error: false, **attrs)
49
+ @error = error
50
+ @attrs = attrs
51
+ end
52
+
53
+ def view_template(&)
54
+ select(
55
+ class: select_classes,
56
+ **@attrs,
57
+ &
58
+ )
59
+ end
60
+
61
+ private
62
+
63
+ def select_classes
64
+ classes(
65
+ "select",
66
+ @error ? "input-error" : nil,
67
+ @attrs[:class]
68
+ )
69
+ end
70
+ end
71
+
72
+ class Textarea < Base
73
+ def initialize(error: false, **attrs)
74
+ @error = error
75
+ @attrs = attrs
76
+ end
77
+
78
+ def view_template(&)
79
+ textarea(
80
+ class: textarea_classes,
81
+ **@attrs,
82
+ &
83
+ )
84
+ end
85
+
86
+ private
87
+
88
+ def textarea_classes
89
+ classes(
90
+ "textarea",
91
+ @error ? "input-error" : nil,
92
+ @attrs[:class]
93
+ )
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainzlab
4
+ module Components
5
+ class Modal < Base
6
+ SIZES = {
7
+ sm: "modal-sm",
8
+ md: "modal-md",
9
+ lg: "modal-lg",
10
+ xl: "modal-xl"
11
+ }.freeze
12
+
13
+ def initialize(size: :md, **attrs)
14
+ @size = size
15
+ @attrs = attrs
16
+ end
17
+
18
+ def view_template(&)
19
+ div(class: "modal-overlay", data: { modal_target: "overlay" })
20
+ div(class: modal_classes, role: "dialog", **@attrs, &)
21
+ end
22
+
23
+ private
24
+
25
+ def modal_classes
26
+ classes("modal", SIZES[@size], @attrs[:class])
27
+ end
28
+ end
29
+
30
+ class ModalHeader < Base
31
+ def initialize(**attrs)
32
+ @attrs = attrs
33
+ end
34
+
35
+ def view_template(&)
36
+ div(class: classes("modal-header", @attrs[:class]), **@attrs.except(:class), &)
37
+ end
38
+ end
39
+
40
+ class ModalBody < Base
41
+ def initialize(**attrs)
42
+ @attrs = attrs
43
+ end
44
+
45
+ def view_template(&)
46
+ div(class: classes("modal-body", @attrs[:class]), **@attrs.except(:class), &)
47
+ end
48
+ end
49
+
50
+ class ModalFooter < Base
51
+ def initialize(**attrs)
52
+ @attrs = attrs
53
+ end
54
+
55
+ def view_template(&)
56
+ div(class: classes("modal-footer", @attrs[:class]), **@attrs.except(:class), &)
57
+ end
58
+ end
59
+ end
60
+ end