@keenmate/pure-admin-core 1.5.1 → 2.0.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.
- package/README.md +19 -2
- package/dist/css/main.css +740 -19
- package/package.json +5 -1
- package/schemas/base-variables.schema.json +623 -0
- package/schemas/component-variables.schema.json +80 -0
- package/schemas/pure-admin-theme.schema.json +325 -0
- package/scripts/pack-theme.js +397 -0
- package/src/scss/_core.scss +3 -0
- package/src/scss/core-components/_cards.scss +17 -0
- package/src/scss/core-components/_data-display.scss +237 -0
- package/src/scss/core-components/_data-viz.scss +608 -0
- package/src/scss/core-components/_grid.scss +3 -2
- package/src/scss/core-components/_statistics.scss +11 -10
- package/src/scss/core-components/layout/_layout-container.scss +1 -0
- package/src/scss/variables/_components.scss +67 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// pack-theme.js — Package a Pure Admin theme into a distributable zip
|
|
5
|
+
// =============================================================================
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// node pack-theme.js <theme-dir> [--output <dir>]
|
|
9
|
+
//
|
|
10
|
+
// Example:
|
|
11
|
+
// node pack-theme.js ../pure-admin-cafeindustrial-theme/
|
|
12
|
+
// node pack-theme.js ./my-theme --output ./releases/
|
|
13
|
+
//
|
|
14
|
+
// The script:
|
|
15
|
+
// 1. Reads and validates theme.json from the theme directory
|
|
16
|
+
// 2. Verifies CSS file exists (or compiles SCSS if missing)
|
|
17
|
+
// 3. Generates a README.md with usage instructions
|
|
18
|
+
// 4. Packages everything into pure-admin-theme-{id}-{version}.zip
|
|
19
|
+
//
|
|
20
|
+
// Zip contents:
|
|
21
|
+
// pure-admin-theme-{id}-{version}.zip
|
|
22
|
+
// ├── theme.json
|
|
23
|
+
// ├── css/{id}.css
|
|
24
|
+
// ├── scss/{id}.scss
|
|
25
|
+
// ├── preview/thumbnail.png (if exists)
|
|
26
|
+
// └── README.md
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
const { execSync } = require('child_process');
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Parse arguments
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
let themeDir = null;
|
|
38
|
+
let outputDir = null;
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < args.length; i++) {
|
|
41
|
+
if (args[i] === '--output' && args[i + 1]) {
|
|
42
|
+
outputDir = args[++i];
|
|
43
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
44
|
+
console.log(`
|
|
45
|
+
Usage: node pack-theme.js <theme-dir> [--output <dir>]
|
|
46
|
+
|
|
47
|
+
Options:
|
|
48
|
+
--output <dir> Output directory for the zip (default: theme-dir/dist/)
|
|
49
|
+
--help, -h Show this help message
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
node pack-theme.js ../pure-admin-cafeindustrial-theme/
|
|
53
|
+
node pack-theme.js ./my-theme --output ./releases/
|
|
54
|
+
`);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
} else if (!themeDir) {
|
|
57
|
+
themeDir = args[i];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!themeDir) {
|
|
62
|
+
console.error('Error: No theme directory specified.');
|
|
63
|
+
console.error('Usage: node pack-theme.js <theme-dir> [--output <dir>]');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Resolve paths
|
|
68
|
+
themeDir = path.resolve(themeDir);
|
|
69
|
+
const coreDir = path.resolve(__dirname, '..');
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(themeDir)) {
|
|
72
|
+
console.error(`Error: Theme directory not found: ${themeDir}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// 1. Read and validate theme.json
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
const themeJsonPath = path.join(themeDir, 'theme.json');
|
|
80
|
+
if (!fs.existsSync(themeJsonPath)) {
|
|
81
|
+
console.error(`Error: theme.json not found in ${themeDir}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let theme;
|
|
86
|
+
try {
|
|
87
|
+
theme = JSON.parse(fs.readFileSync(themeJsonPath, 'utf-8'));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(`Error: Invalid JSON in theme.json: ${err.message}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Validate required fields
|
|
94
|
+
const requiredFields = ['name', 'id', 'version', 'modes', 'exports'];
|
|
95
|
+
const missing = requiredFields.filter(f => !theme[f]);
|
|
96
|
+
if (missing.length > 0) {
|
|
97
|
+
console.error(`Error: theme.json is missing required fields: ${missing.join(', ')}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Validate id format
|
|
102
|
+
if (!/^[a-z][a-z0-9-]*$/.test(theme.id)) {
|
|
103
|
+
console.error(`Error: theme.id "${theme.id}" must be lowercase alphanumeric with hyphens (e.g. "cafeindustrial")`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate version format
|
|
108
|
+
if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/.test(theme.version)) {
|
|
109
|
+
console.error(`Error: theme.version "${theme.version}" must be semver (e.g. "1.0.0")`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`Packaging theme: ${theme.name} v${theme.version} (${theme.id})`);
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// 2. Locate or compile CSS
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
let cssSourcePath = null;
|
|
119
|
+
|
|
120
|
+
// Check exports.css path first
|
|
121
|
+
if (theme.exports && theme.exports.css) {
|
|
122
|
+
const exportedCss = path.resolve(themeDir, theme.exports.css);
|
|
123
|
+
if (fs.existsSync(exportedCss)) {
|
|
124
|
+
cssSourcePath = exportedCss;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Also check common locations
|
|
129
|
+
if (!cssSourcePath) {
|
|
130
|
+
const candidates = [
|
|
131
|
+
path.join(themeDir, 'dist', `${theme.id}.css`),
|
|
132
|
+
path.join(themeDir, 'dist', 'css', `${theme.id}.css`),
|
|
133
|
+
path.join(themeDir, 'css', `${theme.id}.css`),
|
|
134
|
+
];
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
if (fs.existsSync(candidate)) {
|
|
137
|
+
cssSourcePath = candidate;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If no CSS found, try to compile from SCSS
|
|
144
|
+
if (!cssSourcePath) {
|
|
145
|
+
console.log('No compiled CSS found, attempting to compile from SCSS...');
|
|
146
|
+
|
|
147
|
+
let scssPath = null;
|
|
148
|
+
if (theme.exports && theme.exports.scss) {
|
|
149
|
+
const exportedScss = path.resolve(themeDir, theme.exports.scss);
|
|
150
|
+
if (fs.existsSync(exportedScss)) {
|
|
151
|
+
scssPath = exportedScss;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!scssPath) {
|
|
155
|
+
scssPath = path.join(themeDir, 'src', 'scss', `${theme.id}.scss`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!fs.existsSync(scssPath)) {
|
|
159
|
+
console.error(`Error: No CSS or SCSS source found for theme "${theme.id}"`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Compile SCSS
|
|
164
|
+
const tempCssDir = path.join(themeDir, 'dist');
|
|
165
|
+
if (!fs.existsSync(tempCssDir)) {
|
|
166
|
+
fs.mkdirSync(tempCssDir, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const tempCssPath = path.join(tempCssDir, `${theme.id}.css`);
|
|
170
|
+
|
|
171
|
+
// Build load paths for sass
|
|
172
|
+
// 1. node_modules at workspace root (for @keenmate/pure-admin-core/... imports)
|
|
173
|
+
// 2. core scss dir (for legacy bare imports)
|
|
174
|
+
const loadPaths = [];
|
|
175
|
+
|
|
176
|
+
// Check for node_modules in various locations
|
|
177
|
+
const nodeModulesCandidates = [
|
|
178
|
+
path.join(themeDir, 'node_modules'),
|
|
179
|
+
path.join(themeDir, '..', 'node_modules'),
|
|
180
|
+
path.join(coreDir, '..', '..', 'node_modules'),
|
|
181
|
+
];
|
|
182
|
+
for (const nm of nodeModulesCandidates) {
|
|
183
|
+
if (fs.existsSync(nm)) {
|
|
184
|
+
loadPaths.push(nm);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Also add core scss dir for bare imports
|
|
189
|
+
loadPaths.push(path.join(coreDir, 'src', 'scss'));
|
|
190
|
+
|
|
191
|
+
const loadPathArgs = loadPaths.map(p => `--load-path="${p}"`).join(' ');
|
|
192
|
+
const sassCmd = `sass "${scssPath}" "${tempCssPath}" --no-source-map --silence-deprecation=import ${loadPathArgs}`;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
console.log(` Compiling: ${path.basename(scssPath)}`);
|
|
196
|
+
execSync(sassCmd, { stdio: 'pipe' });
|
|
197
|
+
cssSourcePath = tempCssPath;
|
|
198
|
+
console.log(' Compilation successful.');
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error(`Error: SCSS compilation failed:`);
|
|
201
|
+
console.error(err.stderr ? err.stderr.toString() : err.message);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(` CSS: ${path.relative(themeDir, cssSourcePath)}`);
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// 3. Locate SCSS source
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
let scssSourcePath = null;
|
|
212
|
+
if (theme.exports && theme.exports.scss) {
|
|
213
|
+
const exportedScss = path.resolve(themeDir, theme.exports.scss);
|
|
214
|
+
if (fs.existsSync(exportedScss)) {
|
|
215
|
+
scssSourcePath = exportedScss;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!scssSourcePath) {
|
|
219
|
+
const candidate = path.join(themeDir, 'src', 'scss', `${theme.id}.scss`);
|
|
220
|
+
if (fs.existsSync(candidate)) {
|
|
221
|
+
scssSourcePath = candidate;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (scssSourcePath) {
|
|
226
|
+
console.log(` SCSS: ${path.relative(themeDir, scssSourcePath)}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// 4. Locate preview thumbnail
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
let thumbnailPath = null;
|
|
233
|
+
const thumbnailCandidates = [
|
|
234
|
+
theme.preview && theme.preview.thumbnail ? path.resolve(themeDir, theme.preview.thumbnail) : null,
|
|
235
|
+
path.join(themeDir, 'preview', 'thumbnail.png'),
|
|
236
|
+
path.join(themeDir, 'preview', 'thumbnail.jpg'),
|
|
237
|
+
].filter(Boolean);
|
|
238
|
+
|
|
239
|
+
for (const candidate of thumbnailCandidates) {
|
|
240
|
+
if (fs.existsSync(candidate)) {
|
|
241
|
+
thumbnailPath = candidate;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (thumbnailPath) {
|
|
247
|
+
console.log(` Preview: ${path.relative(themeDir, thumbnailPath)}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// 5. Generate README.md
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
const modesText = theme.modes && theme.modes.supported
|
|
254
|
+
? theme.modes.supported.join(', ')
|
|
255
|
+
: 'light';
|
|
256
|
+
|
|
257
|
+
const tagsText = theme.tags && theme.tags.length > 0
|
|
258
|
+
? theme.tags.join(', ')
|
|
259
|
+
: '';
|
|
260
|
+
|
|
261
|
+
const coreVersionText = theme.coreVersion || (theme.dependencies && theme.dependencies.core) || '>=1.5.0';
|
|
262
|
+
|
|
263
|
+
const readme = `# ${theme.name}
|
|
264
|
+
|
|
265
|
+
${theme.description || ''}
|
|
266
|
+
|
|
267
|
+
- **Version:** ${theme.version}
|
|
268
|
+
- **Author:** ${theme.author || 'Unknown'}
|
|
269
|
+
- **License:** ${theme.license || 'MIT'}
|
|
270
|
+
- **Modes:** ${modesText}
|
|
271
|
+
- **Core Version:** ${coreVersionText}
|
|
272
|
+
${tagsText ? `- **Tags:** ${tagsText}` : ''}
|
|
273
|
+
|
|
274
|
+
## Quick Start — CSS Only
|
|
275
|
+
|
|
276
|
+
Drop the compiled CSS file into your project:
|
|
277
|
+
|
|
278
|
+
\`\`\`html
|
|
279
|
+
<link rel="stylesheet" href="css/${theme.id}.css">
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
No build tools required. The CSS is fully self-contained.
|
|
283
|
+
|
|
284
|
+
## Quick Start — SCSS Customization
|
|
285
|
+
|
|
286
|
+
If you want to customize theme variables before compiling:
|
|
287
|
+
|
|
288
|
+
1. Install the core package:
|
|
289
|
+
\`\`\`bash
|
|
290
|
+
npm install @keenmate/pure-admin-core
|
|
291
|
+
\`\`\`
|
|
292
|
+
|
|
293
|
+
2. Compile with sass:
|
|
294
|
+
\`\`\`bash
|
|
295
|
+
sass scss/${theme.id}.scss output.css \\
|
|
296
|
+
--load-path=node_modules \\
|
|
297
|
+
--silence-deprecation=import
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
3. Or import in your own SCSS and override variables before the import.
|
|
301
|
+
|
|
302
|
+
## Mode Switching
|
|
303
|
+
${theme.modes && theme.modes.supported && theme.modes.supported.length > 1
|
|
304
|
+
? `This theme supports ${modesText} modes. Add the mode class to toggle:
|
|
305
|
+
|
|
306
|
+
\`\`\`html
|
|
307
|
+
<body class="pa-mode-dark"> <!-- dark mode -->
|
|
308
|
+
<body class="pa-mode-light"> <!-- light mode -->
|
|
309
|
+
\`\`\``
|
|
310
|
+
: `This theme supports ${modesText} mode.`}
|
|
311
|
+
|
|
312
|
+
## More Information
|
|
313
|
+
|
|
314
|
+
- Pure Admin documentation: https://pure-admin.keenmate.dev
|
|
315
|
+
- Theme gallery: https://pure-theme-park.keenmate.dev
|
|
316
|
+
${theme.homepage ? `- Theme homepage: ${theme.homepage}` : ''}
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
*Generated by pure-admin-core pack-theme*
|
|
320
|
+
`;
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// 6. Create the zip
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
const zipName = `pure-admin-theme-${theme.id}-${theme.version}.zip`;
|
|
326
|
+
|
|
327
|
+
if (!outputDir) {
|
|
328
|
+
outputDir = path.join(themeDir, 'dist');
|
|
329
|
+
}
|
|
330
|
+
outputDir = path.resolve(outputDir);
|
|
331
|
+
|
|
332
|
+
if (!fs.existsSync(outputDir)) {
|
|
333
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const zipPath = path.join(outputDir, zipName);
|
|
337
|
+
|
|
338
|
+
// Try to load archiver, fall back to manual zip creation
|
|
339
|
+
let archiver;
|
|
340
|
+
try {
|
|
341
|
+
archiver = require('archiver');
|
|
342
|
+
} catch {
|
|
343
|
+
console.error('Error: "archiver" package is required but not installed.');
|
|
344
|
+
console.error('Install it with: npm install archiver --save-dev');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const output = fs.createWriteStream(zipPath);
|
|
349
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
350
|
+
|
|
351
|
+
archive.on('error', (err) => {
|
|
352
|
+
console.error(`Error creating zip: ${err.message}`);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
archive.on('warning', (err) => {
|
|
357
|
+
if (err.code === 'ENOENT') {
|
|
358
|
+
console.warn(`Warning: ${err.message}`);
|
|
359
|
+
} else {
|
|
360
|
+
throw err;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
output.on('close', () => {
|
|
365
|
+
const sizeKB = (archive.pointer() / 1024).toFixed(1);
|
|
366
|
+
console.log(`\nCreated: ${zipPath}`);
|
|
367
|
+
console.log(`Size: ${sizeKB} KB`);
|
|
368
|
+
console.log('\nZip contents:');
|
|
369
|
+
console.log(` theme.json`);
|
|
370
|
+
console.log(` css/${theme.id}.css`);
|
|
371
|
+
if (scssSourcePath) console.log(` scss/${theme.id}.scss`);
|
|
372
|
+
if (thumbnailPath) console.log(` preview/${path.basename(thumbnailPath)}`);
|
|
373
|
+
console.log(` README.md`);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
archive.pipe(output);
|
|
377
|
+
|
|
378
|
+
// Add theme.json
|
|
379
|
+
archive.file(themeJsonPath, { name: 'theme.json' });
|
|
380
|
+
|
|
381
|
+
// Add CSS
|
|
382
|
+
archive.file(cssSourcePath, { name: `css/${theme.id}.css` });
|
|
383
|
+
|
|
384
|
+
// Add SCSS (if available)
|
|
385
|
+
if (scssSourcePath) {
|
|
386
|
+
archive.file(scssSourcePath, { name: `scss/${theme.id}.scss` });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Add preview thumbnail (if available)
|
|
390
|
+
if (thumbnailPath) {
|
|
391
|
+
archive.file(thumbnailPath, { name: `preview/${path.basename(thumbnailPath)}` });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Add generated README
|
|
395
|
+
archive.append(readme, { name: 'README.md' });
|
|
396
|
+
|
|
397
|
+
archive.finalize();
|
package/src/scss/_core.scss
CHANGED
|
@@ -111,5 +111,8 @@
|
|
|
111
111
|
// Data Display (read-only fields)
|
|
112
112
|
@use 'core-components/data-display' as *;
|
|
113
113
|
|
|
114
|
+
// Data Visualization (progress bars, rings, gauges, heatmaps, sparklines)
|
|
115
|
+
@use 'core-components/data-viz' as *;
|
|
116
|
+
|
|
114
117
|
// Utility classes and helpers
|
|
115
118
|
@use 'core-components/utilities' as *;
|
|
@@ -277,6 +277,23 @@
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
// Live-data state — persistent tinted background reflecting latest change
|
|
281
|
+
// JS swaps the class on each data update; color stays until next update
|
|
282
|
+
&--live-up {
|
|
283
|
+
background-color: rgba($success-bg, 0.10);
|
|
284
|
+
transition: background-color 0.3s ease;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
&--live-down {
|
|
288
|
+
background-color: rgba($danger-bg, 0.10);
|
|
289
|
+
transition: background-color 0.3s ease;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
&--live-neutral {
|
|
293
|
+
background-color: $card-bg;
|
|
294
|
+
transition: background-color 0.3s ease;
|
|
295
|
+
}
|
|
296
|
+
|
|
280
297
|
// Theme color variants (color-1 through color-9)
|
|
281
298
|
// These use theme-customizable colors from --pa-color-* CSS variables
|
|
282
299
|
@for $i from 1 through 9 {
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
======================================== */
|
|
5
5
|
@use '../variables' as *;
|
|
6
6
|
|
|
7
|
+
// Copy button base styles (shared across data display patterns)
|
|
8
|
+
@mixin _copy-btn-base {
|
|
9
|
+
flex-shrink: 0;
|
|
10
|
+
padding: $field-copy-padding;
|
|
11
|
+
background: transparent;
|
|
12
|
+
border: none;
|
|
13
|
+
border-radius: $border-radius;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
transition: opacity $field-copy-transition, background $field-copy-transition;
|
|
16
|
+
|
|
17
|
+
&:hover {
|
|
18
|
+
opacity: $field-copy-hover-opacity;
|
|
19
|
+
background: $field-copy-hover-bg;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
// ============================================================================
|
|
8
24
|
// SINGLE FIELD (.pa-field)
|
|
9
25
|
// Default: stacked (label on top, value below)
|
|
@@ -579,6 +595,65 @@
|
|
|
579
595
|
grid-column: 2;
|
|
580
596
|
}
|
|
581
597
|
}
|
|
598
|
+
|
|
599
|
+
// ------------------------------------------------------------------
|
|
600
|
+
// COPYABLE
|
|
601
|
+
// ------------------------------------------------------------------
|
|
602
|
+
|
|
603
|
+
&__copy {
|
|
604
|
+
@include _copy-btn-base;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
&__value--copy-btn,
|
|
608
|
+
&__value--copy-hover {
|
|
609
|
+
display: flex;
|
|
610
|
+
align-items: center;
|
|
611
|
+
gap: $spacing-sm;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
&__value--copy-btn .pa-desc-table__copy {
|
|
615
|
+
opacity: $field-copy-opacity;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
&__value--copy-hover .pa-desc-table__copy {
|
|
619
|
+
opacity: 0;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
&__value--copy-hover:hover .pa-desc-table__copy {
|
|
623
|
+
opacity: $field-copy-opacity;
|
|
624
|
+
|
|
625
|
+
&:hover {
|
|
626
|
+
opacity: $field-copy-hover-opacity;
|
|
627
|
+
background: $field-copy-hover-bg;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
&__value--copy-click {
|
|
632
|
+
cursor: pointer;
|
|
633
|
+
transition: opacity $field-copy-transition;
|
|
634
|
+
|
|
635
|
+
&:hover {
|
|
636
|
+
opacity: $field-copy-click-hover-opacity;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
&::after {
|
|
640
|
+
content: 'Click to copy';
|
|
641
|
+
font-size: $font-size-2xs;
|
|
642
|
+
opacity: 0;
|
|
643
|
+
margin-inline-start: $spacing-sm;
|
|
644
|
+
transition: opacity $field-copy-transition;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
&:hover::after {
|
|
648
|
+
opacity: $field-copy-hint-opacity;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
&__value--copied::after {
|
|
653
|
+
content: 'Copied!' !important;
|
|
654
|
+
opacity: 1 !important;
|
|
655
|
+
color: var(--pa-color-4, #28a745);
|
|
656
|
+
}
|
|
582
657
|
}
|
|
583
658
|
|
|
584
659
|
// ============================================================================
|
|
@@ -690,6 +765,52 @@
|
|
|
690
765
|
font-weight: $font-weight-bold;
|
|
691
766
|
}
|
|
692
767
|
}
|
|
768
|
+
|
|
769
|
+
// ------------------------------------------------------------------
|
|
770
|
+
// COPYABLE
|
|
771
|
+
// ------------------------------------------------------------------
|
|
772
|
+
|
|
773
|
+
&__copy {
|
|
774
|
+
@include _copy-btn-base;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
&__row--copy-btn &__value,
|
|
778
|
+
&__row--copy-hover &__value {
|
|
779
|
+
display: flex;
|
|
780
|
+
align-items: center;
|
|
781
|
+
gap: $spacing-sm;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
&__row--copy-btn .pa-prop-card__copy,
|
|
785
|
+
&__row--copy-hover .pa-prop-card__copy {
|
|
786
|
+
order: -1;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
&__row--copy-btn .pa-prop-card__copy {
|
|
790
|
+
opacity: $field-copy-opacity;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
&__row--copy-hover .pa-prop-card__copy {
|
|
794
|
+
opacity: 0;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
&__row--copy-hover:hover .pa-prop-card__copy {
|
|
798
|
+
opacity: $field-copy-opacity;
|
|
799
|
+
|
|
800
|
+
&:hover {
|
|
801
|
+
opacity: $field-copy-hover-opacity;
|
|
802
|
+
background: $field-copy-hover-bg;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
&__row--copy-click &__value {
|
|
807
|
+
cursor: pointer;
|
|
808
|
+
transition: opacity $field-copy-transition;
|
|
809
|
+
|
|
810
|
+
&:hover {
|
|
811
|
+
opacity: $field-copy-click-hover-opacity;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
693
814
|
}
|
|
694
815
|
|
|
695
816
|
// ============================================================================
|
|
@@ -801,6 +922,63 @@
|
|
|
801
922
|
display: block;
|
|
802
923
|
}
|
|
803
924
|
}
|
|
925
|
+
|
|
926
|
+
// ------------------------------------------------------------------
|
|
927
|
+
// COPYABLE
|
|
928
|
+
// ------------------------------------------------------------------
|
|
929
|
+
|
|
930
|
+
&__copy {
|
|
931
|
+
@include _copy-btn-base;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
&__row--copy-btn &__value,
|
|
935
|
+
&__row--copy-hover &__value {
|
|
936
|
+
gap: $spacing-sm;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
&__row--copy-btn .pa-banded__copy {
|
|
940
|
+
opacity: $field-copy-opacity;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
&__row--copy-hover .pa-banded__copy {
|
|
944
|
+
opacity: 0;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
&__row--copy-hover:hover .pa-banded__copy {
|
|
948
|
+
opacity: $field-copy-opacity;
|
|
949
|
+
|
|
950
|
+
&:hover {
|
|
951
|
+
opacity: $field-copy-hover-opacity;
|
|
952
|
+
background: $field-copy-hover-bg;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
&__row--copy-click &__value {
|
|
957
|
+
cursor: pointer;
|
|
958
|
+
transition: opacity $field-copy-transition;
|
|
959
|
+
|
|
960
|
+
&:hover {
|
|
961
|
+
opacity: $field-copy-click-hover-opacity;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
&::after {
|
|
965
|
+
content: 'Click to copy';
|
|
966
|
+
font-size: $font-size-2xs;
|
|
967
|
+
opacity: 0;
|
|
968
|
+
margin-inline-start: $spacing-sm;
|
|
969
|
+
transition: opacity $field-copy-transition;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
&:hover::after {
|
|
973
|
+
opacity: $field-copy-hint-opacity;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
&__row--copied &__value::after {
|
|
978
|
+
content: 'Copied!' !important;
|
|
979
|
+
opacity: 1 !important;
|
|
980
|
+
color: var(--pa-color-4, #28a745);
|
|
981
|
+
}
|
|
804
982
|
}
|
|
805
983
|
|
|
806
984
|
// ============================================================================
|
|
@@ -839,4 +1017,63 @@
|
|
|
839
1017
|
color: var(--pa-text-color-1);
|
|
840
1018
|
line-height: $accent-grid-value-line-height;
|
|
841
1019
|
}
|
|
1020
|
+
|
|
1021
|
+
// ------------------------------------------------------------------
|
|
1022
|
+
// COPYABLE
|
|
1023
|
+
// ------------------------------------------------------------------
|
|
1024
|
+
|
|
1025
|
+
&__copy {
|
|
1026
|
+
@include _copy-btn-base;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
&__item--copy-btn &__value,
|
|
1030
|
+
&__item--copy-hover &__value {
|
|
1031
|
+
display: flex;
|
|
1032
|
+
align-items: center;
|
|
1033
|
+
gap: $spacing-sm;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
&__item--copy-btn .pa-accent-grid__copy {
|
|
1037
|
+
opacity: $field-copy-opacity;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
&__item--copy-hover .pa-accent-grid__copy {
|
|
1041
|
+
opacity: 0;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
&__item--copy-hover:hover .pa-accent-grid__copy {
|
|
1045
|
+
opacity: $field-copy-opacity;
|
|
1046
|
+
|
|
1047
|
+
&:hover {
|
|
1048
|
+
opacity: $field-copy-hover-opacity;
|
|
1049
|
+
background: $field-copy-hover-bg;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
&__item--copy-click &__value {
|
|
1054
|
+
cursor: pointer;
|
|
1055
|
+
transition: opacity $field-copy-transition;
|
|
1056
|
+
|
|
1057
|
+
&:hover {
|
|
1058
|
+
opacity: $field-copy-click-hover-opacity;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
&::after {
|
|
1062
|
+
content: 'Click to copy';
|
|
1063
|
+
font-size: $font-size-2xs;
|
|
1064
|
+
opacity: 0;
|
|
1065
|
+
margin-inline-start: $spacing-sm;
|
|
1066
|
+
transition: opacity $field-copy-transition;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
&:hover::after {
|
|
1070
|
+
opacity: $field-copy-hint-opacity;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
&__item--copied &__value::after {
|
|
1075
|
+
content: 'Copied!' !important;
|
|
1076
|
+
opacity: 1 !important;
|
|
1077
|
+
color: var(--pa-color-4, #28a745);
|
|
1078
|
+
}
|
|
842
1079
|
}
|