@keenmate/pure-admin-core 2.0.1 → 2.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.
Files changed (66) hide show
  1. package/README.md +65 -9
  2. package/dist/css/main.css +366 -83
  3. package/package.json +6 -4
  4. package/scripts/download-themes.js +268 -0
  5. package/scripts/pack-theme.js +2 -2
  6. package/snippets/badges.html +4 -4
  7. package/snippets/buttons.html +64 -0
  8. package/snippets/tooltips.html +36 -36
  9. package/src/scss/_core.scss +3 -0
  10. package/src/scss/core-components/_buttons.scss +97 -0
  11. package/src/scss/core-components/_cards.scss +13 -3
  12. package/src/scss/core-components/_data-display.scss +35 -2
  13. package/src/scss/core-components/_filter-card.scss +58 -0
  14. package/src/scss/core-components/_grid.scss +9 -0
  15. package/src/scss/core-components/_tables.scss +4 -0
  16. package/src/scss/core-components/_timeline.scss +2 -2
  17. package/src/scss/core-components/_tooltips.scss +110 -54
  18. package/src/scss/core-components/badges/_badge-base.scss +14 -3
  19. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8UH-qVHQ.woff2 +0 -0
  20. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8UH-qVHQ.woff2.1 +0 -0
  21. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8UH-qVHQ.woff2.2 +0 -0
  22. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8VH-qVHQ.woff2 +0 -0
  23. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8VH-qVHQ.woff2.1 +0 -0
  24. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8VH-qVHQ.woff2.2 +0 -0
  25. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8bH-o.woff2 +0 -0
  26. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8bH-o.woff2.1 +0 -0
  27. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8fH-qVHQ.woff2 +0 -0
  28. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8fH-qVHQ.woff2.1 +0 -0
  29. package/dist/fonts/google/3y976aknfjLm_3lMKjiMgmUUYBs04Y8fH-qVHQ.woff2.2 +0 -0
  30. package/dist/fonts/google/6aez4K2oVqwIvtE2H68T.woff2 +0 -0
  31. package/dist/fonts/google/6aez4K2oVqwIvtU2Hw.woff2 +0 -0
  32. package/dist/fonts/google/6aez4K2oVqwIvtY2H68T.woff2 +0 -0
  33. package/dist/fonts/google/6aez4K2oVqwIvtg2H68T.woff2 +0 -0
  34. package/dist/fonts/google/6aez4K2oVqwIvto2H68T.woff2 +0 -0
  35. package/dist/fonts/google/6aez4K2oVqwIvts2H68T.woff2 +0 -0
  36. package/dist/fonts/google/7Auup_AqnyWWAxW2Wk3swUz56MS91Eww8SX21nejog.woff2 +0 -0
  37. package/dist/fonts/google/7Auup_AqnyWWAxW2Wk3swUz56MS91Eww8SX21nijogp5.woff2 +0 -0
  38. package/dist/fonts/google/7Auup_AqnyWWAxW2Wk3swUz56MS91Eww8SX21nmjogp5.woff2 +0 -0
  39. package/dist/fonts/google/PN_xRfK9oXHga0XdZ8g_vT0.woff2 +0 -0
  40. package/dist/fonts/google/PN_xRfK9oXHga0XdZsg_.woff2 +0 -0
  41. package/dist/fonts/google/PN_xRfK9oXHga0XdaMg_vT0.woff2 +0 -0
  42. package/dist/fonts/google/TK3tWkYFABsmjsphPho.woff2 +0 -0
  43. package/dist/fonts/google/TK3tWkYFABsmjspuPho7vA.woff2 +0 -0
  44. package/dist/fonts/google/TK3tWkYFABsmjspvPho7vA.woff2 +0 -0
  45. package/dist/fonts/google/dg45_pLmvrkcOkBnKsOzXyGWTBcmg-X6VjTYJwQj.woff2 +0 -0
  46. package/dist/fonts/google/dg45_pLmvrkcOkBnKsOzXyGWTBcmg-X6VjXYJwQj.woff2 +0 -0
  47. package/dist/fonts/google/dg45_pLmvrkcOkBnKsOzXyGWTBcmg-X6Vj_YJwQj.woff2 +0 -0
  48. package/dist/fonts/google/dg45_pLmvrkcOkBnKsOzXyGWTBcmg-X6VjbYJwQj.woff2 +0 -0
  49. package/dist/fonts/google/dg45_pLmvrkcOkBnKsOzXyGWTBcmg-X6VjvYJw.woff2 +0 -0
  50. package/dist/fonts/google/fonts-tracklist.txt +0 -48
  51. package/dist/fonts/google/vEFO2_JTCgwQ5ejvMV0O96D01E8J0tJXHKbBjM4.woff2 +0 -0
  52. package/dist/fonts/google/vEFO2_JTCgwQ5ejvMV0O96D01E8J0tJXHKbOjM7sfA.woff2 +0 -0
  53. package/dist/fonts/google/vEFO2_JTCgwQ5ejvMV0O96D01E8J0tJXHKbPjM7sfA.woff2 +0 -0
  54. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfLtrftV.woff2 +0 -0
  55. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfXtrftV.woff2 +0 -0
  56. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfbtrQ.woff2 +0 -0
  57. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfjtrftV.woff2 +0 -0
  58. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfntrftV.woff2 +0 -0
  59. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfrtrftV.woff2 +0 -0
  60. package/dist/fonts/google/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfvtrftV.woff2 +0 -0
  61. package/dist/fonts/google/xn7_YHE41ni1AdIRqAuZuw1Bx9mbZk79FN_B-bnBeA.woff2 +0 -0
  62. package/dist/fonts/google/xn7_YHE41ni1AdIRqAuZuw1Bx9mbZk79FN_C-bk.woff2 +0 -0
  63. package/dist/fonts/google/xn7_YHE41ni1AdIRqAuZuw1Bx9mbZk79FN_G-bnBeA.woff2 +0 -0
  64. package/dist/fonts/google/xn7_YHE41ni1AdIRqAuZuw1Bx9mbZk79FN_M-bnBeA.woff2 +0 -0
  65. package/dist/fonts/google/xn7_YHE41ni1AdIRqAuZuw1Bx9mbZk79FN_N-bnBeA.woff2 +0 -0
  66. package/dist/fonts/google/xn7_YHE41ni1AdIRqAuZuw1Bx9mbZk79FN_P-bnBeA.woff2 +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keenmate/pure-admin-core",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Lightweight, data-focused HTML/CSS admin framework built with PureCSS foundation and comprehensive component system",
5
5
  "style": "dist/css/main.css",
6
6
  "exports": {
@@ -9,7 +9,6 @@
9
9
  "./css/*": "./dist/css/*",
10
10
  "./scss": "./src/scss/main.scss",
11
11
  "./scss/*": "./src/scss/*",
12
- "./fonts/*": "./dist/fonts/*",
13
12
  "./snippets/*": "./snippets/*"
14
13
  },
15
14
  "files": [
@@ -17,6 +16,7 @@
17
16
  "src/scss/",
18
17
  "schemas/",
19
18
  "scripts/pack-theme.js",
19
+ "scripts/download-themes.js",
20
20
  "snippets/",
21
21
  "README.md",
22
22
  "LICENSE"
@@ -24,8 +24,7 @@
24
24
  "scripts": {
25
25
  "build:scss": "sass src/scss/main.scss dist/css/main.css --no-source-map --silence-deprecation=import",
26
26
  "build:scss:watch": "sass src/scss/main.scss dist/css/main.css --watch --silence-deprecation=import",
27
- "build:fonts": "node scripts/copy-fonts.js",
28
- "build": "npm run build:scss && npm run build:fonts",
27
+ "build": "npm run build:scss",
29
28
  "watch": "npm run build:scss:watch",
30
29
  "generate-hashes": "node scripts/generate-hashes.js",
31
30
  "pack-theme": "node scripts/pack-theme.js",
@@ -41,6 +40,9 @@
41
40
  "components",
42
41
  "ui-kit"
43
42
  ],
43
+ "bin": {
44
+ "download-themes": "./scripts/download-themes.js"
45
+ },
44
46
  "author": "KeenMate",
45
47
  "license": "MIT",
46
48
  "peerDependencies": {
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Download themes from pureadmin.io
5
+ *
6
+ * Reads themes.json + .themes.json (local override) and downloads
7
+ * themes that don't have a local "path" set.
8
+ *
9
+ * Format:
10
+ * {
11
+ * "themes": {
12
+ * "audi": {}, // download from pureadmin.io
13
+ * "corporate": { "path": "../my-themes/corp" }, // use local path
14
+ * "custom": { "url": "https://..." } // download from custom URL
15
+ * }
16
+ * }
17
+ *
18
+ * Downloaded themes are saved to ./themes/{name}/dist/{name}.css
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const https = require('https');
24
+ const http = require('http');
25
+
26
+ const BUNDLE_URL = 'https://pureadmin.io/api/bundle';
27
+ const PROJECT_ROOT = process.cwd();
28
+ const THEMES_DIR = path.join(PROJECT_ROOT, 'themes');
29
+ const THEMES_CONFIG = path.join(PROJECT_ROOT, 'themes.json');
30
+ const THEMES_LOCAL = path.join(PROJECT_ROOT, '.themes.json');
31
+
32
+ function download(url) {
33
+ return new Promise((resolve, reject) => {
34
+ const client = url.startsWith('https') ? https : http;
35
+
36
+ client.get(url, (res) => {
37
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
38
+ return download(res.headers.location).then(resolve).catch(reject);
39
+ }
40
+
41
+ if (res.statusCode !== 200) {
42
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
43
+ return;
44
+ }
45
+
46
+ const chunks = [];
47
+ res.on('data', chunk => chunks.push(chunk));
48
+ res.on('end', () => resolve(Buffer.concat(chunks)));
49
+ res.on('error', reject);
50
+ }).on('error', reject);
51
+ });
52
+ }
53
+
54
+ function extractZip(buffer, destDir) {
55
+ const tmpFile = path.join(destDir, '..', '_tmp_theme.zip');
56
+ if (!fs.existsSync(path.dirname(tmpFile))) {
57
+ fs.mkdirSync(path.dirname(tmpFile), { recursive: true });
58
+ }
59
+ fs.writeFileSync(tmpFile, buffer);
60
+
61
+ const { execSync } = require('child_process');
62
+ try {
63
+ execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: 'ignore' });
64
+ } catch (e) {
65
+ try {
66
+ execSync(`powershell -Command "Expand-Archive -Force -Path '${tmpFile}' -DestinationPath '${destDir}'"`, { stdio: 'ignore' });
67
+ } catch (e2) {
68
+ fs.unlinkSync(tmpFile);
69
+ throw new Error('Could not extract zip. Install unzip or use PowerShell.');
70
+ }
71
+ }
72
+ fs.unlinkSync(tmpFile);
73
+ }
74
+
75
+ function findThemeCss(dir, themeName) {
76
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
77
+
78
+ for (const entry of entries) {
79
+ const fullPath = path.join(dir, entry.name);
80
+
81
+ if (entry.isFile() && entry.name === `${themeName}.css`) {
82
+ return fullPath;
83
+ }
84
+
85
+ if (entry.isDirectory()) {
86
+ const found = findThemeCss(fullPath, themeName);
87
+ if (found) return found;
88
+ }
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ function getThemeVersion(themeDir) {
95
+ const manifestPath = path.join(themeDir, 'theme.json');
96
+ if (fs.existsSync(manifestPath)) {
97
+ try {
98
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
99
+ return manifest.version || null;
100
+ } catch (e) {
101
+ return null;
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+
107
+ function copyDir(src, dest) {
108
+ if (!fs.existsSync(dest)) {
109
+ fs.mkdirSync(dest, { recursive: true });
110
+ }
111
+
112
+ const entries = fs.readdirSync(src, { withFileTypes: true });
113
+ for (const entry of entries) {
114
+ const srcPath = path.join(src, entry.name);
115
+ const destPath = path.join(dest, entry.name);
116
+
117
+ if (entry.isDirectory()) {
118
+ copyDir(srcPath, destPath);
119
+ } else {
120
+ fs.copyFileSync(srcPath, destPath);
121
+ }
122
+ }
123
+ }
124
+
125
+ async function main() {
126
+ // Load themes.json (base) + .themes.json (local overrides)
127
+ let themes = {};
128
+
129
+ if (fs.existsSync(THEMES_CONFIG)) {
130
+ const base = JSON.parse(fs.readFileSync(THEMES_CONFIG, 'utf-8'));
131
+ themes = { ...themes, ...(base.themes || {}) };
132
+ console.log(`Loaded themes.json (${Object.keys(base.themes || {}).length} theme(s))`);
133
+ }
134
+
135
+ if (fs.existsSync(THEMES_LOCAL)) {
136
+ const local = JSON.parse(fs.readFileSync(THEMES_LOCAL, 'utf-8'));
137
+ themes = { ...themes, ...(local.themes || {}) };
138
+ console.log(`Loaded .themes.json (${Object.keys(local.themes || {}).length} override(s))`);
139
+ }
140
+
141
+ if (Object.keys(themes).length === 0) {
142
+ console.error('No themes found. Create themes.json or .themes.json, e.g.:');
143
+ console.error(' { "themes": { "audi": {}, "corporate": {} } }');
144
+ process.exit(1);
145
+ }
146
+
147
+ console.log('');
148
+
149
+ // Ensure themes directory exists
150
+ if (!fs.existsSync(THEMES_DIR)) {
151
+ fs.mkdirSync(THEMES_DIR, { recursive: true });
152
+ }
153
+
154
+ // Separate themes by type
155
+ const bundleThemes = []; // No path, no url — download from bundle
156
+ const urlThemes = []; // Custom url
157
+ const localThemes = []; // Has path — skip
158
+
159
+ for (const [name, config] of Object.entries(themes)) {
160
+ const cfg = config || {};
161
+ if (cfg.path) {
162
+ localThemes.push(name);
163
+ } else if (cfg.url) {
164
+ urlThemes.push({ name, url: cfg.url });
165
+ } else {
166
+ bundleThemes.push(name);
167
+ }
168
+ }
169
+
170
+ let downloaded = 0;
171
+
172
+ // Download named themes via bundle (single request)
173
+ if (bundleThemes.length > 0) {
174
+ process.stdout.write(`Fetching theme bundle from pureadmin.io (${bundleThemes.length} theme(s))...`);
175
+
176
+ try {
177
+ const data = await download(BUNDLE_URL);
178
+ console.log(` ${(data.length / 1024).toFixed(0)}KB`);
179
+
180
+ // Extract bundle to temp dir
181
+ const tmpDir = path.join(THEMES_DIR, '_bundle_tmp');
182
+ if (fs.existsSync(tmpDir)) {
183
+ fs.rmSync(tmpDir, { recursive: true });
184
+ }
185
+ fs.mkdirSync(tmpDir, { recursive: true });
186
+ extractZip(data, tmpDir);
187
+
188
+ // Find and copy requested themes
189
+ for (const name of bundleThemes) {
190
+ const cssFile = findThemeCss(tmpDir, name);
191
+
192
+ if (cssFile) {
193
+ const themeDestDir = path.join(THEMES_DIR, name);
194
+ const distDir = path.join(themeDestDir, 'dist');
195
+ if (!fs.existsSync(distDir)) {
196
+ fs.mkdirSync(distDir, { recursive: true });
197
+ }
198
+ fs.copyFileSync(cssFile, path.join(distDir, `${name}.css`));
199
+
200
+ // Copy assets dir if it exists (fonts etc.)
201
+ const assetsDir = path.join(path.dirname(cssFile), 'assets');
202
+ if (fs.existsSync(assetsDir)) {
203
+ copyDir(assetsDir, path.join(distDir, 'assets'));
204
+ }
205
+
206
+ // Copy theme.json manifest if it exists
207
+ const themeJsonSrc = path.join(path.dirname(cssFile), '..', 'theme.json');
208
+ if (fs.existsSync(themeJsonSrc)) {
209
+ fs.copyFileSync(themeJsonSrc, path.join(themeDestDir, 'theme.json'));
210
+ }
211
+
212
+ // Read version from theme.json
213
+ const version = getThemeVersion(themeDestDir);
214
+ console.log(` ${name}: extracted${version ? ` (v${version})` : ''}`);
215
+ downloaded++;
216
+ } else {
217
+ console.log(` ${name}: NOT FOUND in bundle`);
218
+ }
219
+ }
220
+
221
+ // Clean up temp dir
222
+ fs.rmSync(tmpDir, { recursive: true });
223
+ } catch (err) {
224
+ console.log(` FAILED: ${err.message}`);
225
+ }
226
+ }
227
+
228
+ // Download URL themes individually
229
+ for (const { name, url } of urlThemes) {
230
+ process.stdout.write(` ${name}: downloading from ${url}...`);
231
+
232
+ try {
233
+ const data = await download(url);
234
+ const themeDestDir = path.join(THEMES_DIR, name);
235
+
236
+ if (data[0] === 0x50 && data[1] === 0x4B) {
237
+ if (!fs.existsSync(themeDestDir)) {
238
+ fs.mkdirSync(themeDestDir, { recursive: true });
239
+ }
240
+ extractZip(data, themeDestDir);
241
+ } else {
242
+ const distDir = path.join(themeDestDir, 'dist');
243
+ if (!fs.existsSync(distDir)) {
244
+ fs.mkdirSync(distDir, { recursive: true });
245
+ }
246
+ fs.writeFileSync(path.join(distDir, `${name}.css`), data);
247
+ }
248
+
249
+ console.log(' done');
250
+ downloaded++;
251
+ } catch (err) {
252
+ console.log(` FAILED: ${err.message}`);
253
+ }
254
+ }
255
+
256
+ // Report skipped
257
+ for (const name of localThemes) {
258
+ console.log(` ${name}: local path (${themes[name].path}), skipping`);
259
+ }
260
+
261
+ console.log(`\nDownloaded ${downloaded} theme(s), skipped ${localThemes.length} local path(s)`);
262
+ }
263
+
264
+ console.log('Downloading themes...\n');
265
+ main().catch(err => {
266
+ console.error('Fatal error:', err.message);
267
+ process.exit(1);
268
+ });
@@ -311,8 +311,8 @@ ${theme.modes && theme.modes.supported && theme.modes.supported.length > 1
311
311
 
312
312
  ## More Information
313
313
 
314
- - Pure Admin documentation: https://pure-admin.keenmate.dev
315
- - Theme gallery: https://pure-theme-park.keenmate.dev
314
+ - Pure Admin documentation: https://demo.pureadmin.io
315
+ - Theme gallery: https://pureadmin.io
316
316
  ${theme.homepage ? `- Theme homepage: ${theme.homepage}` : ''}
317
317
 
318
318
  ---
@@ -83,9 +83,9 @@
83
83
  <span class="pa-badge pa-badge--info minwr-15 maxwr-15 text-truncate">Very Long Badge Text That Needs Truncation</span>
84
84
  </span>
85
85
 
86
- <!-- Fixed Width with Left Ellipsis (for paths, breadcrumbs) -->
86
+ <!-- Fixed Width with Start-Side Ellipsis (for paths, breadcrumbs) -->
87
87
  <span class="pa-tooltip pa-tooltip--bottom pa-tooltip--multiline" data-tooltip="Settings > User Preferences > Notifications > Email">
88
- <span class="pa-badge pa-badge--secondary minwr-10 maxwr-10 text-truncate pa-badge--ellipsis-left">Settings > User Preferences > Notifications > Email</span>
88
+ <span class="pa-badge pa-badge--secondary minwr-10 maxwr-10 text-truncate pa-badge--ellipsis-start">Settings > User Preferences > Notifications > Email</span>
89
89
  </span>
90
90
 
91
91
 
@@ -186,8 +186,8 @@ SIZES:
186
186
 
187
187
  MODIFIERS:
188
188
  - .pa-badge--pill: Rounded pill shape
189
- - .pa-badge--ellipsis-left: Truncate from left instead of right
190
- - Use utilities for fixed width: minwr-* maxwr-* text-truncate
189
+ - .pa-badge--ellipsis-start: Truncate from start instead of end
190
+ - Use utilities for fixed width: maxwr-* text-truncate
191
191
 
192
192
  ELEMENTS:
193
193
  - .pa-badge__icon: Icon container inside badge
@@ -190,6 +190,70 @@
190
190
  </div>
191
191
 
192
192
 
193
+ <!-- SPLIT BUTTONS -->
194
+
195
+ <!-- Basic Split Button (chevron rotates on open) -->
196
+ <div class="pa-btn-split">
197
+ <button class="pa-btn pa-btn--primary">Save</button>
198
+ <button class="pa-btn pa-btn--primary pa-btn-split__toggle" onclick="toggleSplitMenu(event)">
199
+ <i class="fas fa-chevron-down text-2xs pa-btn-split__chevron"></i>
200
+ </button>
201
+ <div class="pa-btn-split__menu">
202
+ <button class="pa-btn-split__item">Save as Draft</button>
203
+ <button class="pa-btn-split__item">Save &amp; Close</button>
204
+ </div>
205
+ </div>
206
+
207
+ <!-- Split Button with Danger Item -->
208
+ <div class="pa-btn-split">
209
+ <button class="pa-btn pa-btn--primary">Export</button>
210
+ <button class="pa-btn pa-btn--primary pa-btn-split__toggle" onclick="toggleSplitMenu(event)">
211
+ <i class="fas fa-chevron-down text-2xs pa-btn-split__chevron"></i>
212
+ </button>
213
+ <div class="pa-btn-split__menu">
214
+ <button class="pa-btn-split__item">Export as CSV</button>
215
+ <button class="pa-btn-split__item">Export as PDF</button>
216
+ <button class="pa-btn-split__item pa-btn-split__item--danger">Delete All</button>
217
+ </div>
218
+ </div>
219
+
220
+ <!-- Split Button - Upward Placement -->
221
+ <div class="pa-btn-split" data-placement="top-end">
222
+ <button class="pa-btn pa-btn--primary">Upload</button>
223
+ <button class="pa-btn pa-btn--primary pa-btn-split__toggle" onclick="toggleSplitMenu(event)">
224
+ <i class="fas fa-chevron-up text-2xs pa-btn-split__chevron"></i>
225
+ </button>
226
+ <div class="pa-btn-split__menu">
227
+ <button class="pa-btn-split__item">Upload File</button>
228
+ <button class="pa-btn-split__item">Upload Folder</button>
229
+ </div>
230
+ </div>
231
+
232
+ <!-- Split Button - Custom Icon (no rotation) -->
233
+ <div class="pa-btn-split">
234
+ <button class="pa-btn pa-btn--secondary">Share</button>
235
+ <button class="pa-btn pa-btn--secondary pa-btn-split__toggle" onclick="toggleSplitMenu(event)">
236
+ <i class="fas fa-ellipsis-vertical"></i>
237
+ </button>
238
+ <div class="pa-btn-split__menu">
239
+ <button class="pa-btn-split__item">Share via Email</button>
240
+ <button class="pa-btn-split__item">Share via Link</button>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- Split Button - Caret Icon (rotates) -->
245
+ <div class="pa-btn-split">
246
+ <button class="pa-btn pa-btn--danger">Delete</button>
247
+ <button class="pa-btn pa-btn--danger pa-btn-split__toggle" onclick="toggleSplitMenu(event)">
248
+ <i class="fas fa-caret-down pa-btn-split__chevron"></i>
249
+ </button>
250
+ <div class="pa-btn-split__menu">
251
+ <button class="pa-btn-split__item pa-btn-split__item--danger">Delete Permanently</button>
252
+ <button class="pa-btn-split__item">Move to Trash</button>
253
+ </div>
254
+ </div>
255
+
256
+
193
257
  <!-- BUTTONS WITH ICONS -->
194
258
 
195
259
  <!-- Button with Icon (Text Icon) -->
@@ -19,14 +19,14 @@
19
19
  <!-- Tooltip Top (default) -->
20
20
  <span class="pa-tooltip" data-tooltip="This is a tooltip">Hover me</span>
21
21
 
22
- <!-- Tooltip Right -->
23
- <span class="pa-tooltip pa-tooltip--right" data-tooltip="Tooltip on the right">Hover me</span>
22
+ <!-- Tooltip End -->
23
+ <span class="pa-tooltip pa-tooltip--end" data-tooltip="Tooltip on the end">Hover me</span>
24
24
 
25
25
  <!-- Tooltip Bottom -->
26
26
  <span class="pa-tooltip pa-tooltip--bottom" data-tooltip="Tooltip on the bottom">Hover me</span>
27
27
 
28
- <!-- Tooltip Left -->
29
- <span class="pa-tooltip pa-tooltip--left" data-tooltip="Tooltip on the left">Hover me</span>
28
+ <!-- Tooltip Start -->
29
+ <span class="pa-tooltip pa-tooltip--start" data-tooltip="Tooltip on the start">Hover me</span>
30
30
 
31
31
 
32
32
  <!-- TOOLTIP POSITIONS (All 4 directions) -->
@@ -36,9 +36,9 @@
36
36
  Top
37
37
  </button>
38
38
 
39
- <!-- Right Tooltip -->
40
- <button class="pa-btn pa-btn--primary pa-tooltip pa-tooltip--right" data-tooltip="Right tooltip">
41
- Right
39
+ <!-- End Tooltip -->
40
+ <button class="pa-btn pa-btn--primary pa-tooltip pa-tooltip--end" data-tooltip="End tooltip">
41
+ End
42
42
  </button>
43
43
 
44
44
  <!-- Bottom Tooltip -->
@@ -46,9 +46,9 @@
46
46
  Bottom
47
47
  </button>
48
48
 
49
- <!-- Left Tooltip -->
50
- <button class="pa-btn pa-btn--primary pa-tooltip pa-tooltip--left" data-tooltip="Left tooltip">
51
- Left
49
+ <!-- Start Tooltip -->
50
+ <button class="pa-btn pa-btn--primary pa-tooltip pa-tooltip--start" data-tooltip="Start tooltip">
51
+ Start
52
52
  </button>
53
53
 
54
54
 
@@ -109,14 +109,14 @@
109
109
  Primary Bottom
110
110
  </button>
111
111
 
112
- <!-- Success Right Tooltip -->
113
- <button class="pa-btn pa-btn--success pa-tooltip pa-tooltip--success pa-tooltip--right" data-tooltip="Operation successful!">
114
- Success Right
112
+ <!-- Success End Tooltip -->
113
+ <button class="pa-btn pa-btn--success pa-tooltip pa-tooltip--success pa-tooltip--end" data-tooltip="Operation successful!">
114
+ Success End
115
115
  </button>
116
116
 
117
- <!-- Danger Left Tooltip -->
118
- <button class="pa-btn pa-btn--danger pa-tooltip pa-tooltip--danger pa-tooltip--left" data-tooltip="Critical warning!">
119
- Danger Left
117
+ <!-- Danger Start Tooltip -->
118
+ <button class="pa-btn pa-btn--danger pa-tooltip pa-tooltip--danger pa-tooltip--start" data-tooltip="Critical warning!">
119
+ Danger Start
120
120
  </button>
121
121
 
122
122
 
@@ -176,7 +176,7 @@
176
176
 
177
177
  <!-- Popovers are click-triggered rich content overlays
178
178
  - Use Floating UI for positioning (like tooltips)
179
- - Support all 4 positions (top, right, bottom, left) via data-placement attribute
179
+ - Support all 4 positions (top, end, bottom, start) via data-placement attribute
180
180
  - Auto collision detection and flipping
181
181
  - Close with X button or clicking outside
182
182
  -->
@@ -195,8 +195,8 @@
195
195
  </div>
196
196
  </div>
197
197
 
198
- <!-- Popover Right -->
199
- <div class="pa-popover" data-placement="right">
198
+ <!-- Popover End -->
199
+ <div class="pa-popover" data-placement="end">
200
200
  <button class="pa-popover__trigger">i</button>
201
201
  <div class="pa-popover__content">
202
202
  <div class="pa-popover__header">
@@ -204,7 +204,7 @@
204
204
  <button class="pa-popover__close">×</button>
205
205
  </div>
206
206
  <div class="pa-popover__body">
207
- <p>Popover positioned to the right.</p>
207
+ <p>Popover positioned at the inline-end.</p>
208
208
  </div>
209
209
  </div>
210
210
  </div>
@@ -223,8 +223,8 @@
223
223
  </div>
224
224
  </div>
225
225
 
226
- <!-- Popover Left -->
227
- <div class="pa-popover" data-placement="left">
226
+ <!-- Popover Start -->
227
+ <div class="pa-popover" data-placement="start">
228
228
  <button class="pa-popover__trigger">!</button>
229
229
  <div class="pa-popover__content">
230
230
  <div class="pa-popover__header">
@@ -232,7 +232,7 @@
232
232
  <button class="pa-popover__close">×</button>
233
233
  </div>
234
234
  <div class="pa-popover__body">
235
- <p>Popover positioned to the left.</p>
235
+ <p>Popover positioned at the inline-start.</p>
236
236
  </div>
237
237
  </div>
238
238
  </div>
@@ -286,16 +286,16 @@
286
286
 
287
287
  <!-- POPOVER ALIGNMENT -->
288
288
 
289
- <!-- Left Aligned (default) -->
289
+ <!-- Start Aligned (default) -->
290
290
  <div class="pa-popover" data-placement="bottom">
291
291
  <button class="pa-popover__trigger">?</button>
292
292
  <div class="pa-popover__content">
293
293
  <div class="pa-popover__header">
294
- <h4>Left Aligned</h4>
294
+ <h4>Start Aligned</h4>
295
295
  <button class="pa-popover__close">×</button>
296
296
  </div>
297
297
  <div class="pa-popover__body">
298
- <p>Default alignment is left.</p>
298
+ <p>Default alignment is start.</p>
299
299
  <ul>
300
300
  <li>Lists look natural</li>
301
301
  <li>Easy to read</li>
@@ -318,16 +318,16 @@
318
318
  </div>
319
319
  </div>
320
320
 
321
- <!-- Right Aligned -->
321
+ <!-- End Aligned -->
322
322
  <div class="pa-popover pa-popover--end" data-placement="bottom">
323
323
  <button class="pa-popover__trigger">?</button>
324
324
  <div class="pa-popover__content">
325
325
  <div class="pa-popover__header">
326
- <h4>Right Aligned</h4>
326
+ <h4>End Aligned</h4>
327
327
  <button class="pa-popover__close">×</button>
328
328
  </div>
329
329
  <div class="pa-popover__body">
330
- <p>Use --right modifier for RTL layouts.</p>
330
+ <p>Use --end modifier for end-aligned text.</p>
331
331
  </div>
332
332
  </div>
333
333
  </div>
@@ -357,7 +357,7 @@
357
357
  </div>
358
358
 
359
359
  <!-- Popover with Code -->
360
- <div class="pa-popover pa-popover--lg" data-placement="right">
360
+ <div class="pa-popover pa-popover--lg" data-placement="end">
361
361
  <button class="pa-popover__trigger">?</button>
362
362
  <div class="pa-popover__content">
363
363
  <div class="pa-popover__header">
@@ -399,10 +399,10 @@ document.addEventListener('content-loaded', function() {
399
399
  TOOLTIPS:
400
400
  - pa-tooltip (base class, top position by default)
401
401
 
402
- POSITIONS:
403
- - pa-tooltip--right
402
+ POSITIONS (RTL-aware):
403
+ - pa-tooltip--end (inline-end: right in LTR, left in RTL)
404
404
  - pa-tooltip--bottom
405
- - pa-tooltip--left
405
+ - pa-tooltip--start (inline-start: left in LTR, right in RTL)
406
406
 
407
407
  VARIANTS:
408
408
  - pa-tooltip--primary (primary colored background)
@@ -417,11 +417,11 @@ MULTILINE:
417
417
  POPOVERS:
418
418
  - pa-popover (base container)
419
419
 
420
- POSITIONING (use data-placement attribute):
420
+ POSITIONING (use data-placement attribute, RTL-aware):
421
421
  - data-placement="top" (default)
422
- - data-placement="right"
422
+ - data-placement="end" (inline-end: right in LTR, left in RTL)
423
423
  - data-placement="bottom"
424
- - data-placement="left"
424
+ - data-placement="start" (inline-start: left in LTR, right in RTL)
425
425
 
426
426
  SIZES:
427
427
  - pa-popover--sm (small)
@@ -69,6 +69,9 @@
69
69
  // Table components
70
70
  @use 'core-components/tables' as *;
71
71
 
72
+ // Filter card (expandable table filters)
73
+ @use 'core-components/filter-card' as *;
74
+
72
75
  // Comparison tables
73
76
  @use 'core-components/comparison' as *;
74
77