panda-core 0.2.3 → 0.4.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +185 -0
  3. data/app/assets/tailwind/application.css +279 -0
  4. data/app/assets/tailwind/tailwind.config.js +21 -0
  5. data/app/components/panda/core/UI/badge.rb +107 -0
  6. data/app/components/panda/core/UI/button.rb +89 -0
  7. data/app/components/panda/core/UI/card.rb +88 -0
  8. data/app/components/panda/core/admin/button_component.rb +46 -28
  9. data/app/components/panda/core/admin/container_component.rb +52 -4
  10. data/app/components/panda/core/admin/flash_message_component.rb +74 -9
  11. data/app/components/panda/core/admin/form_error_component.rb +48 -0
  12. data/app/components/panda/core/admin/form_input_component.rb +50 -0
  13. data/app/components/panda/core/admin/form_select_component.rb +68 -0
  14. data/app/components/panda/core/admin/heading_component.rb +52 -24
  15. data/app/components/panda/core/admin/panel_component.rb +33 -4
  16. data/app/components/panda/core/admin/slideover_component.rb +8 -4
  17. data/app/components/panda/core/admin/statistics_component.rb +19 -0
  18. data/app/components/panda/core/admin/tab_bar_component.rb +101 -0
  19. data/app/components/panda/core/admin/table_component.rb +90 -9
  20. data/app/components/panda/core/admin/tag_component.rb +21 -16
  21. data/app/components/panda/core/admin/user_activity_component.rb +43 -0
  22. data/app/components/panda/core/admin/user_display_component.rb +78 -0
  23. data/app/components/panda/core/base.rb +122 -0
  24. data/app/controllers/panda/core/admin/base_controller.rb +68 -0
  25. data/app/controllers/panda/core/admin/dashboard_controller.rb +7 -6
  26. data/app/controllers/panda/core/admin/my_profile_controller.rb +3 -3
  27. data/app/controllers/panda/core/admin/sessions_controller.rb +26 -5
  28. data/app/helpers/panda/core/sessions_helper.rb +21 -0
  29. data/app/javascript/panda/core/application.js +1 -0
  30. data/app/javascript/panda/core/vendor/@hotwired--stimulus.js +4 -0
  31. data/app/javascript/panda/core/vendor/@hotwired--turbo.js +160 -0
  32. data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +4 -0
  33. data/app/models/panda/core/user.rb +17 -13
  34. data/app/views/layouts/panda/core/admin.html.erb +40 -57
  35. data/app/views/layouts/panda/core/admin_simple.html.erb +5 -0
  36. data/app/views/panda/core/admin/dashboard/_default_content.html.erb +73 -0
  37. data/app/views/panda/core/admin/dashboard/show.html.erb +4 -10
  38. data/app/views/panda/core/admin/my_profile/edit.html.erb +13 -27
  39. data/app/views/panda/core/admin/sessions/new.html.erb +13 -12
  40. data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +27 -34
  41. data/app/views/panda/core/admin/shared/_flash.html.erb +4 -30
  42. data/app/views/panda/core/admin/shared/_sidebar.html.erb +36 -20
  43. data/app/views/panda/core/shared/_footer.html.erb +2 -0
  44. data/app/views/panda/core/shared/_header.html.erb +19 -0
  45. data/config/importmap.rb +15 -0
  46. data/config/initializers/panda_core.rb +37 -1
  47. data/config/routes.rb +7 -7
  48. data/db/migrate/20250810120000_add_current_theme_to_panda_core_users.rb +7 -0
  49. data/lib/generators/panda/core/install_generator.rb +3 -9
  50. data/lib/generators/panda/core/templates/README +25 -0
  51. data/lib/generators/panda/core/templates/initializer.rb +28 -0
  52. data/lib/panda/core/asset_loader.rb +23 -8
  53. data/lib/panda/core/configuration.rb +41 -9
  54. data/lib/panda/core/debug.rb +47 -0
  55. data/lib/panda/core/engine.rb +82 -8
  56. data/lib/panda/core/version.rb +1 -1
  57. data/lib/panda/core.rb +1 -0
  58. data/lib/tasks/assets.rake +58 -392
  59. data/lib/tasks/panda_core_tasks.rake +16 -0
  60. metadata +102 -14
  61. data/app/components/panda/core/admin/container_component.html.erb +0 -12
  62. data/app/components/panda/core/admin/flash_message_component.html.erb +0 -31
  63. data/app/components/panda/core/admin/panel_component.html.erb +0 -7
  64. data/app/components/panda/core/admin/slideover_component.html.erb +0 -9
  65. data/app/components/panda/core/admin/table_component.html.erb +0 -29
  66. data/app/controllers/panda/core/admin_controller.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 348ffbfa5cfbd2fc7fcb605670dd7ecb3914eddd6c076984d94d689d050e44fe
4
- data.tar.gz: 50f977fc4f31605d4b985f9f37d3167b239a4b1627e2aaedb9638e10d7bd0762
3
+ metadata.gz: 1571548de89ff0ad16010f4500ed47a1c53dda70c730945fa439272a33567999
4
+ data.tar.gz: 02cc839e0831b886a37729b43a5d185d1bd7a13eb3ff59dd2e3afc0f7996e4cd
5
5
  SHA512:
6
- metadata.gz: 077e81f9825f43dd1e65425fca186277ba082bab5b4e361a746dba2bbdf3224bdca7db055db47504ca6f4d345962129f58ba3b090557992472689a6e57f90f5a
7
- data.tar.gz: 3be894b015597b1f06a12452398eef7c03824de92e553b1d8e960f161407ee9bf3d5cd6df43e57ea5eb7075cb4b16ac38bdeae6010f30fe11e962467e45b92d6
6
+ metadata.gz: acaebae64c9e8b970edda07e35f0b3820e988d3c738c3cf0c4d9c647be828d98773992d6a1ab70a4dc74cc01edaa04582a43c0fa285222a2e2d8169afaba7ac0
7
+ data.tar.gz: 927db64e6429b4687667e8e47b185abea96e1f50668b5e6abe2d36050a1606aac880610f94d2f08205fb4a1280e3c95f05f8c45a8afc1d03ca10a38fad0abdd5
data/README.md CHANGED
@@ -59,6 +59,191 @@ The gems listed in the `panda-core.gemspec` will be added to your Gemfile (or, i
59
59
 
60
60
  Make sure to follow the setup instructions for each of these gems.
61
61
 
62
+ ## Configuration
63
+
64
+ Panda Core is configured in `config/initializers/panda.rb`. The install generator creates this file with a complete default configuration:
65
+
66
+ ```ruby
67
+ # config/initializers/panda.rb
68
+ Panda::Core.configure do |config|
69
+ config.admin_path = "/admin"
70
+
71
+ config.login_page_title = "Panda Admin"
72
+ config.admin_title = "Panda Admin"
73
+
74
+ config.authentication_providers = {
75
+ google_oauth2: {
76
+ enabled: true,
77
+ name: "Google",
78
+ client_id: Rails.application.credentials.dig(:google, :client_id),
79
+ client_secret: Rails.application.credentials.dig(:google, :client_secret),
80
+ options: {
81
+ scope: "email,profile",
82
+ prompt: "select_account",
83
+ hd: "yourdomain.com" # Restrict to specific domain
84
+ }
85
+ }
86
+ }
87
+
88
+ # Core settings
89
+ config.session_token_cookie = :panda_session
90
+ config.user_class = "Panda::Core::User"
91
+ config.user_identity_class = "Panda::Core::UserIdentity"
92
+ config.storage_provider = :active_storage
93
+ config.cache_store = :memory_store
94
+
95
+ # Optional editor configuration
96
+ # config.editor_js_tools = []
97
+ # config.editor_js_tool_config = {}
98
+ end
99
+ ```
100
+
101
+ ### Authentication Providers
102
+
103
+ Panda Core supports multiple OAuth providers. Configure your credentials in Rails credentials:
104
+
105
+ ```yaml
106
+ # config/credentials.yml.enc
107
+ google:
108
+ client_id: "your-google-client-id"
109
+ client_secret: "your-google-client-secret"
110
+
111
+ microsoft:
112
+ client_id: "your-microsoft-client-id"
113
+ client_secret: "your-microsoft-client-secret"
114
+ ```
115
+
116
+ Then reference them in your initializer. You can enable multiple providers simultaneously.
117
+
118
+ ### Admin Path
119
+
120
+ The `admin_path` setting controls where the admin interface is mounted (default: `/admin`). You can customize this to avoid route conflicts or match your preferences:
121
+
122
+ ```ruby
123
+ config.admin_path = "/manage" # Custom admin path
124
+ ```
125
+
126
+ This is useful when:
127
+ - You want to avoid conflicts with existing routes
128
+ - You prefer a different URL structure
129
+ - You're running multiple admin interfaces
130
+ - You need different admin paths per environment
131
+
132
+ ### Asset Pipeline
133
+
134
+ Panda Core provides the **single source of truth** for all Panda admin interface styling. This includes:
135
+
136
+ - Base Tailwind CSS utilities and theme system
137
+ - EditorJS content styles (used by Panda CMS)
138
+ - Admin component styles
139
+ - Form styles and typography
140
+
141
+ **Asset Compilation**
142
+
143
+ Panda Core uses Tailwind CSS v4 to compile all admin interface styling.
144
+
145
+ **Quick Start** - Compile full CSS (Core + CMS):
146
+
147
+ ```bash
148
+ cd /path/to/panda-core
149
+
150
+ bundle exec tailwindcss -i app/assets/tailwind/application.css \
151
+ -o public/panda-core-assets/panda-core.css \
152
+ --content '../cms/app/views/**/*.erb' \
153
+ --content '../cms/app/components/**/*.rb' \
154
+ --content '../cms/app/javascript/**/*.js' \
155
+ --content 'app/views/**/*.erb' \
156
+ --content 'app/components/**/*.rb' \
157
+ --minify
158
+ ```
159
+
160
+ **Result**: `panda-core.css` (~37KB) with all utility classes
161
+
162
+ **Important**: Always compile with full content before committing!
163
+
164
+ For complete documentation on:
165
+ - Development workflows (Core-only vs full stack)
166
+ - Release processes
167
+ - Troubleshooting
168
+ - Best practices
169
+
170
+ See **[docs/ASSET_COMPILATION.md](docs/ASSET_COMPILATION.md)**
171
+
172
+ **How Asset Serving Works:**
173
+
174
+ Core's Rack middleware serves `/panda-core-assets/` from the gem's `public/` directory:
175
+
176
+ ```ruby
177
+ # In Core's engine.rb
178
+ config.app_middleware.use(Rack::Static,
179
+ urls: ["/panda-core-assets"],
180
+ root: Panda::Core::Engine.root.join("public")
181
+ )
182
+ ```
183
+
184
+ This means:
185
+ - ✅ CMS and other gems automatically load CSS from Core
186
+ - ✅ No copying needed - served directly from gem
187
+ - ✅ Version changes are instant (just restart server)
188
+ - ✅ Single source of truth for all admin styling
189
+
190
+ **What Gets Compiled:**
191
+
192
+ The compilation process:
193
+ - Scans all Core and CMS views/components for Tailwind classes (via `tailwind.config.js`)
194
+ - Includes EditorJS content styles for rich text editing
195
+ - Applies theme variables for default and sky themes
196
+ - Outputs a single minified CSS file (~37KB)
197
+
198
+ **Asset Serving**
199
+
200
+ Panda Core automatically serves compiled assets from `/panda-core-assets/` using Rack::Static middleware configured in the engine.
201
+
202
+ **How Panda CMS Uses Core Styling**
203
+
204
+ Panda CMS depends on Panda Core and loads its CSS automatically:
205
+
206
+ ```erb
207
+ <!-- In CMS views -->
208
+ <link rel="stylesheet" href="/panda-core-assets/panda-core.css">
209
+ ```
210
+
211
+ CMS no longer compiles its own CSS - all styling comes from Core.
212
+
213
+ **Content Scanning**
214
+
215
+ The `tailwind.config.js` file configures content scanning to include:
216
+ - Core views: `../../app/views/**/*.html.erb`
217
+ - Core components: `../../app/components/**/*.rb`
218
+ - CMS views: `../../../cms/app/views/**/*.html.erb`
219
+ - CMS components: `../../../cms/app/components/**/*.rb`
220
+ - CMS JavaScript: `../../../cms/app/javascript/**/*.js`
221
+
222
+ This ensures all Tailwind classes used across the Panda ecosystem are included in the compiled CSS.
223
+
224
+ **Customizing Styles**
225
+
226
+ To customize the admin interface styles:
227
+
228
+ 1. Edit `app/assets/tailwind/application.css` in panda-core
229
+ 2. Add custom CSS in the appropriate `@layer` (base, components, or utilities)
230
+ 3. Recompile: `bundle exec tailwindcss -i app/assets/tailwind/application.css -o public/panda-core-assets/panda-core.css --minify`
231
+ 4. Copy the updated CSS to test locations if needed
232
+ 5. Restart your Rails server to see changes
233
+
234
+ **Theme System**
235
+
236
+ Panda Core provides two built-in themes accessible via `data-theme` attribute:
237
+
238
+ - `default`: Purple/pink color scheme
239
+ - `sky`: Blue color scheme
240
+
241
+ Themes use CSS custom properties that can be referenced in your styles:
242
+ - `--color-white`, `--color-black`
243
+ - `--color-light`, `--color-mid`, `--color-dark`
244
+ - `--color-highlight`, `--color-active`, `--color-inactive`
245
+ - `--color-warning`, `--color-error`
246
+
62
247
  ## Development
63
248
 
64
249
  After checking out the repo:
@@ -0,0 +1,279 @@
1
+ @import 'tailwindcss';
2
+
3
+ @source "../../app/views/**/*.html.erb";
4
+ @source "../../app/components/**/*.html.erb";
5
+ @source "../../app/components/**/*.rb";
6
+ @source "../../app/helpers/**/*.rb";
7
+ @source "../../../cms/app/views/**/*.html.erb";
8
+ @source "../../../cms/app/components/**/*.html.erb";
9
+ @source "../../../cms/app/components/**/*.rb";
10
+
11
+ @theme {
12
+ --color-white: var(--color-white);
13
+ --color-black: var(--color-black);
14
+ --color-light: var(--color-light);
15
+ --color-mid: var(--color-mid);
16
+ --color-dark: var(--color-dark);
17
+ --color-highlight: var(--color-highlight);
18
+ --color-active: var(--color-active);
19
+ --color-inactive: var(--color-inactive);
20
+ --color-warning: var(--color-warning);
21
+ --color-error: var(--color-error);
22
+ }
23
+
24
+ @layer base {
25
+ html[data-theme='default'] {
26
+ --color-white: rgb(249, 249, 249); /* #F9F9F9 */
27
+ --color-black: rgb(26, 22, 29); /* #1A161D */
28
+
29
+ --color-light: rgb(238, 206, 230); /* #EECEE6 */
30
+ --color-mid: rgb(141, 94, 183); /* #8D5EB7 */
31
+ --color-dark: rgb(33, 29, 73); /* #211D49 */
32
+
33
+ --color-highlight: rgb(208, 64, 20); /* #D04014 */
34
+
35
+ --color-active: rgb(0, 135, 85); /* #008755 */
36
+ --color-warning: rgb(250, 207, 142); /* #FACF8E */
37
+ --color-inactive: rgb(216, 247, 245); /* #d6e4f7 */
38
+ --color-error: rgb(245, 129, 129); /* #F58181 */
39
+ }
40
+
41
+ html[data-theme='sky'] {
42
+ --color-white: rgb(249, 249, 249); /* #F9F9F9 */
43
+ --color-black: rgb(26, 22, 29); /* #1A161D */
44
+ --color-light: rgb(204, 238, 242); /* #CCEEF2 */
45
+ --color-mid: rgb(42, 102, 159); /* #2A669F */
46
+ --color-dark: rgb(20, 32, 74); /* #14204A */
47
+ --color-highlight: rgb(208, 64, 20); /* #D04014 */
48
+
49
+ --color-active: rgb(69, 154, 89); /* #459A59 - darker green with better contrast */
50
+ --color-warning: rgb(244, 190, 102); /* #F4BE66 */
51
+ --color-inactive: rgb(216, 247, 245); /* #d6e4f7 */
52
+ --color-error: rgb(208, 64, 20); /* #D04014 */
53
+ }
54
+
55
+ a.block-link:after {
56
+ position: absolute;
57
+ content: '';
58
+ inset: 0;
59
+ }
60
+
61
+ /* Admin gradient backgrounds */
62
+ html[data-theme='default'] .bg-gradient-admin {
63
+ background: linear-gradient(to bottom right, rgb(33, 29, 73), rgb(141, 94, 183));
64
+ }
65
+
66
+ html[data-theme='sky'] .bg-gradient-admin {
67
+ background: linear-gradient(to bottom right, rgb(20, 32, 74), rgb(42, 102, 159));
68
+ }
69
+ }
70
+
71
+ /* Form input styles */
72
+ @layer components {
73
+ /* Base form field styles - matches panda-cms admin pattern */
74
+ input[type='text'],
75
+ input[type='email'],
76
+ input[type='password'],
77
+ input[type='url'],
78
+ input[type='tel'],
79
+ input[type='number'],
80
+ input[type='date'],
81
+ input[type='datetime-local'],
82
+ input[type='month'],
83
+ input[type='week'],
84
+ input[type='time'],
85
+ input[type='search'],
86
+ textarea {
87
+ @apply block w-full rounded-md border-0 p-2 text-gray-900 bg-white;
88
+ @apply ring-1 ring-inset ring-mid placeholder:text-gray-300;
89
+ @apply focus:ring-1 focus:ring-inset focus:ring-dark;
90
+ @apply hover:cursor-pointer sm:leading-6;
91
+ @apply disabled:ring-gray-300 disabled:focus:ring-gray-300 disabled:bg-gray-50 disabled:cursor-not-allowed;
92
+ }
93
+
94
+ /* Select specific styling - matches panda-cms admin pattern */
95
+ select {
96
+ @apply block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 bg-white;
97
+ @apply ring-1 ring-inset ring-mid;
98
+ @apply focus:ring-1 focus:ring-inset focus:ring-dark;
99
+ @apply hover:cursor-pointer sm:leading-6;
100
+ @apply disabled:ring-gray-300 disabled:focus:ring-gray-300 disabled:bg-gray-50 disabled:cursor-not-allowed;
101
+ @apply appearance-none bg-right bg-no-repeat;
102
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
103
+ background-position: right 0.5rem center;
104
+ background-size: 1.5em 1.5em;
105
+ }
106
+
107
+ /* Checkbox and radio styling */
108
+ input[type='checkbox'],
109
+ input[type='radio'] {
110
+ @apply w-4 h-4 text-indigo-600 bg-white border-gray-300 rounded transition-colors duration-200;
111
+ @apply focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2;
112
+ @apply disabled:bg-gray-100 disabled:cursor-not-allowed;
113
+ }
114
+
115
+ input[type='radio'] {
116
+ @apply rounded-full;
117
+ }
118
+
119
+ /* Label styling */
120
+ label {
121
+ @apply block text-sm font-medium text-gray-700 mb-1;
122
+ }
123
+
124
+ /* Field wrapper styling */
125
+ .field {
126
+ @apply mb-4;
127
+ }
128
+
129
+ /* Error state styling */
130
+ input.error,
131
+ textarea.error,
132
+ select.error {
133
+ @apply ring-red-500 focus:ring-red-500;
134
+ }
135
+
136
+ .field-error {
137
+ @apply text-sm text-red-600 mt-1;
138
+ }
139
+
140
+ /* Button styling */
141
+ .btn {
142
+ @apply inline-flex items-center justify-center px-6 py-3 text-base font-semibold rounded-md transition-colors duration-200;
143
+ @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
144
+ @apply disabled:opacity-50 disabled:cursor-not-allowed;
145
+ }
146
+
147
+ .btn-primary {
148
+ @apply bg-mid text-white hover:opacity-90 focus:ring-mid;
149
+ }
150
+
151
+ .btn-secondary {
152
+ @apply bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500;
153
+ }
154
+
155
+ .btn-danger {
156
+ @apply bg-red-600 text-white hover:bg-red-700 focus:ring-red-500;
157
+ }
158
+ }
159
+
160
+ /* EditorJS content styles */
161
+ @layer components {
162
+ .codex-editor__redactor .ce-block .ce-block__content {
163
+ @apply text-base font-normal font-sans text-dark leading-[1.6] space-y-[1.6rem];
164
+
165
+ h1.ce-header {
166
+ @apply text-3xl md:text-4xl font-semibold font-sans leading-[1.2];
167
+ }
168
+
169
+ h2.ce-header {
170
+ @apply text-2xl font-medium font-sans leading-[1.3] mb-4 mt-8;
171
+ }
172
+
173
+ h3.ce-header {
174
+ @apply text-xl font-normal font-sans leading-[1.3] mb-4 mt-6;
175
+ }
176
+
177
+ p,
178
+ li {
179
+ @apply leading-[1.6] tracking-wide max-w-[85ch];
180
+
181
+ a {
182
+ @apply text-[#1A9597] underline underline-offset-2 hover:text-[#158486] focus:outline-2 focus:outline-offset-2 focus:outline-[#1A9597];
183
+ }
184
+
185
+ strong,
186
+ b {
187
+ @apply font-semibold;
188
+ }
189
+ }
190
+
191
+ p {
192
+ @apply mb-4;
193
+ }
194
+
195
+ .cdx-quote {
196
+ @apply bg-[#eef0f3] border-l-inactive border-l-8 p-6 mb-4;
197
+
198
+ .cdx-quote__caption {
199
+ @apply block ml-6 mt-2 text-sm text-dark;
200
+ }
201
+
202
+ .cdx-quote__text {
203
+ quotes: '\201C' '\201D' '\2018' '\2019';
204
+ @apply pl-6;
205
+
206
+ &:before {
207
+ @apply -ml-8 mr-2 text-dark text-6xl leading-4 align-text-bottom font-serif;
208
+ content: open-quote;
209
+ }
210
+
211
+ p {
212
+ @apply inline italic text-lg;
213
+ }
214
+ }
215
+ }
216
+
217
+ .cdx-list {
218
+ @apply mb-4 pl-6;
219
+
220
+ &--ordered {
221
+ @apply list-decimal;
222
+ }
223
+
224
+ &--unordered {
225
+ @apply list-disc;
226
+ }
227
+
228
+ .cdx-list {
229
+ @apply mt-2 mb-0;
230
+ }
231
+
232
+ .cdx-list__item {
233
+ @apply mb-2 pl-2;
234
+ }
235
+ }
236
+
237
+ .cdx-nested-list {
238
+ @apply mb-4 pl-6;
239
+
240
+ &--ordered {
241
+ @apply list-decimal;
242
+ }
243
+
244
+ &--unordered {
245
+ @apply list-disc;
246
+ }
247
+
248
+ .cdx-nested-list {
249
+ @apply mt-2 mb-0;
250
+ }
251
+
252
+ .cdx-nested-list__item {
253
+ @apply mb-2 pl-2;
254
+ }
255
+ }
256
+
257
+ .cdx-table {
258
+ @apply w-full border-collapse border-2 border-dark my-6;
259
+
260
+ &__head {
261
+ @apply font-semibold border-dark border-r-2 p-3 bg-light;
262
+ }
263
+
264
+ &__row {
265
+ @apply border-dark border-b-2;
266
+ }
267
+
268
+ &__cell {
269
+ @apply border-dark border-r-2 p-3;
270
+ }
271
+ }
272
+
273
+ .cdx-embed {
274
+ iframe {
275
+ @apply w-full border-none;
276
+ }
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,21 @@
1
+ module.exports = {
2
+ content: {
3
+ relative: true,
4
+ files: [
5
+ // Panda Core views and components
6
+ "../../app/views/**/*.html.erb",
7
+ "../../app/components/**/*.html.erb",
8
+ "../../app/components/**/*.rb",
9
+ "../../app/helpers/**/*.rb",
10
+
11
+ // Panda CMS views and components (for compilation)
12
+ "../../../cms/app/views/**/*.html.erb",
13
+ "../../../cms/app/builders/**/*.rb",
14
+ "../../../cms/app/components/**/*.html.erb",
15
+ "../../../cms/app/components/**/*.rb",
16
+ "../../../cms/app/helpers/**/*.rb",
17
+ "../../../cms/app/javascript/**/*.js",
18
+ "../../../cms/vendor/javascript/**/*.js",
19
+ ],
20
+ },
21
+ };
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module UI
6
+ # Badge component for status indicators, labels, and counts.
7
+ #
8
+ # Badges are small, inline elements that highlight an item's status
9
+ # or provide additional metadata at a glance.
10
+ #
11
+ # @example Basic badge
12
+ # render Panda::Core::UI::Badge.new(text: "New")
13
+ #
14
+ # @example Status badges
15
+ # render Panda::Core::UI::Badge.new(text: "Active", variant: :success)
16
+ # render Panda::Core::UI::Badge.new(text: "Pending", variant: :warning)
17
+ # render Panda::Core::UI::Badge.new(text: "Error", variant: :danger)
18
+ #
19
+ # @example With count
20
+ # render Panda::Core::UI::Badge.new(text: "99+", variant: :primary, size: :small)
21
+ #
22
+ # @example Removable badge
23
+ # render Panda::Core::UI::Badge.new(
24
+ # text: "Tag",
25
+ # removable: true,
26
+ # data: { action: "click->tags#remove" }
27
+ # )
28
+ #
29
+ class Badge < Panda::Core::Base
30
+ prop :text, String
31
+ prop :variant, Symbol, default: :default
32
+ prop :size, Symbol, default: :medium
33
+ prop :removable, _Boolean, default: false
34
+ prop :rounded, _Boolean, default: false
35
+
36
+ def view_template
37
+ span(**@attrs) do
38
+ plain text
39
+ if removable
40
+ whitespace
41
+ button(
42
+ type: "button",
43
+ class: "inline-flex items-center ml-1 hover:opacity-70",
44
+ aria: {label: "Remove"}
45
+ ) do
46
+ svg(
47
+ class: "h-3 w-3",
48
+ xmlns: "http://www.w3.org/2000/svg",
49
+ viewBox: "0 0 20 20",
50
+ fill: "currentColor"
51
+ ) do |s|
52
+ s.path(
53
+ d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def default_attrs
62
+ {
63
+ class: badge_classes
64
+ }
65
+ end
66
+
67
+ private
68
+
69
+ def badge_classes
70
+ base = "inline-flex items-center font-medium"
71
+ base += " #{size_classes}"
72
+ base += " #{variant_classes}"
73
+ base += rounded ? " rounded-full" : " rounded"
74
+ base
75
+ end
76
+
77
+ def size_classes
78
+ case size
79
+ when :small, :sm
80
+ "px-2 py-0.5 text-xs"
81
+ when :large, :lg
82
+ "px-3 py-1 text-base"
83
+ else # :medium, :md
84
+ "px-2.5 py-0.5 text-sm"
85
+ end
86
+ end
87
+
88
+ def variant_classes
89
+ case variant
90
+ when :primary
91
+ "bg-blue-50 text-blue-700 ring-1 ring-inset ring-blue-700/10"
92
+ when :success
93
+ "bg-green-50 text-green-700 ring-1 ring-inset ring-green-600/20"
94
+ when :warning
95
+ "bg-yellow-50 text-yellow-800 ring-1 ring-inset ring-yellow-600/20"
96
+ when :danger
97
+ "bg-red-50 text-red-700 ring-1 ring-inset ring-red-600/10"
98
+ when :info
99
+ "bg-sky-50 text-sky-700 ring-1 ring-inset ring-sky-700/10"
100
+ else # :default
101
+ "bg-gray-50 text-gray-600 ring-1 ring-inset ring-gray-500/10"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module UI
6
+ # Modern Phlex-based button component with type-safe props.
7
+ #
8
+ # This component demonstrates the recommended pattern for building
9
+ # Phlex components in the Panda ecosystem using the shared base class.
10
+ #
11
+ # @example Basic usage
12
+ # render Panda::Core::UI::Button.new(text: "Click me")
13
+ #
14
+ # @example With variant
15
+ # render Panda::Core::UI::Button.new(
16
+ # text: "Delete",
17
+ # variant: :danger,
18
+ # size: :large
19
+ # )
20
+ #
21
+ # @example With custom attributes
22
+ # render Panda::Core::UI::Button.new(
23
+ # text: "Submit",
24
+ # variant: :primary,
25
+ # class: "mt-4",
26
+ # data: { turbo_method: :post }
27
+ # )
28
+ #
29
+ class Button < Panda::Core::Base
30
+ # Type-safe properties using Literal
31
+ prop :text, String
32
+ prop :variant, Symbol, default: :default
33
+ prop :size, Symbol, default: :medium
34
+ prop :disabled, _Boolean, default: false
35
+ prop :type, String, default: "button"
36
+
37
+ def view_template
38
+ button(**@attrs) { text }
39
+ end
40
+
41
+ def default_attrs
42
+ {
43
+ type: type,
44
+ disabled: disabled,
45
+ class: button_classes
46
+ }
47
+ end
48
+
49
+ private
50
+
51
+ def button_classes
52
+ base = "inline-flex items-center rounded-md font-medium shadow-sm transition-colors"
53
+ base += " #{size_classes}"
54
+ base += " #{variant_classes}"
55
+ base += " disabled:opacity-50 disabled:cursor-not-allowed" if disabled
56
+ base
57
+ end
58
+
59
+ def size_classes
60
+ case size
61
+ when :small, :sm
62
+ "gap-x-1.5 px-2.5 py-1.5 text-sm"
63
+ when :large, :lg
64
+ "gap-x-2 px-3.5 py-2.5 text-lg"
65
+ else # :medium, :md
66
+ "gap-x-1.5 px-3 py-2 text-base"
67
+ end
68
+ end
69
+
70
+ def variant_classes
71
+ case variant
72
+ when :primary
73
+ "bg-blue-600 text-white hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
74
+ when :secondary
75
+ "bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
76
+ when :success
77
+ "bg-green-600 text-white hover:bg-green-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
78
+ when :danger
79
+ "bg-red-600 text-white hover:bg-red-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
80
+ when :ghost
81
+ "bg-transparent text-gray-700 hover:bg-gray-100"
82
+ else # :default
83
+ "bg-gray-700 text-white hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-700"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end