@ngxsmk/theme 2.0.0 → 3.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.
- package/README.md +50 -9
- package/esm2022/lib/css.mjs +155 -4
- package/esm2022/lib/define-config.mjs +3 -3
- package/esm2022/lib/ionic-provider.mjs +30 -0
- package/esm2022/lib/presets.mjs +167 -1
- package/esm2022/lib/theme-injector.service.mjs +17 -8
- package/esm2022/lib/token-adapter.mjs +154 -49
- package/esm2022/lib/tokens.mjs +10 -1
- package/esm2022/lib/types.mjs +1 -1
- package/esm2022/public-api.mjs +2 -1
- package/fesm2022/ngxsmk-theme.mjs +529 -62
- package/fesm2022/ngxsmk-theme.mjs.map +1 -1
- package/lib/css.d.ts.map +1 -1
- package/lib/ionic-provider.d.ts +18 -0
- package/lib/ionic-provider.d.ts.map +1 -0
- package/lib/presets.d.ts +14 -0
- package/lib/presets.d.ts.map +1 -1
- package/lib/theme-injector.service.d.ts.map +1 -1
- package/lib/token-adapter.d.ts +9 -7
- package/lib/token-adapter.d.ts.map +1 -1
- package/lib/tokens.d.ts +9 -0
- package/lib/tokens.d.ts.map +1 -1
- package/lib/types.d.ts +13 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +6 -2
- package/public-api.d.ts +1 -0
- package/public-api.d.ts.map +1 -1
- package/styles/ngxsmk.css +113 -3
- package/styles/ngxsmk.emerald.css +113 -3
- package/styles/ngxsmk.ink.css +500 -0
- package/styles/ngxsmk.neutral.css +113 -3
- package/styles/ngxsmk.rose.css +113 -3
- package/styles/ngxsmk.violet.css +113 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, Injectable, signal, computed, effect } from '@angular/core';
|
|
3
|
+
import { inject, Injectable, signal, computed, effect, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Design token model for the NGXSMK universal theme engine.
|
|
@@ -303,6 +303,15 @@ const EASING = {
|
|
|
303
303
|
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
304
304
|
emphasized: 'cubic-bezier(0.2, 0, 0, 1)',
|
|
305
305
|
};
|
|
306
|
+
/** Safe-area inset defaults (fallback to CSS env() variables for iOS/Android/Ionic). */
|
|
307
|
+
const SAFE_AREA = {
|
|
308
|
+
top: 'env(safe-area-inset-top, 0px)',
|
|
309
|
+
bottom: 'env(safe-area-inset-bottom, 0px)',
|
|
310
|
+
left: 'env(safe-area-inset-left, 0px)',
|
|
311
|
+
right: 'env(safe-area-inset-right, 0px)',
|
|
312
|
+
};
|
|
313
|
+
/** Minimum touch target size for mobile accessibility (Apple HIG & Android Material). */
|
|
314
|
+
const TOUCH_TARGET_MIN = '44px';
|
|
306
315
|
|
|
307
316
|
/**
|
|
308
317
|
* Identity helper that gives `ngxsmk.config.ts` files full type inference:
|
|
@@ -329,9 +338,9 @@ function resolveTheme(config) {
|
|
|
329
338
|
fontFamily: { ...DEFAULT_FONT_FAMILY, ...config.typography?.fontFamily },
|
|
330
339
|
typography: DEFAULT_TYPOGRAPHY,
|
|
331
340
|
spacing: SPACING,
|
|
332
|
-
radius: RADIUS,
|
|
341
|
+
radius: { ...RADIUS, ...(config.radiusScale ?? {}) },
|
|
333
342
|
radiusBase: config.borderRadius ?? 'md',
|
|
334
|
-
shadow: SHADOW,
|
|
343
|
+
shadow: { ...SHADOW, ...(config.shadowScale ?? {}) },
|
|
335
344
|
duration: DURATION,
|
|
336
345
|
easing: EASING,
|
|
337
346
|
zIndex: Z_INDEX,
|
|
@@ -362,12 +371,25 @@ const COMPONENT_TOKEN_DEFAULTS = {
|
|
|
362
371
|
'--ngxsmk-button-font-weight': '500',
|
|
363
372
|
'--ngxsmk-button-radius': 'var(--ngxsmk-radius-base)',
|
|
364
373
|
'--ngxsmk-button-disabled-opacity': '0.5',
|
|
374
|
+
'--ngxsmk-button-border-width': '1px',
|
|
375
|
+
'--ngxsmk-button-gap': 'var(--ngxsmk-space-2)',
|
|
376
|
+
},
|
|
377
|
+
input: {
|
|
378
|
+
'--ngxsmk-input-bg': 'var(--ngxsmk-color-surface)',
|
|
379
|
+
'--ngxsmk-input-color': 'var(--ngxsmk-color-on-surface)',
|
|
380
|
+
'--ngxsmk-input-border': 'var(--ngxsmk-color-outline-strong)',
|
|
381
|
+
'--ngxsmk-input-radius': 'var(--ngxsmk-radius-base)',
|
|
382
|
+
'--ngxsmk-input-height': 'var(--ngxsmk-control-height)',
|
|
383
|
+
'--ngxsmk-input-padding-inline': 'var(--ngxsmk-space-3)',
|
|
384
|
+
'--ngxsmk-input-placeholder': 'var(--ngxsmk-color-on-surface-variant)',
|
|
365
385
|
},
|
|
366
386
|
card: {
|
|
367
387
|
'--ngxsmk-card-bg': 'var(--ngxsmk-color-surface)',
|
|
368
388
|
'--ngxsmk-card-radius': 'var(--ngxsmk-radius-xl)',
|
|
369
389
|
'--ngxsmk-card-shadow': 'var(--ngxsmk-shadow-sm)',
|
|
370
390
|
'--ngxsmk-card-border-color': 'var(--ngxsmk-color-outline)',
|
|
391
|
+
'--ngxsmk-card-border-width': '1px',
|
|
392
|
+
'--ngxsmk-card-padding': 'var(--ngxsmk-space-5)',
|
|
371
393
|
},
|
|
372
394
|
accordion: {
|
|
373
395
|
'--ngxsmk-accordion-bg': 'var(--ngxsmk-color-surface)',
|
|
@@ -389,6 +411,122 @@ const COMPONENT_TOKEN_DEFAULTS = {
|
|
|
389
411
|
'--ngxsmk-switch-thumb-bg': 'var(--ngxsmk-color-surface)',
|
|
390
412
|
'--ngxsmk-switch-checked-bg': 'var(--ngxsmk-color-primary)',
|
|
391
413
|
},
|
|
414
|
+
checkbox: {
|
|
415
|
+
'--ngxsmk-checkbox-size': '1.125rem',
|
|
416
|
+
'--ngxsmk-checkbox-radius': 'var(--ngxsmk-radius-sm)',
|
|
417
|
+
'--ngxsmk-checkbox-border': 'var(--ngxsmk-color-outline-strong)',
|
|
418
|
+
'--ngxsmk-checkbox-checked-bg': 'var(--ngxsmk-color-primary)',
|
|
419
|
+
},
|
|
420
|
+
badge: {
|
|
421
|
+
'--ngxsmk-badge-radius': 'var(--ngxsmk-radius-full)',
|
|
422
|
+
'--ngxsmk-badge-font-weight': '600',
|
|
423
|
+
'--ngxsmk-badge-padding-inline': '0.5rem',
|
|
424
|
+
'--ngxsmk-badge-padding-block': '0.125rem',
|
|
425
|
+
'--ngxsmk-badge-border-mix': '20%',
|
|
426
|
+
},
|
|
427
|
+
alert: {
|
|
428
|
+
'--ngxsmk-alert-radius': 'var(--ngxsmk-radius-lg)',
|
|
429
|
+
'--ngxsmk-alert-border-mix': '22%',
|
|
430
|
+
'--ngxsmk-alert-padding': 'var(--ngxsmk-space-4)',
|
|
431
|
+
},
|
|
432
|
+
dialog: {
|
|
433
|
+
'--ngxsmk-dialog-radius': 'var(--ngxsmk-radius-2xl)',
|
|
434
|
+
'--ngxsmk-dialog-shadow': 'var(--ngxsmk-shadow-xl)',
|
|
435
|
+
'--ngxsmk-dialog-bg': 'var(--ngxsmk-color-surface-elevated)',
|
|
436
|
+
'--ngxsmk-dialog-backdrop': 'var(--ngxsmk-color-backdrop)',
|
|
437
|
+
'--ngxsmk-dialog-blur': '4px',
|
|
438
|
+
'--ngxsmk-dialog-max-width': '28rem',
|
|
439
|
+
},
|
|
440
|
+
sheet: {
|
|
441
|
+
'--ngxsmk-sheet-radius': 'var(--ngxsmk-radius-xl)',
|
|
442
|
+
'--ngxsmk-sheet-shadow': 'var(--ngxsmk-shadow-xl)',
|
|
443
|
+
'--ngxsmk-sheet-bg': 'var(--ngxsmk-color-surface-elevated)',
|
|
444
|
+
'--ngxsmk-sheet-backdrop': 'var(--ngxsmk-color-backdrop)',
|
|
445
|
+
'--ngxsmk-sheet-width': '24rem',
|
|
446
|
+
},
|
|
447
|
+
lightbox: {
|
|
448
|
+
'--ngxsmk-lightbox-backdrop': 'rgb(0 0 0 / 0.9)',
|
|
449
|
+
'--ngxsmk-lightbox-fg': '#ffffff',
|
|
450
|
+
'--ngxsmk-lightbox-control-bg': 'rgb(255 255 255 / 0.12)',
|
|
451
|
+
'--ngxsmk-lightbox-control-bg-hover': 'rgb(255 255 255 / 0.22)',
|
|
452
|
+
},
|
|
453
|
+
toast: {
|
|
454
|
+
'--ngxsmk-toast-radius': 'var(--ngxsmk-radius-lg)',
|
|
455
|
+
'--ngxsmk-toast-shadow': 'var(--ngxsmk-shadow-lg)',
|
|
456
|
+
'--ngxsmk-toast-bg': 'var(--ngxsmk-color-surface-elevated)',
|
|
457
|
+
'--ngxsmk-toast-accent-width': '3px',
|
|
458
|
+
},
|
|
459
|
+
table: {
|
|
460
|
+
'--ngxsmk-table-border': 'var(--ngxsmk-color-outline)',
|
|
461
|
+
'--ngxsmk-table-header-bg': 'color-mix(in srgb, var(--ngxsmk-color-surface-variant) 55%, transparent)',
|
|
462
|
+
'--ngxsmk-table-row-hover': 'var(--ngxsmk-color-surface-hover)',
|
|
463
|
+
'--ngxsmk-table-stripe': 'color-mix(in srgb, var(--ngxsmk-color-surface-variant) 35%, transparent)',
|
|
464
|
+
'--ngxsmk-table-drop-indicator': 'inset 2px 0 0 var(--ngxsmk-color-primary)',
|
|
465
|
+
'--ngxsmk-table-cell-padding': '0.625rem 0.75rem',
|
|
466
|
+
},
|
|
467
|
+
tabs: {
|
|
468
|
+
'--ngxsmk-tabs-indicator': '2px',
|
|
469
|
+
'--ngxsmk-tabs-radius': 'var(--ngxsmk-radius-md)',
|
|
470
|
+
'--ngxsmk-tabs-gap': 'var(--ngxsmk-space-1)',
|
|
471
|
+
},
|
|
472
|
+
select: {
|
|
473
|
+
'--ngxsmk-select-bg': 'var(--ngxsmk-color-surface)',
|
|
474
|
+
'--ngxsmk-select-border': 'var(--ngxsmk-color-outline-strong)',
|
|
475
|
+
'--ngxsmk-select-radius': 'var(--ngxsmk-radius-base)',
|
|
476
|
+
'--ngxsmk-select-list-shadow': 'var(--ngxsmk-shadow-lg)',
|
|
477
|
+
'--ngxsmk-select-list-radius': 'var(--ngxsmk-radius-lg)',
|
|
478
|
+
},
|
|
479
|
+
menu: {
|
|
480
|
+
'--ngxsmk-menu-bg': 'var(--ngxsmk-color-surface-elevated)',
|
|
481
|
+
'--ngxsmk-menu-radius': 'var(--ngxsmk-radius-lg)',
|
|
482
|
+
'--ngxsmk-menu-shadow': 'var(--ngxsmk-shadow-lg)',
|
|
483
|
+
'--ngxsmk-menu-border': 'var(--ngxsmk-color-outline)',
|
|
484
|
+
'--ngxsmk-menu-item-radius': 'var(--ngxsmk-radius-md)',
|
|
485
|
+
},
|
|
486
|
+
pagination: {
|
|
487
|
+
'--ngxsmk-pagination-size': '2.25rem',
|
|
488
|
+
'--ngxsmk-pagination-radius': 'var(--ngxsmk-radius-md)',
|
|
489
|
+
},
|
|
490
|
+
formField: {
|
|
491
|
+
'--ngxsmk-form-field-gap': 'var(--ngxsmk-space-1-5)',
|
|
492
|
+
'--ngxsmk-form-field-label-weight': '500',
|
|
493
|
+
},
|
|
494
|
+
radio: {
|
|
495
|
+
'--ngxsmk-radio-size': '1.125rem',
|
|
496
|
+
'--ngxsmk-radio-dot-size': '0.5rem',
|
|
497
|
+
'--ngxsmk-radio-border': 'var(--ngxsmk-color-outline-strong)',
|
|
498
|
+
},
|
|
499
|
+
dataTable: {
|
|
500
|
+
'--ngxsmk-data-table-border': 'var(--ngxsmk-color-outline)',
|
|
501
|
+
'--ngxsmk-data-table-radius': 'var(--ngxsmk-radius-md)',
|
|
502
|
+
'--ngxsmk-data-table-filter-bg': 'color-mix(in srgb, var(--ngxsmk-color-surface-variant) 40%, transparent)',
|
|
503
|
+
'--ngxsmk-data-table-filter-label-size': '0.7rem',
|
|
504
|
+
'--ngxsmk-data-table-page-size': '2rem',
|
|
505
|
+
'--ngxsmk-data-table-loading-height': '3px',
|
|
506
|
+
},
|
|
507
|
+
aiChat: {
|
|
508
|
+
'--ngxsmk-ai-chat-radius': 'var(--ngxsmk-radius-xl)',
|
|
509
|
+
'--ngxsmk-ai-chat-border': 'var(--ngxsmk-color-outline)',
|
|
510
|
+
'--ngxsmk-ai-chat-bg': 'var(--ngxsmk-color-surface)',
|
|
511
|
+
'--ngxsmk-ai-chat-header-bg': 'var(--ngxsmk-color-surface-variant)',
|
|
512
|
+
'--ngxsmk-ai-chat-bubble-bg': 'var(--ngxsmk-color-surface-variant)',
|
|
513
|
+
'--ngxsmk-ai-chat-bubble-user-bg': 'var(--ngxsmk-color-primary)',
|
|
514
|
+
'--ngxsmk-ai-chat-bubble-radius': 'var(--ngxsmk-radius-lg)',
|
|
515
|
+
'--ngxsmk-ai-chat-composer-radius': 'var(--ngxsmk-radius-full)',
|
|
516
|
+
},
|
|
517
|
+
promptInput: {
|
|
518
|
+
'--ngxsmk-prompt-radius': 'var(--ngxsmk-radius-xl)',
|
|
519
|
+
'--ngxsmk-prompt-border': 'var(--ngxsmk-color-outline-strong)',
|
|
520
|
+
'--ngxsmk-prompt-shadow': 'var(--ngxsmk-shadow-sm)',
|
|
521
|
+
'--ngxsmk-prompt-bg': 'var(--ngxsmk-color-surface)',
|
|
522
|
+
'--ngxsmk-prompt-send-radius': 'var(--ngxsmk-radius-full)',
|
|
523
|
+
},
|
|
524
|
+
chatBubble: {
|
|
525
|
+
'--ngxsmk-chat-bubble-radius': 'var(--ngxsmk-radius-lg)',
|
|
526
|
+
'--ngxsmk-chat-bubble-bg': 'var(--ngxsmk-color-surface-container)',
|
|
527
|
+
'--ngxsmk-chat-bubble-user-bg': 'var(--ngxsmk-color-primary-container)',
|
|
528
|
+
'--ngxsmk-chat-bubble-padding': 'var(--ngxsmk-space-2) var(--ngxsmk-space-3)',
|
|
529
|
+
},
|
|
392
530
|
spreadsheet: {
|
|
393
531
|
'--ngxsmk-spreadsheet-bg': 'var(--ngxsmk-color-surface)',
|
|
394
532
|
'--ngxsmk-spreadsheet-header-bg': 'var(--ngxsmk-color-surface-variant)',
|
|
@@ -420,16 +558,22 @@ const COMPONENT_TOKEN_DEFAULTS = {
|
|
|
420
558
|
'--ngxsmk-input-group-radius': 'var(--ngxsmk-radius-md)',
|
|
421
559
|
'--ngxsmk-input-group-shadow': 'none',
|
|
422
560
|
'--ngxsmk-input-group-focus-border': 'var(--ngxsmk-color-primary)',
|
|
423
|
-
'--ngxsmk-input-group-focus-shadow': '
|
|
561
|
+
'--ngxsmk-input-group-focus-shadow': 'var(--ngxsmk-focus-ring)',
|
|
424
562
|
'--ngxsmk-input-group-error-border': 'var(--ngxsmk-color-error)',
|
|
425
|
-
'--ngxsmk-input-group-error-focus-shadow': '
|
|
563
|
+
'--ngxsmk-input-group-error-focus-shadow': 'var(--ngxsmk-focus-ring-error)',
|
|
426
564
|
'--ngxsmk-input-group-success-border': 'var(--ngxsmk-color-success)',
|
|
427
565
|
'--ngxsmk-input-group-warning-border': 'var(--ngxsmk-color-warning)',
|
|
428
|
-
'--ngxsmk-input-group-height': '
|
|
566
|
+
'--ngxsmk-input-group-height': 'var(--ngxsmk-control-height)',
|
|
429
567
|
'--ngxsmk-input-group-padding': '0.75rem',
|
|
430
568
|
'--ngxsmk-input-group-gap': 'var(--ngxsmk-space-2)',
|
|
431
569
|
'--ngxsmk-input-group-font': 'var(--ngxsmk-font-sans)',
|
|
432
570
|
},
|
|
571
|
+
heatmap: {
|
|
572
|
+
'--ngxsmk-heatmap-level-1': 'color-mix(in srgb, var(--ngxsmk-color-success) 28%, var(--ngxsmk-color-surface-variant))',
|
|
573
|
+
'--ngxsmk-heatmap-level-2': 'color-mix(in srgb, var(--ngxsmk-color-success) 52%, var(--ngxsmk-color-surface-variant))',
|
|
574
|
+
'--ngxsmk-heatmap-level-3': 'color-mix(in srgb, var(--ngxsmk-color-success) 76%, var(--ngxsmk-color-surface-variant))',
|
|
575
|
+
'--ngxsmk-heatmap-level-4': 'var(--ngxsmk-color-success)',
|
|
576
|
+
},
|
|
433
577
|
};
|
|
434
578
|
/** Tokens that do not change between light and dark mode. */
|
|
435
579
|
function staticVars(theme) {
|
|
@@ -454,6 +598,12 @@ function staticVars(theme) {
|
|
|
454
598
|
'--ngxsmk-focus-ring': 'var(--ngxsmk-shadow-focus)',
|
|
455
599
|
'--ngxsmk-focus-ring-error': '0 0 0 2px color-mix(in srgb, var(--ngxsmk-color-error) 15%, transparent), ' +
|
|
456
600
|
'0 0 0 4px color-mix(in srgb, var(--ngxsmk-color-error) 45%, transparent)',
|
|
601
|
+
// Mobile viewport & safe-area tokens (iOS / Android / Ionic)
|
|
602
|
+
'--ngxsmk-safe-area-top': 'env(safe-area-inset-top, 0px)',
|
|
603
|
+
'--ngxsmk-safe-area-bottom': 'env(safe-area-inset-bottom, 0px)',
|
|
604
|
+
'--ngxsmk-safe-area-left': 'env(safe-area-inset-left, 0px)',
|
|
605
|
+
'--ngxsmk-safe-area-right': 'env(safe-area-inset-right, 0px)',
|
|
606
|
+
'--ngxsmk-touch-target-min': '44px',
|
|
457
607
|
// Component-specific token defaults
|
|
458
608
|
...Object.values(COMPONENT_TOKEN_DEFAULTS).reduce((merged, tokens) => ({ ...merged, ...tokens }), {}),
|
|
459
609
|
};
|
|
@@ -532,12 +682,17 @@ function lightVars(theme) {
|
|
|
532
682
|
'--ngxsmk-color-on-surface': '#0A1317',
|
|
533
683
|
'--ngxsmk-color-surface-variant': theme.neutral[100],
|
|
534
684
|
'--ngxsmk-color-on-surface-variant': theme.neutral[500],
|
|
685
|
+
'--ngxsmk-color-surface-muted': theme.neutral[50],
|
|
686
|
+
'--ngxsmk-color-surface-elevated': '#FFFFFF',
|
|
687
|
+
'--ngxsmk-color-surface-overlay': '#FFFFFF',
|
|
688
|
+
'--ngxsmk-color-on-surface-muted': theme.neutral[400],
|
|
535
689
|
'--ngxsmk-color-surface-hover': 'rgb(5 54 89 / 0.05)',
|
|
536
690
|
'--ngxsmk-color-surface-active': 'rgb(5 54 89 / 0.1)',
|
|
537
691
|
'--ngxsmk-color-surface-container': '#FFFFFF',
|
|
538
692
|
'--ngxsmk-color-outline': 'rgb(5 54 89 / 0.1)',
|
|
539
693
|
'--ngxsmk-color-outline-strong': theme.neutral[200],
|
|
540
694
|
'--ngxsmk-color-outline-variant': 'rgb(5 54 89 / 0.06)',
|
|
695
|
+
'--ngxsmk-color-outline-subtle': 'rgb(5 54 89 / 0.05)',
|
|
541
696
|
// Elevation ladder
|
|
542
697
|
'--ngxsmk-color-surface-1': '#FFFFFF',
|
|
543
698
|
'--ngxsmk-color-surface-2': theme.neutral[50],
|
|
@@ -599,12 +754,17 @@ function darkVars(theme) {
|
|
|
599
754
|
'--ngxsmk-color-on-surface': '#DFE2E5',
|
|
600
755
|
'--ngxsmk-color-surface-variant': '#28292C',
|
|
601
756
|
'--ngxsmk-color-on-surface-variant': '#AAAFB5',
|
|
757
|
+
'--ngxsmk-color-surface-muted': '#141416',
|
|
758
|
+
'--ngxsmk-color-surface-elevated': '#28292C',
|
|
759
|
+
'--ngxsmk-color-surface-overlay': '#2F3136',
|
|
760
|
+
'--ngxsmk-color-on-surface-muted': '#777C85',
|
|
602
761
|
'--ngxsmk-color-surface-hover': 'rgb(255 255 255 / 0.05)',
|
|
603
762
|
'--ngxsmk-color-surface-active': 'rgb(255 255 255 / 0.1)',
|
|
604
763
|
'--ngxsmk-color-surface-container': '#28292C',
|
|
605
764
|
'--ngxsmk-color-outline': 'rgb(242 244 246 / 0.1)',
|
|
606
765
|
'--ngxsmk-color-outline-strong': '#494D53',
|
|
607
766
|
'--ngxsmk-color-outline-variant': 'rgb(242 244 246 / 0.06)',
|
|
767
|
+
'--ngxsmk-color-outline-subtle': 'rgb(242 244 246 / 0.05)',
|
|
608
768
|
// Elevation ladder
|
|
609
769
|
'--ngxsmk-color-surface-1': theme.neutral[800],
|
|
610
770
|
'--ngxsmk-color-surface-2': theme.neutral[700],
|
|
@@ -738,6 +898,9 @@ const neutralPreset = {
|
|
|
738
898
|
/**
|
|
739
899
|
* Default preset: Emerald green. This preset generates the prebuilt
|
|
740
900
|
* `ngxsmk.css` stylesheet shipped with @ngxsmk/theme.
|
|
901
|
+
*
|
|
902
|
+
* **Classic contract:** keep this visually stable for existing consumers.
|
|
903
|
+
* Premium Ink lives in `inkPreset` / `ngxsmk.ink.css` as an opt-in.
|
|
741
904
|
*/
|
|
742
905
|
const emeraldPreset = {
|
|
743
906
|
name: 'emerald',
|
|
@@ -747,59 +910,298 @@ const rosePreset = {
|
|
|
747
910
|
name: 'rose',
|
|
748
911
|
brand: { primary: '#E11D48' },
|
|
749
912
|
};
|
|
913
|
+
/**
|
|
914
|
+
* Cool graphite neutrals for the Ink & precision look. Independent of the
|
|
915
|
+
* Classic `DEFAULT_NEUTRAL` so emerald/violet/rose stay unchanged.
|
|
916
|
+
*/
|
|
917
|
+
const INK_NEUTRAL = {
|
|
918
|
+
50: '#F6F7F8',
|
|
919
|
+
100: '#EBEDF0',
|
|
920
|
+
200: '#D5D8DE',
|
|
921
|
+
300: '#B0B5BF',
|
|
922
|
+
400: '#7E8491',
|
|
923
|
+
500: '#5A606C',
|
|
924
|
+
600: '#454954',
|
|
925
|
+
700: '#33363E',
|
|
926
|
+
800: '#1C1E24',
|
|
927
|
+
900: '#12141A',
|
|
928
|
+
950: '#0A0B0E',
|
|
929
|
+
};
|
|
930
|
+
/**
|
|
931
|
+
* Opt-in premium preset: Ink & precision.
|
|
932
|
+
* Generates `ngxsmk.ink.css`. Does not replace the default `ngxsmk.css`.
|
|
933
|
+
*
|
|
934
|
+
* Consumers:
|
|
935
|
+
* ```css
|
|
936
|
+
* @import '@ngxsmk/theme/styles/ngxsmk.ink.css';
|
|
937
|
+
* ```
|
|
938
|
+
* or `theme.applyTheme(inkPreset)`.
|
|
939
|
+
*/
|
|
940
|
+
const inkPreset = {
|
|
941
|
+
name: 'ink',
|
|
942
|
+
brand: { primary: '#0F766E' },
|
|
943
|
+
neutral: INK_NEUTRAL,
|
|
944
|
+
typography: {
|
|
945
|
+
fontFamily: {
|
|
946
|
+
sans: "'Geist Sans', 'Geist Sans Fallback', system-ui, -apple-system, sans-serif",
|
|
947
|
+
mono: "'Geist Mono', 'Geist Mono Fallback', monospace",
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
borderRadius: 'md',
|
|
951
|
+
radiusScale: {
|
|
952
|
+
sm: '0.1875rem', // 3px
|
|
953
|
+
md: '0.375rem', // 6px — element base
|
|
954
|
+
lg: '0.5rem', // 8px — cards / popovers
|
|
955
|
+
xl: '0.625rem', // 10px
|
|
956
|
+
'2xl': '0.75rem', // 12px
|
|
957
|
+
'3xl': '1rem', // 16px — page surfaces (was 28px in Classic)
|
|
958
|
+
},
|
|
959
|
+
shadowScale: {
|
|
960
|
+
sm: '0 0 0 1px rgb(0 0 0 / 0.04), 0 1px 2px rgb(0 0 0 / 0.04)',
|
|
961
|
+
md: '0 0 0 1px rgb(0 0 0 / 0.04), 0 1px 2px rgb(0 0 0 / 0.05), 0 4px 10px rgb(0 0 0 / 0.05)',
|
|
962
|
+
lg: '0 0 0 1px rgb(0 0 0 / 0.05), 0 2px 4px rgb(0 0 0 / 0.05), 0 10px 28px rgb(0 0 0 / 0.08)',
|
|
963
|
+
xl: '0 0 0 1px rgb(0 0 0 / 0.05), 0 4px 8px rgb(0 0 0 / 0.06), 0 18px 40px rgb(0 0 0 / 0.1)',
|
|
964
|
+
'2xl': '0 0 0 1px rgb(0 0 0 / 0.06), 0 8px 16px rgb(0 0 0 / 0.08), 0 28px 56px rgb(0 0 0 / 0.12)',
|
|
965
|
+
},
|
|
966
|
+
overrides: {
|
|
967
|
+
light: {
|
|
968
|
+
// Graphite canvas — quieter than Classic cool-blue wash.
|
|
969
|
+
'--ngxsmk-color-background': '#F4F5F7',
|
|
970
|
+
'--ngxsmk-color-surface': '#FFFFFF',
|
|
971
|
+
'--ngxsmk-color-surface-elevated': '#FFFFFF',
|
|
972
|
+
'--ngxsmk-color-surface-overlay': '#FFFFFF',
|
|
973
|
+
'--ngxsmk-color-surface-variant': '#EBEDF0',
|
|
974
|
+
'--ngxsmk-color-on-background': '#0A0B0E',
|
|
975
|
+
'--ngxsmk-color-on-surface': '#0A0B0E',
|
|
976
|
+
'--ngxsmk-color-on-surface-variant': '#5A606C',
|
|
977
|
+
'--ngxsmk-color-surface-hover': 'rgb(10 11 14 / 0.04)',
|
|
978
|
+
'--ngxsmk-color-surface-active': 'rgb(10 11 14 / 0.08)',
|
|
979
|
+
'--ngxsmk-color-outline': 'rgb(10 11 14 / 0.1)',
|
|
980
|
+
'--ngxsmk-color-outline-strong': '#D5D8DE',
|
|
981
|
+
'--ngxsmk-color-outline-variant': 'rgb(10 11 14 / 0.06)',
|
|
982
|
+
'--ngxsmk-color-outline-subtle': 'rgb(10 11 14 / 0.05)',
|
|
983
|
+
'--ngxsmk-color-backdrop': 'rgb(10 11 14 / 0.48)',
|
|
984
|
+
'--ngxsmk-color-ring': '#0F766E',
|
|
985
|
+
// Precision focus — tighter than Classic double ring.
|
|
986
|
+
'--ngxsmk-shadow-focus': '0 0 0 1px #FFFFFF, 0 0 0 3px color-mix(in srgb, #0F766E 55%, transparent)',
|
|
987
|
+
// Density.
|
|
988
|
+
'--ngxsmk-control-height': '2.25rem',
|
|
989
|
+
'--ngxsmk-control-height-sm': '1.875rem',
|
|
990
|
+
'--ngxsmk-control-height-md': '2.25rem',
|
|
991
|
+
'--ngxsmk-control-height-lg': '2.75rem',
|
|
992
|
+
// Component chrome — border-first, no float.
|
|
993
|
+
'--ngxsmk-hover-lift': 'none',
|
|
994
|
+
'--ngxsmk-button-font-weight': '600',
|
|
995
|
+
'--ngxsmk-button-radius': 'var(--ngxsmk-radius-md)',
|
|
996
|
+
'--ngxsmk-input-border': 'var(--ngxsmk-color-outline)',
|
|
997
|
+
'--ngxsmk-input-height': 'var(--ngxsmk-control-height)',
|
|
998
|
+
'--ngxsmk-input-group-height': '2.25rem',
|
|
999
|
+
'--ngxsmk-input-group-border': 'var(--ngxsmk-color-outline)',
|
|
1000
|
+
'--ngxsmk-card-shadow': 'none',
|
|
1001
|
+
'--ngxsmk-card-radius': 'var(--ngxsmk-radius-lg)',
|
|
1002
|
+
'--ngxsmk-card-border-width': '1px',
|
|
1003
|
+
'--ngxsmk-dialog-radius': 'var(--ngxsmk-radius-lg)',
|
|
1004
|
+
'--ngxsmk-dialog-blur': '8px',
|
|
1005
|
+
'--ngxsmk-sheet-radius': 'var(--ngxsmk-radius-lg)',
|
|
1006
|
+
'--ngxsmk-toast-radius': 'var(--ngxsmk-radius-md)',
|
|
1007
|
+
'--ngxsmk-toast-accent-width': '2px',
|
|
1008
|
+
'--ngxsmk-select-border': 'var(--ngxsmk-color-outline)',
|
|
1009
|
+
'--ngxsmk-menu-radius': 'var(--ngxsmk-radius-md)',
|
|
1010
|
+
'--ngxsmk-pagination-size': '2rem',
|
|
1011
|
+
'--ngxsmk-pagination-radius': 'var(--ngxsmk-radius-md)',
|
|
1012
|
+
'--ngxsmk-table-header-bg': 'color-mix(in srgb, var(--ngxsmk-color-surface-variant) 70%, transparent)',
|
|
1013
|
+
'--ngxsmk-badge-font-weight': '650',
|
|
1014
|
+
'--ngxsmk-progress-height': '0.375rem',
|
|
1015
|
+
// Forms — denser labels, quieter group chrome.
|
|
1016
|
+
'--ngxsmk-form-field-gap': 'var(--ngxsmk-space-1)',
|
|
1017
|
+
'--ngxsmk-form-field-label-weight': '600',
|
|
1018
|
+
'--ngxsmk-input-group-radius': 'var(--ngxsmk-radius-md)',
|
|
1019
|
+
'--ngxsmk-input-group-padding': '0.625rem',
|
|
1020
|
+
'--ngxsmk-radio-border': 'var(--ngxsmk-color-outline)',
|
|
1021
|
+
// Data table — ops density.
|
|
1022
|
+
'--ngxsmk-data-table-radius': 'var(--ngxsmk-radius-md)',
|
|
1023
|
+
'--ngxsmk-data-table-filter-bg': 'color-mix(in srgb, var(--ngxsmk-color-surface-variant) 55%, transparent)',
|
|
1024
|
+
'--ngxsmk-data-table-page-size': '1.875rem',
|
|
1025
|
+
'--ngxsmk-data-table-loading-height': '2px',
|
|
1026
|
+
// AI surfaces — border-first console, no soft float.
|
|
1027
|
+
'--ngxsmk-ai-chat-radius': 'var(--ngxsmk-radius-lg)',
|
|
1028
|
+
'--ngxsmk-ai-chat-header-bg': 'var(--ngxsmk-color-surface)',
|
|
1029
|
+
'--ngxsmk-ai-chat-bubble-bg': 'var(--ngxsmk-color-surface-variant)',
|
|
1030
|
+
'--ngxsmk-ai-chat-bubble-radius': 'var(--ngxsmk-radius-md)',
|
|
1031
|
+
'--ngxsmk-ai-chat-composer-radius': 'var(--ngxsmk-radius-md)',
|
|
1032
|
+
'--ngxsmk-prompt-radius': 'var(--ngxsmk-radius-lg)',
|
|
1033
|
+
'--ngxsmk-prompt-border': 'var(--ngxsmk-color-outline)',
|
|
1034
|
+
'--ngxsmk-prompt-shadow': 'none',
|
|
1035
|
+
'--ngxsmk-prompt-send-radius': 'var(--ngxsmk-radius-md)',
|
|
1036
|
+
'--ngxsmk-chat-bubble-radius': 'var(--ngxsmk-radius-md)',
|
|
1037
|
+
'--ngxsmk-chat-bubble-padding': 'var(--ngxsmk-space-2) var(--ngxsmk-space-3)',
|
|
1038
|
+
},
|
|
1039
|
+
dark: {
|
|
1040
|
+
'--ngxsmk-color-background': '#0A0B0E',
|
|
1041
|
+
'--ngxsmk-color-surface': '#12141A',
|
|
1042
|
+
'--ngxsmk-color-surface-elevated': '#1C1E24',
|
|
1043
|
+
'--ngxsmk-color-surface-overlay': '#1C1E24',
|
|
1044
|
+
'--ngxsmk-color-surface-container': '#12141A',
|
|
1045
|
+
'--ngxsmk-color-surface-variant': '#1C1E24',
|
|
1046
|
+
'--ngxsmk-color-surface-1': '#12141A',
|
|
1047
|
+
'--ngxsmk-color-surface-2': '#1C1E24',
|
|
1048
|
+
'--ngxsmk-color-outline': 'rgb(246 247 248 / 0.12)',
|
|
1049
|
+
'--ngxsmk-color-outline-strong': 'rgb(246 247 248 / 0.18)',
|
|
1050
|
+
'--ngxsmk-color-outline-variant': 'rgb(246 247 248 / 0.08)',
|
|
1051
|
+
'--ngxsmk-color-backdrop': 'rgb(0 0 0 / 0.62)',
|
|
1052
|
+
'--ngxsmk-shadow-focus': '0 0 0 1px #12141A, 0 0 0 3px color-mix(in srgb, #2DD4BF 50%, transparent)',
|
|
1053
|
+
'--ngxsmk-hover-lift': 'none',
|
|
1054
|
+
'--ngxsmk-control-height': '2.25rem',
|
|
1055
|
+
'--ngxsmk-control-height-sm': '1.875rem',
|
|
1056
|
+
'--ngxsmk-control-height-md': '2.25rem',
|
|
1057
|
+
'--ngxsmk-control-height-lg': '2.75rem',
|
|
1058
|
+
'--ngxsmk-button-font-weight': '600',
|
|
1059
|
+
'--ngxsmk-card-shadow': 'none',
|
|
1060
|
+
'--ngxsmk-card-radius': 'var(--ngxsmk-radius-lg)',
|
|
1061
|
+
'--ngxsmk-dialog-radius': 'var(--ngxsmk-radius-lg)',
|
|
1062
|
+
'--ngxsmk-input-border': 'var(--ngxsmk-color-outline)',
|
|
1063
|
+
'--ngxsmk-select-border': 'var(--ngxsmk-color-outline)',
|
|
1064
|
+
'--ngxsmk-form-field-label-weight': '600',
|
|
1065
|
+
'--ngxsmk-input-group-height': '2.25rem',
|
|
1066
|
+
'--ngxsmk-ai-chat-radius': 'var(--ngxsmk-radius-lg)',
|
|
1067
|
+
'--ngxsmk-ai-chat-header-bg': 'var(--ngxsmk-color-surface)',
|
|
1068
|
+
'--ngxsmk-prompt-shadow': 'none',
|
|
1069
|
+
'--ngxsmk-prompt-border': 'var(--ngxsmk-color-outline)',
|
|
1070
|
+
'--ngxsmk-prompt-send-radius': 'var(--ngxsmk-radius-md)',
|
|
1071
|
+
'--ngxsmk-data-table-filter-bg': 'color-mix(in srgb, var(--ngxsmk-color-surface-variant) 70%, transparent)',
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
};
|
|
750
1075
|
const presets = {
|
|
751
1076
|
violet: violetPreset,
|
|
752
1077
|
neutral: neutralPreset,
|
|
753
1078
|
emerald: emeraldPreset,
|
|
754
1079
|
rose: rosePreset,
|
|
1080
|
+
ink: inkPreset,
|
|
755
1081
|
};
|
|
756
1082
|
|
|
757
1083
|
/**
|
|
758
|
-
* Maps NGXSMK semantic color roles to Ionic
|
|
759
|
-
*
|
|
760
|
-
* Ionic expects:
|
|
761
|
-
* --ion-color-primary, --ion-color-primary-rgb, --ion-color-primary-shade,
|
|
762
|
-
* --ion-color-primary-tint, --ion-color-secondary, etc.
|
|
1084
|
+
* Maps NGXSMK semantic color roles to Ionic 7 & 8 color variable specifications.
|
|
763
1085
|
*
|
|
764
|
-
*
|
|
1086
|
+
* Emits full color suites (base, rgb, contrast, contrast-rgb, shade, tint),
|
|
1087
|
+
* 19-step contrast ladder (--ion-color-step-50..950), safe area mappings, and
|
|
1088
|
+
* surface variables for seamless Ionic framework interoperability.
|
|
765
1089
|
*/
|
|
766
1090
|
const ionicVarsAdapter = {
|
|
767
1091
|
name: 'ionic',
|
|
768
|
-
vars(theme) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
vars['--ion-color-primary'] = theme.brandBase;
|
|
772
|
-
vars['--ion-color-primary-rgb'] = hexToRgbStr(theme.brandBase);
|
|
773
|
-
vars['--ion-color-primary-shade'] = shiftLightnessHex(theme.brandBase, -14);
|
|
774
|
-
vars['--ion-color-primary-tint'] = shiftLightnessHex(theme.brandBase, 8);
|
|
775
|
-
// Secondary
|
|
776
|
-
vars['--ion-color-secondary'] = theme.secondaryBase;
|
|
777
|
-
vars['--ion-color-secondary-rgb'] = hexToRgbStr(theme.secondaryBase);
|
|
778
|
-
vars['--ion-color-secondary-shade'] = shiftLightnessHex(theme.secondaryBase, -14);
|
|
779
|
-
vars['--ion-color-secondary-tint'] = shiftLightnessHex(theme.secondaryBase, 8);
|
|
780
|
-
// Tertiary (derived from brand hue + 64)
|
|
781
|
-
const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
|
|
782
|
-
vars['--ion-color-tertiary'] = tertiary[500];
|
|
783
|
-
vars['--ion-color-tertiary-rgb'] = hexToRgbStr(tertiary[500]);
|
|
784
|
-
vars['--ion-color-tertiary-shade'] = tertiary[600];
|
|
785
|
-
vars['--ion-color-tertiary-tint'] = tertiary[400];
|
|
786
|
-
// Semantic status colors
|
|
787
|
-
for (const [name, hex] of Object.entries(theme.semantic)) {
|
|
788
|
-
vars[`--ion-color-${name}`] = hex;
|
|
789
|
-
vars[`--ion-color-${name}-rgb`] = hexToRgbStr(hex);
|
|
1092
|
+
vars(theme, options) {
|
|
1093
|
+
if (options?.isDark) {
|
|
1094
|
+
return this.darkVars ? this.darkVars(theme) : buildLightVars(theme);
|
|
790
1095
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
vars['--ion-text-color-rgb'] = '10, 19, 23';
|
|
796
|
-
// Typography
|
|
797
|
-
vars['--ion-font-family'] = theme.fontFamily.sans;
|
|
798
|
-
// Border radius (Ionic uses --ion-border-radius)
|
|
799
|
-
vars['--ion-border-radius'] = theme.radius[theme.radiusBase];
|
|
800
|
-
return vars;
|
|
1096
|
+
return buildLightVars(theme);
|
|
1097
|
+
},
|
|
1098
|
+
darkVars(theme) {
|
|
1099
|
+
return buildDarkVars(theme);
|
|
801
1100
|
},
|
|
802
1101
|
};
|
|
1102
|
+
function buildColorSuite(prefix, baseHex, vars) {
|
|
1103
|
+
const contrastHex = getContrastHex(baseHex);
|
|
1104
|
+
vars[`--ion-color-${prefix}`] = baseHex;
|
|
1105
|
+
vars[`--ion-color-${prefix}-rgb`] = hexToRgbStr(baseHex);
|
|
1106
|
+
vars[`--ion-color-${prefix}-contrast`] = contrastHex;
|
|
1107
|
+
vars[`--ion-color-${prefix}-contrast-rgb`] = hexToRgbStr(contrastHex);
|
|
1108
|
+
vars[`--ion-color-${prefix}-shade`] = shiftLightnessHex(baseHex, -12);
|
|
1109
|
+
vars[`--ion-color-${prefix}-tint`] = shiftLightnessHex(baseHex, 10);
|
|
1110
|
+
}
|
|
1111
|
+
function buildLightVars(theme) {
|
|
1112
|
+
const vars = {};
|
|
1113
|
+
// Primary & Secondary
|
|
1114
|
+
buildColorSuite('primary', theme.brandBase, vars);
|
|
1115
|
+
buildColorSuite('secondary', theme.secondaryBase, vars);
|
|
1116
|
+
// Tertiary (derived from brand hue + 64)
|
|
1117
|
+
const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
|
|
1118
|
+
buildColorSuite('tertiary', tertiary[500], vars);
|
|
1119
|
+
// Status & Feedback colors
|
|
1120
|
+
const successHex = theme.semantic['success'] ?? '#0D8626';
|
|
1121
|
+
const warningHex = theme.semantic['warning'] ?? '#E9AF08';
|
|
1122
|
+
const dangerHex = theme.semantic['error'] ?? '#E3193B';
|
|
1123
|
+
const infoHex = theme.semantic['info'] ?? '#2563EB';
|
|
1124
|
+
buildColorSuite('success', successHex, vars);
|
|
1125
|
+
buildColorSuite('warning', warningHex, vars);
|
|
1126
|
+
buildColorSuite('danger', dangerHex, vars);
|
|
1127
|
+
buildColorSuite('info', infoHex, vars);
|
|
1128
|
+
// Medium / Light / Dark baseline scales
|
|
1129
|
+
buildColorSuite('light', theme.neutral[100] ?? '#F1F4F7', vars);
|
|
1130
|
+
buildColorSuite('medium', theme.neutral[500] ?? '#778593', vars);
|
|
1131
|
+
buildColorSuite('dark', theme.neutral[900] ?? '#18181A', vars);
|
|
1132
|
+
// Background / Surfaces
|
|
1133
|
+
const bgHex = theme.neutral[50] ?? '#FFFFFF';
|
|
1134
|
+
const textHex = '#0A1317';
|
|
1135
|
+
vars['--ion-background-color'] = bgHex;
|
|
1136
|
+
vars['--ion-background-color-rgb'] = hexToRgbStr(bgHex);
|
|
1137
|
+
vars['--ion-text-color'] = textHex;
|
|
1138
|
+
vars['--ion-text-color-rgb'] = hexToRgbStr(textHex);
|
|
1139
|
+
vars['--ion-card-background'] = '#FFFFFF';
|
|
1140
|
+
vars['--ion-item-background'] = '#FFFFFF';
|
|
1141
|
+
vars['--ion-item-color'] = textHex;
|
|
1142
|
+
vars['--ion-toolbar-background'] = bgHex;
|
|
1143
|
+
vars['--ion-toolbar-color'] = textHex;
|
|
1144
|
+
vars['--ion-tab-bar-background'] = '#FFFFFF';
|
|
1145
|
+
vars['--ion-border-color'] = theme.neutral[200] ?? '#CCD3DB';
|
|
1146
|
+
// Typography & Radius
|
|
1147
|
+
vars['--ion-font-family'] = theme.fontFamily.sans;
|
|
1148
|
+
vars['--ion-border-radius'] = theme.radius[theme.radiusBase];
|
|
1149
|
+
// Stepped colors for light mode (interpolated between bg and text)
|
|
1150
|
+
interpolateSteppedColors(bgHex, textHex, vars);
|
|
1151
|
+
// Safe area insets
|
|
1152
|
+
vars['--ion-safe-area-top'] = 'var(--ngxsmk-safe-area-top, env(safe-area-inset-top, 0px))';
|
|
1153
|
+
vars['--ion-safe-area-bottom'] =
|
|
1154
|
+
'var(--ngxsmk-safe-area-bottom, env(safe-area-inset-bottom, 0px))';
|
|
1155
|
+
vars['--ion-safe-area-left'] = 'var(--ngxsmk-safe-area-left, env(safe-area-inset-left, 0px))';
|
|
1156
|
+
vars['--ion-safe-area-right'] = 'var(--ngxsmk-safe-area-right, env(safe-area-inset-right, 0px))';
|
|
1157
|
+
return vars;
|
|
1158
|
+
}
|
|
1159
|
+
function buildDarkVars(theme) {
|
|
1160
|
+
const vars = {};
|
|
1161
|
+
// Primary & Secondary dark adjustments
|
|
1162
|
+
const primaryDark = theme.brand[400] ?? theme.brandBase;
|
|
1163
|
+
const secondaryDark = theme.secondary[400] ?? theme.secondaryBase;
|
|
1164
|
+
buildColorSuite('primary', primaryDark, vars);
|
|
1165
|
+
buildColorSuite('secondary', secondaryDark, vars);
|
|
1166
|
+
// Tertiary
|
|
1167
|
+
const tertiary = deriveScale(rotateHue(theme.brandBase, 64));
|
|
1168
|
+
buildColorSuite('tertiary', tertiary[400] ?? tertiary[500], vars);
|
|
1169
|
+
// Status & Feedback
|
|
1170
|
+
buildColorSuite('success', shiftLightnessHex(theme.semantic['success'] ?? '#0D8626', 10), vars);
|
|
1171
|
+
buildColorSuite('warning', shiftLightnessHex(theme.semantic['warning'] ?? '#E9AF08', 8), vars);
|
|
1172
|
+
buildColorSuite('danger', shiftLightnessHex(theme.semantic['error'] ?? '#E3193B', 10), vars);
|
|
1173
|
+
buildColorSuite('info', shiftLightnessHex(theme.semantic['info'] ?? '#2563EB', 10), vars);
|
|
1174
|
+
buildColorSuite('light', theme.neutral[800] ?? '#1F1F22', vars);
|
|
1175
|
+
buildColorSuite('medium', theme.neutral[400] ?? '#778593', vars);
|
|
1176
|
+
buildColorSuite('dark', theme.neutral[100] ?? '#F1F4F7', vars);
|
|
1177
|
+
// Dark backgrounds & surfaces
|
|
1178
|
+
const darkBgHex = '#111112';
|
|
1179
|
+
const darkTextHex = '#DFE2E5';
|
|
1180
|
+
const darkSurface = '#1F1F22';
|
|
1181
|
+
vars['--ion-background-color'] = darkBgHex;
|
|
1182
|
+
vars['--ion-background-color-rgb'] = hexToRgbStr(darkBgHex);
|
|
1183
|
+
vars['--ion-text-color'] = darkTextHex;
|
|
1184
|
+
vars['--ion-text-color-rgb'] = hexToRgbStr(darkTextHex);
|
|
1185
|
+
vars['--ion-card-background'] = darkSurface;
|
|
1186
|
+
vars['--ion-item-background'] = darkSurface;
|
|
1187
|
+
vars['--ion-item-color'] = darkTextHex;
|
|
1188
|
+
vars['--ion-toolbar-background'] = '#18181A';
|
|
1189
|
+
vars['--ion-toolbar-color'] = darkTextHex;
|
|
1190
|
+
vars['--ion-tab-bar-background'] = '#18181A';
|
|
1191
|
+
vars['--ion-border-color'] = 'rgba(242, 244, 246, 0.1)';
|
|
1192
|
+
// Typography & Radius
|
|
1193
|
+
vars['--ion-font-family'] = theme.fontFamily.sans;
|
|
1194
|
+
vars['--ion-border-radius'] = theme.radius[theme.radiusBase];
|
|
1195
|
+
// Stepped colors for dark mode
|
|
1196
|
+
interpolateSteppedColors(darkBgHex, darkTextHex, vars);
|
|
1197
|
+
// Safe area insets
|
|
1198
|
+
vars['--ion-safe-area-top'] = 'var(--ngxsmk-safe-area-top, env(safe-area-inset-top, 0px))';
|
|
1199
|
+
vars['--ion-safe-area-bottom'] =
|
|
1200
|
+
'var(--ngxsmk-safe-area-bottom, env(safe-area-inset-bottom, 0px))';
|
|
1201
|
+
vars['--ion-safe-area-left'] = 'var(--ngxsmk-safe-area-left, env(safe-area-inset-left, 0px))';
|
|
1202
|
+
vars['--ion-safe-area-right'] = 'var(--ngxsmk-safe-area-right, env(safe-area-inset-right, 0px))';
|
|
1203
|
+
return vars;
|
|
1204
|
+
}
|
|
803
1205
|
/** Default NGXSMK adapter — emits `--ngxsmk-*` variables. */
|
|
804
1206
|
const cssVarsAdapter = {
|
|
805
1207
|
name: 'ngxsmk',
|
|
@@ -807,18 +1209,26 @@ const cssVarsAdapter = {
|
|
|
807
1209
|
};
|
|
808
1210
|
// ─── Helpers ──────────────────────────────────────────────
|
|
809
1211
|
function hexToRgbStr(hex) {
|
|
810
|
-
const
|
|
811
|
-
const r = parseInt(
|
|
812
|
-
const g = parseInt(
|
|
813
|
-
const b = parseInt(
|
|
1212
|
+
const clean = hex.replace('#', '');
|
|
1213
|
+
const r = parseInt(clean.substring(0, 2), 16) || 0;
|
|
1214
|
+
const g = parseInt(clean.substring(2, 4), 16) || 0;
|
|
1215
|
+
const b = parseInt(clean.substring(4, 6), 16) || 0;
|
|
814
1216
|
return `${r}, ${g}, ${b}`;
|
|
815
1217
|
}
|
|
1218
|
+
function getContrastHex(hex) {
|
|
1219
|
+
const clean = hex.replace('#', '');
|
|
1220
|
+
const r = parseInt(clean.substring(0, 2), 16) || 0;
|
|
1221
|
+
const g = parseInt(clean.substring(2, 4), 16) || 0;
|
|
1222
|
+
const b = parseInt(clean.substring(4, 6), 16) || 0;
|
|
1223
|
+
// Standard WCAG relative luminance approximation
|
|
1224
|
+
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
|
1225
|
+
return yiq >= 150 ? '#000000' : '#FFFFFF';
|
|
1226
|
+
}
|
|
816
1227
|
function shiftLightnessHex(hex, amount) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
let
|
|
820
|
-
let
|
|
821
|
-
let b = parseInt(h.substring(4, 6), 16);
|
|
1228
|
+
const clean = hex.replace('#', '');
|
|
1229
|
+
let r = parseInt(clean.substring(0, 2), 16) || 0;
|
|
1230
|
+
let g = parseInt(clean.substring(2, 4), 16) || 0;
|
|
1231
|
+
let b = parseInt(clean.substring(4, 6), 16) || 0;
|
|
822
1232
|
if (amount > 0) {
|
|
823
1233
|
r = Math.min(255, r + Math.round((255 - r) * (amount / 100)));
|
|
824
1234
|
g = Math.min(255, g + Math.round((255 - g) * (amount / 100)));
|
|
@@ -830,7 +1240,28 @@ function shiftLightnessHex(hex, amount) {
|
|
|
830
1240
|
g = Math.max(0, Math.round(g * factor));
|
|
831
1241
|
b = Math.max(0, Math.round(b * factor));
|
|
832
1242
|
}
|
|
833
|
-
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}
|
|
1243
|
+
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
|
|
1244
|
+
}
|
|
1245
|
+
function interpolateSteppedColors(bgHex, textHex, vars) {
|
|
1246
|
+
const cleanBg = bgHex.replace('#', '');
|
|
1247
|
+
const cleanText = textHex.replace('#', '');
|
|
1248
|
+
const bgR = parseInt(cleanBg.substring(0, 2), 16) || 0;
|
|
1249
|
+
const bgG = parseInt(cleanBg.substring(2, 4), 16) || 0;
|
|
1250
|
+
const bgB = parseInt(cleanBg.substring(4, 6), 16) || 0;
|
|
1251
|
+
const textR = parseInt(cleanText.substring(0, 2), 16) || 0;
|
|
1252
|
+
const textG = parseInt(cleanText.substring(2, 4), 16) || 0;
|
|
1253
|
+
const textB = parseInt(cleanText.substring(4, 6), 16) || 0;
|
|
1254
|
+
const steps = [
|
|
1255
|
+
50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950,
|
|
1256
|
+
];
|
|
1257
|
+
for (const step of steps) {
|
|
1258
|
+
const weight = step / 1000;
|
|
1259
|
+
const r = Math.round(bgR * (1 - weight) + textR * weight);
|
|
1260
|
+
const g = Math.round(bgG * (1 - weight) + textG * weight);
|
|
1261
|
+
const b = Math.round(bgB * (1 - weight) + textB * weight);
|
|
1262
|
+
const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
|
|
1263
|
+
vars[`--ion-color-step-${step}`] = hex;
|
|
1264
|
+
}
|
|
834
1265
|
}
|
|
835
1266
|
|
|
836
1267
|
const STYLE_ID = 'ngxsmk-theme';
|
|
@@ -862,7 +1293,7 @@ class NgxThemeInjectorService {
|
|
|
862
1293
|
if (!style) {
|
|
863
1294
|
style = this.document.createElement('style');
|
|
864
1295
|
style.id = STYLE_ID;
|
|
865
|
-
head
|
|
1296
|
+
head?.appendChild(style);
|
|
866
1297
|
}
|
|
867
1298
|
let css = buildThemeCss(config);
|
|
868
1299
|
if (adapter) {
|
|
@@ -870,7 +1301,9 @@ class NgxThemeInjectorService {
|
|
|
870
1301
|
const platformVars = adapter.vars(resolved);
|
|
871
1302
|
css += '\n' + this.buildAdapterCss(adapter.name, platformVars);
|
|
872
1303
|
}
|
|
873
|
-
style
|
|
1304
|
+
if (style) {
|
|
1305
|
+
style.textContent = css;
|
|
1306
|
+
}
|
|
874
1307
|
}
|
|
875
1308
|
/**
|
|
876
1309
|
* Apply Ionic-specific token variables alongside the standard NGXSMK theme.
|
|
@@ -882,11 +1315,12 @@ class NgxThemeInjectorService {
|
|
|
882
1315
|
if (!style) {
|
|
883
1316
|
style = this.document.createElement('style');
|
|
884
1317
|
style.id = IONIC_STYLE_ID;
|
|
885
|
-
head
|
|
1318
|
+
head?.appendChild(style);
|
|
886
1319
|
}
|
|
887
1320
|
const resolved = resolveTheme(config);
|
|
888
|
-
const
|
|
889
|
-
|
|
1321
|
+
const lightVars = ionicVarsAdapter.vars(resolved);
|
|
1322
|
+
const darkVars = ionicVarsAdapter.darkVars ? ionicVarsAdapter.darkVars(resolved) : undefined;
|
|
1323
|
+
style.textContent = this.buildAdapterCss('ionic', lightVars, darkVars);
|
|
890
1324
|
}
|
|
891
1325
|
/** Remove a runtime-applied theme, falling back to the build-time CSS. */
|
|
892
1326
|
clearTheme() {
|
|
@@ -896,9 +1330,15 @@ class NgxThemeInjectorService {
|
|
|
896
1330
|
clearIonicTheme() {
|
|
897
1331
|
this.document.getElementById(IONIC_STYLE_ID)?.remove();
|
|
898
1332
|
}
|
|
899
|
-
buildAdapterCss(adapterName, vars) {
|
|
1333
|
+
buildAdapterCss(adapterName, vars, darkVars) {
|
|
900
1334
|
const lines = Object.entries(vars).map(([k, v]) => ` ${k}: ${v};`);
|
|
901
|
-
|
|
1335
|
+
let result = `/* @ngxsmk/theme — ${adapterName} adapter */\n:root {\n${lines.join('\n')}\n}`;
|
|
1336
|
+
if (darkVars && Object.keys(darkVars).length > 0) {
|
|
1337
|
+
const darkLines = Object.entries(darkVars).map(([k, v]) => ` ${k}: ${v};`);
|
|
1338
|
+
result += `\n\n:root.dark, body.dark, html.dark, [data-theme="dark"] {\n${darkLines.join('\n')}\n}`;
|
|
1339
|
+
result += `\n\n@media (prefers-color-scheme: dark) {\n :root:not(.light) {\n ${darkLines.join('\n ')}\n }\n}`;
|
|
1340
|
+
}
|
|
1341
|
+
return result;
|
|
902
1342
|
}
|
|
903
1343
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
904
1344
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.0", ngImport: i0, type: NgxThemeInjectorService, providedIn: 'root' }); }
|
|
@@ -983,6 +1423,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.0", ngImpor
|
|
|
983
1423
|
args: [{ providedIn: 'root' }]
|
|
984
1424
|
}], ctorParameters: () => [] });
|
|
985
1425
|
|
|
1426
|
+
/**
|
|
1427
|
+
* Standalone provider for Angular 18-22+ applications that automatically
|
|
1428
|
+
* synchronizes NGXSMK design tokens with Ionic 7/8 CSS variables on startup.
|
|
1429
|
+
*
|
|
1430
|
+
* @example
|
|
1431
|
+
* ```ts
|
|
1432
|
+
* export const appConfig: ApplicationConfig = {
|
|
1433
|
+
* providers: [
|
|
1434
|
+
* provideIonicAngular(),
|
|
1435
|
+
* provideNgxsmkIonicTheme(),
|
|
1436
|
+
* ]
|
|
1437
|
+
* };
|
|
1438
|
+
* ```
|
|
1439
|
+
*/
|
|
1440
|
+
function provideNgxsmkIonicTheme(config) {
|
|
1441
|
+
return makeEnvironmentProviders([
|
|
1442
|
+
{
|
|
1443
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
1444
|
+
multi: true,
|
|
1445
|
+
useValue: () => {
|
|
1446
|
+
const theme = inject(NgxsmkThemeService);
|
|
1447
|
+
theme.applyIonicTheme(config ?? violetPreset);
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
]);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
986
1453
|
/*
|
|
987
1454
|
* Public API Surface of @ngxsmk/theme
|
|
988
1455
|
*/
|
|
@@ -991,5 +1458,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.0", ngImpor
|
|
|
991
1458
|
* Generated bundle index. Do not edit.
|
|
992
1459
|
*/
|
|
993
1460
|
|
|
994
|
-
export { CONTROL_HEIGHT, CONTROL_HEIGHTS, DEFAULT_FONT_FAMILY, DEFAULT_NEUTRAL, DEFAULT_SEMANTIC, DEFAULT_TYPOGRAPHY, DURATION, EASING, FONT_WEIGHT, ICON_SIZE, LEADING, NgxThemeInjectorService, NgxsmkThemeService, OPACITY, RADIUS, SHADOW, SPACING, TRACKING, Z_INDEX, buildThemeCss, cssVarsAdapter, defineConfig, deriveScale, emeraldPreset, hexToOklch, ionicVarsAdapter, neutralPreset, oklchToHex, presets, resolveTheme, rosePreset, rotateHue, shiftLightness, violetPreset };
|
|
1461
|
+
export { CONTROL_HEIGHT, CONTROL_HEIGHTS, DEFAULT_FONT_FAMILY, DEFAULT_NEUTRAL, DEFAULT_SEMANTIC, DEFAULT_TYPOGRAPHY, DURATION, EASING, FONT_WEIGHT, ICON_SIZE, LEADING, NgxThemeInjectorService, NgxsmkThemeService, OPACITY, RADIUS, SAFE_AREA, SHADOW, SPACING, TOUCH_TARGET_MIN, TRACKING, Z_INDEX, buildThemeCss, cssVarsAdapter, defineConfig, deriveScale, emeraldPreset, hexToOklch, inkPreset, ionicVarsAdapter, neutralPreset, oklchToHex, presets, provideNgxsmkIonicTheme, resolveTheme, rosePreset, rotateHue, shiftLightness, violetPreset };
|
|
995
1462
|
//# sourceMappingURL=ngxsmk-theme.mjs.map
|