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.
- checksums.yaml +4 -4
- data/README.md +185 -0
- data/app/assets/tailwind/application.css +279 -0
- data/app/assets/tailwind/tailwind.config.js +21 -0
- data/app/components/panda/core/UI/badge.rb +107 -0
- data/app/components/panda/core/UI/button.rb +89 -0
- data/app/components/panda/core/UI/card.rb +88 -0
- data/app/components/panda/core/admin/button_component.rb +46 -28
- data/app/components/panda/core/admin/container_component.rb +52 -4
- data/app/components/panda/core/admin/flash_message_component.rb +74 -9
- data/app/components/panda/core/admin/form_error_component.rb +48 -0
- data/app/components/panda/core/admin/form_input_component.rb +50 -0
- data/app/components/panda/core/admin/form_select_component.rb +68 -0
- data/app/components/panda/core/admin/heading_component.rb +52 -24
- data/app/components/panda/core/admin/panel_component.rb +33 -4
- data/app/components/panda/core/admin/slideover_component.rb +8 -4
- data/app/components/panda/core/admin/statistics_component.rb +19 -0
- data/app/components/panda/core/admin/tab_bar_component.rb +101 -0
- data/app/components/panda/core/admin/table_component.rb +90 -9
- data/app/components/panda/core/admin/tag_component.rb +21 -16
- data/app/components/panda/core/admin/user_activity_component.rb +43 -0
- data/app/components/panda/core/admin/user_display_component.rb +78 -0
- data/app/components/panda/core/base.rb +122 -0
- data/app/controllers/panda/core/admin/base_controller.rb +68 -0
- data/app/controllers/panda/core/admin/dashboard_controller.rb +7 -6
- data/app/controllers/panda/core/admin/my_profile_controller.rb +3 -3
- data/app/controllers/panda/core/admin/sessions_controller.rb +26 -5
- data/app/helpers/panda/core/sessions_helper.rb +21 -0
- data/app/javascript/panda/core/application.js +1 -0
- data/app/javascript/panda/core/vendor/@hotwired--stimulus.js +4 -0
- data/app/javascript/panda/core/vendor/@hotwired--turbo.js +160 -0
- data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +4 -0
- data/app/models/panda/core/user.rb +17 -13
- data/app/views/layouts/panda/core/admin.html.erb +40 -57
- data/app/views/layouts/panda/core/admin_simple.html.erb +5 -0
- data/app/views/panda/core/admin/dashboard/_default_content.html.erb +73 -0
- data/app/views/panda/core/admin/dashboard/show.html.erb +4 -10
- data/app/views/panda/core/admin/my_profile/edit.html.erb +13 -27
- data/app/views/panda/core/admin/sessions/new.html.erb +13 -12
- data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +27 -34
- data/app/views/panda/core/admin/shared/_flash.html.erb +4 -30
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +36 -20
- data/app/views/panda/core/shared/_footer.html.erb +2 -0
- data/app/views/panda/core/shared/_header.html.erb +19 -0
- data/config/importmap.rb +15 -0
- data/config/initializers/panda_core.rb +37 -1
- data/config/routes.rb +7 -7
- data/db/migrate/20250810120000_add_current_theme_to_panda_core_users.rb +7 -0
- data/lib/generators/panda/core/install_generator.rb +3 -9
- data/lib/generators/panda/core/templates/README +25 -0
- data/lib/generators/panda/core/templates/initializer.rb +28 -0
- data/lib/panda/core/asset_loader.rb +23 -8
- data/lib/panda/core/configuration.rb +41 -9
- data/lib/panda/core/debug.rb +47 -0
- data/lib/panda/core/engine.rb +82 -8
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core.rb +1 -0
- data/lib/tasks/assets.rake +58 -392
- data/lib/tasks/panda_core_tasks.rake +16 -0
- metadata +102 -14
- data/app/components/panda/core/admin/container_component.html.erb +0 -12
- data/app/components/panda/core/admin/flash_message_component.html.erb +0 -31
- data/app/components/panda/core/admin/panel_component.html.erb +0 -7
- data/app/components/panda/core/admin/slideover_component.html.erb +0 -9
- data/app/components/panda/core/admin/table_component.html.erb +0 -29
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1571548de89ff0ad16010f4500ed47a1c53dda70c730945fa439272a33567999
|
|
4
|
+
data.tar.gz: 02cc839e0831b886a37729b43a5d185d1bd7a13eb3ff59dd2e3afc0f7996e4cd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|