@duskmoon-dev/core 1.19.3 → 1.19.5

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 (121) hide show
  1. package/README.md +1 -1
  2. package/dist/base.css +484 -0
  3. package/dist/components/accordion.css +33 -9
  4. package/dist/components/alert.css +81 -3
  5. package/dist/components/autocomplete.css +67 -13
  6. package/dist/components/avatar.css +39 -15
  7. package/dist/components/badge.css +15 -3
  8. package/dist/components/card.css +12 -0
  9. package/dist/components/carousel.css +28 -0
  10. package/dist/components/chat.css +15 -11
  11. package/dist/components/checkbox.css +83 -13
  12. package/dist/components/collapse.css +67 -16
  13. package/dist/components/countdown.css +22 -0
  14. package/dist/components/datepicker.css +76 -0
  15. package/dist/components/diff.css +33 -0
  16. package/dist/components/file-input.css +107 -0
  17. package/dist/components/filter-group.css +452 -0
  18. package/dist/components/form-group.css +95 -204
  19. package/dist/components/form.css +1551 -421
  20. package/dist/components/index.css +6248 -5799
  21. package/dist/components/input.css +114 -31
  22. package/dist/components/kbd.css +28 -0
  23. package/dist/components/list.css +22 -3
  24. package/dist/components/loading.css +105 -0
  25. package/dist/components/modal.css +62 -1
  26. package/dist/components/otp-input.css +124 -6
  27. package/dist/components/progress.css +74 -0
  28. package/dist/components/radial-progress.css +47 -0
  29. package/dist/components/radio.css +70 -0
  30. package/dist/components/range.css +41 -0
  31. package/dist/components/rating.css +39 -12
  32. package/dist/components/select.css +74 -0
  33. package/dist/components/skeleton.css +13 -1
  34. package/dist/components/snackbar.css +48 -18
  35. package/dist/components/stat.css +40 -0
  36. package/dist/components/switch.css +70 -0
  37. package/dist/components/table.css +8 -0
  38. package/dist/components/textarea.css +74 -0
  39. package/dist/components/timeline.css +40 -25
  40. package/dist/components/toast.css +188 -34
  41. package/dist/components/toggle-switch.css +165 -0
  42. package/dist/components/toggle.css +198 -32
  43. package/dist/components/tooltip.css +2 -47
  44. package/dist/components/validator.css +66 -0
  45. package/dist/effects/aura.css +22 -0
  46. package/dist/effects/hover-3d.css +8 -0
  47. package/dist/effects/hover-gallery.css +14 -0
  48. package/dist/effects/index.css +65 -0
  49. package/dist/effects/text-rotate.css +16 -0
  50. package/dist/esm/components/accordion.js +33 -9
  51. package/dist/esm/components/alert.js +81 -3
  52. package/dist/esm/components/autocomplete.js +67 -13
  53. package/dist/esm/components/avatar.js +39 -15
  54. package/dist/esm/components/badge.js +15 -3
  55. package/dist/esm/components/card.js +12 -0
  56. package/dist/esm/components/carousel.d.ts +5 -0
  57. package/dist/esm/components/carousel.js +37 -0
  58. package/dist/esm/components/chat.js +15 -11
  59. package/dist/esm/components/checkbox.js +84 -14
  60. package/dist/esm/components/collapse.js +67 -16
  61. package/dist/esm/components/countdown.d.ts +5 -0
  62. package/dist/esm/components/countdown.js +31 -0
  63. package/dist/esm/components/datepicker.js +77 -1
  64. package/dist/esm/components/diff.d.ts +5 -0
  65. package/dist/esm/components/diff.js +42 -0
  66. package/dist/esm/components/file-input.d.ts +5 -0
  67. package/dist/esm/components/file-input.js +116 -0
  68. package/dist/esm/components/filter-group.d.ts +5 -0
  69. package/dist/esm/components/filter-group.js +461 -0
  70. package/dist/esm/components/form-group.js +96 -205
  71. package/dist/esm/components/form.js +1539 -409
  72. package/dist/esm/components/input.js +115 -32
  73. package/dist/esm/components/kbd.d.ts +5 -0
  74. package/dist/esm/components/kbd.js +37 -0
  75. package/dist/esm/components/list.js +22 -3
  76. package/dist/esm/components/loading.d.ts +5 -0
  77. package/dist/esm/components/loading.js +114 -0
  78. package/dist/esm/components/modal.js +62 -1
  79. package/dist/esm/components/otp-input.js +125 -7
  80. package/dist/esm/components/progress.js +74 -0
  81. package/dist/esm/components/radial-progress.d.ts +5 -0
  82. package/dist/esm/components/radial-progress.js +56 -0
  83. package/dist/esm/components/radio.js +71 -1
  84. package/dist/esm/components/range.d.ts +5 -0
  85. package/dist/esm/components/range.js +50 -0
  86. package/dist/esm/components/rating.js +39 -12
  87. package/dist/esm/components/select.js +75 -1
  88. package/dist/esm/components/skeleton.js +13 -1
  89. package/dist/esm/components/snackbar.js +48 -18
  90. package/dist/esm/components/stat.d.ts +5 -0
  91. package/dist/esm/components/stat.js +49 -0
  92. package/dist/esm/components/switch.js +71 -1
  93. package/dist/esm/components/table.js +8 -0
  94. package/dist/esm/components/textarea.js +75 -1
  95. package/dist/esm/components/timeline.js +40 -25
  96. package/dist/esm/components/toast.js +188 -34
  97. package/dist/esm/components/toggle-switch.d.ts +5 -0
  98. package/dist/esm/components/toggle-switch.js +174 -0
  99. package/dist/esm/components/toggle.js +198 -32
  100. package/dist/esm/components/tooltip.js +2 -47
  101. package/dist/esm/components/validator.d.ts +5 -0
  102. package/dist/esm/components/validator.js +75 -0
  103. package/dist/esm/effects/aura.d.ts +5 -0
  104. package/dist/esm/effects/aura.js +31 -0
  105. package/dist/esm/effects/hover-3d.d.ts +5 -0
  106. package/dist/esm/effects/hover-3d.js +17 -0
  107. package/dist/esm/effects/hover-gallery.d.ts +5 -0
  108. package/dist/esm/effects/hover-gallery.js +23 -0
  109. package/dist/esm/effects/index.d.ts +5 -0
  110. package/dist/esm/effects/index.js +74 -0
  111. package/dist/esm/effects/text-rotate.d.ts +5 -0
  112. package/dist/esm/effects/text-rotate.js +25 -0
  113. package/dist/index.css +7652 -7204
  114. package/dist/index.min.css +1 -1
  115. package/dist/standalone/duskmoonui-themes.css +1 -1
  116. package/dist/standalone/duskmoonui.css +7598 -7141
  117. package/dist/standalone/duskmoonui.js +18 -16
  118. package/dist/standalone/duskmoonui.mjs +18 -16
  119. package/dist/types/types/plugin.d.ts +1 -1
  120. package/dist/types/types/plugin.d.ts.map +1 -1
  121. package/package.json +110 -1
@@ -1,3 +1,113 @@
1
+ /** Opt-in native presentation. Applications own errors, messages and announcements. */
2
+ @layer components {
3
+ /* Compatibility wrapper states are intentionally immediate. */
4
+ :is(.form-group-info, .form-control.info) { --dm-validation-color: var(--color-info); }
5
+ :is(.form-group-warning, .form-control.warning) { --dm-validation-color: var(--color-warning); }
6
+ :is(.form-group-success, .form-control.success) { --dm-validation-color: var(--color-success); }
7
+
8
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :valid, .validate:valid, .validator.validator-success:user-valid):not([aria-invalid="true"]):not(:where(.form-group-error *, .form-control.error *)):not(:disabled) {
9
+ --dm-validation-color: var(--color-success);
10
+ --dm-validation-content: var(--color-success-content);
11
+ }
12
+
13
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :invalid, .validate:invalid, .validator:user-invalid):not(:disabled) {
14
+ --dm-validation-color: var(--color-error);
15
+ --dm-validation-content: var(--color-error-content);
16
+ }
17
+
18
+ :is(.form-group-error, .form-control.error) {
19
+ --dm-validation-color: var(--color-error);
20
+ --dm-validation-content: var(--color-error-content);
21
+ }
22
+
23
+ /* Higher specificity than native states, including server-invalid + user-valid. */
24
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .form-group-error *, .form-control.error *):not(:disabled):is(:focus, :not(:focus)) {
25
+ --dm-validation-color: var(--color-error);
26
+ --dm-validation-content: var(--color-error-content);
27
+ }
28
+
29
+ /* This shared selector wins over filled/ghost focus and semantic modifiers. */
30
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled) {
31
+ color: var(--dm-validation-color);
32
+ border-color: var(--dm-validation-color);
33
+ --checkbox-color: var(--dm-validation-color);
34
+ --radio-color: var(--dm-validation-color);
35
+ --switch-color: var(--dm-validation-color);
36
+ --switch-content-color: var(--dm-validation-content, var(--color-primary-content));
37
+ --toggle-color: var(--dm-validation-color);
38
+ }
39
+
40
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled):focus {
41
+ border-color: var(--dm-validation-color);
42
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--dm-validation-color) 20%, transparent);
43
+ }
44
+
45
+ :is(.form-group-error, .form-control.error) :is(.form-label, .label-text) { color: var(--color-error); }
46
+ :is(.form-group-success, .form-control.success) :is(.form-label, .label-text) { color: var(--color-success); }
47
+ :is(.form-group-warning, .form-control.warning) :is(.form-label, .label-text) { color: var(--color-warning); }
48
+ :is(.form-group-info, .form-control.info) :is(.form-label, .label-text) { color: var(--color-info); }
49
+
50
+ .validator-hint { font-size: 0.75rem; color: var(--color-on-surface-variant); }
51
+ .validator-error { display: none; font-size: 0.75rem; color: var(--color-error); }
52
+ /* Keep OTP actions stationary when blur reveals an error during a click. */
53
+ .otp-code ~ .validator-error { display: block; visibility: hidden; }
54
+ :is(.form-group, .form-control):has(:is([aria-invalid="true"], .validator:user-invalid, .validate :invalid, .validate:invalid):not(:disabled)) > .validator-error,
55
+ :is(.form-group-error, .form-control.error) > .validator-error { display: block; visibility: visible; }
56
+
57
+ @media (forced-colors: active) {
58
+ :is(input, select, textarea):is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):focus-visible {
59
+ outline: 2px solid Highlight;
60
+ outline-offset: 2px;
61
+ }
62
+ input:is(.checkbox, .radio, .switch, .toggle) { appearance: auto; }
63
+ input:is(.checkbox, .radio, .switch, .toggle)::before,
64
+ input:is(.checkbox, .radio, .switch, .toggle)::after { content: none; }
65
+ }
66
+ }
67
+
68
+
69
+
70
+ @layer components {
71
+ .file-input {
72
+ display: block;
73
+ box-sizing: border-box;
74
+ width: 100%;
75
+ min-width: 0;
76
+ padding: 0;
77
+ font: inherit;
78
+ color: var(--color-on-surface);
79
+ background-color: var(--color-surface);
80
+ border: 1px solid currentColor;
81
+ border-radius: var(--radius-sm);
82
+ cursor: pointer;
83
+ }
84
+ .file-input::file-selector-button {
85
+ padding: 0.75rem 1rem;
86
+ margin-inline-end: 0.75rem;
87
+ font: inherit;
88
+ color: inherit;
89
+ background-color: var(--color-surface-container);
90
+ border: none;
91
+ border-inline-end: 1px solid currentColor;
92
+ cursor: inherit;
93
+ }
94
+ .file-input:hover:not(:disabled)::file-selector-button { background-color: var(--color-surface-container-high); }
95
+ .file-input:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 20%, transparent); }
96
+ .file-input:disabled { opacity: 0.5; cursor: not-allowed; }
97
+ .file-input-xs::file-selector-button { padding: 0.375rem 0.625rem; }
98
+ .file-input-sm::file-selector-button { padding: 0.5rem 0.75rem; }
99
+ .file-input-lg::file-selector-button { padding: 1rem 1.25rem; }
100
+ .file-input-primary { color: var(--color-primary); }
101
+ .file-input-secondary { color: var(--color-secondary); }
102
+ .file-input-tertiary { color: var(--color-tertiary); }
103
+ .file-input-info { color: var(--color-info); }
104
+ .file-input-success { color: var(--color-success); }
105
+ .file-input-warning { color: var(--color-warning); }
106
+ .file-input-error { color: var(--color-error); }
107
+ .file-input-ghost { border-color: transparent; }
108
+ }
109
+
110
+
1
111
  /**
2
112
  * Input Component Styles
3
113
  * DuskMoonUI - Material Design 3 inspired input system
@@ -6,6 +116,8 @@
6
116
  @layer components {
7
117
  /* Base Input */
8
118
  .input {
119
+ box-sizing: border-box;
120
+ min-width: 0;
9
121
  display: flex;
10
122
  width: 100%;
11
123
  padding: 0.75rem 1rem;
@@ -39,6 +151,7 @@
39
151
  }
40
152
 
41
153
  /* Bordered Input */
154
+ .input-outlined { background-color: transparent; border-color: var(--color-outline); }
42
155
  .input-bordered {
43
156
  border-color: var(--color-outline);
44
157
  background-color: var(--color-surface);
@@ -89,6 +202,7 @@
89
202
  }
90
203
 
91
204
  /* Ghost Input */
205
+ .input-md { height: 3rem; }
92
206
  .input-ghost {
93
207
  background-color: transparent;
94
208
  border-color: transparent;
@@ -167,37 +281,6 @@
167
281
  border-left: none;
168
282
  }
169
283
 
170
- /* File Input */
171
- .file-input {
172
- display: flex;
173
- width: 100%;
174
- padding: 0;
175
- background-color: var(--color-surface);
176
- border: 1px solid var(--color-outline);
177
- border-radius: var(--radius-sm);
178
- overflow: hidden;
179
- cursor: pointer;
180
- }
181
-
182
- .file-input::file-selector-button {
183
- padding: 0.75rem 1rem;
184
- background-color: var(--color-surface-container);
185
- color: var(--color-on-surface);
186
- border: none;
187
- border-right: 1px solid var(--color-outline);
188
- cursor: pointer;
189
- transition: background-color 150ms ease-in-out;
190
- }
191
-
192
- .file-input::file-selector-button:hover {
193
- background-color: var(--color-surface-container-high);
194
- }
195
-
196
- .file-input:focus-visible {
197
- box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 20%, transparent);
198
- outline: none;
199
- }
200
-
201
284
  /* Reduce Motion */
202
285
  @media (prefers-reduced-motion: reduce) {
203
286
  .input,
@@ -0,0 +1,28 @@
1
+ @layer components {
2
+ .kbd {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ min-inline-size: 1.5rem;
7
+ padding: 0.125rem 0.375rem;
8
+ font: 0.875rem ui-monospace, monospace;
9
+ line-height: 1.5;
10
+ color: var(--color-on-surface);
11
+ background: var(--color-surface-container);
12
+ border: 1px solid currentColor;
13
+ border-block-end-width: 2px;
14
+ border-radius: var(--radius-sm);
15
+ vertical-align: middle;
16
+ }
17
+ .kbd-xs { font-size: 0.625rem; }
18
+ .kbd-sm { font-size: 0.75rem; }
19
+ .kbd-lg { font-size: 1rem; padding: 0.25rem 0.5rem; }
20
+ .kbd-primary { color: var(--color-primary); }
21
+ .kbd-secondary { color: var(--color-secondary); }
22
+ .kbd-tertiary { color: var(--color-tertiary); }
23
+ .kbd-info { color: var(--color-info); }
24
+ .kbd-success { color: var(--color-success); }
25
+ .kbd-warning { color: var(--color-warning); }
26
+ .kbd-error { color: var(--color-error); }
27
+ .kbd-ghost { border-color: transparent; }
28
+ }
@@ -63,14 +63,18 @@
63
63
  min-width: 0;
64
64
  }
65
65
 
66
- .list-item-title {
66
+ .list-item-title,
67
+ .list-item-text {
68
+ overflow-wrap: anywhere;
67
69
  font-size: 1rem;
68
70
  font-weight: 500;
69
71
  line-height: 1.5rem;
70
72
  color: var(--color-on-surface);
71
73
  }
72
74
 
73
- .list-item-subtitle {
75
+ .list-item-subtitle,
76
+ .list-item-secondary {
77
+ overflow-wrap: anywhere;
74
78
  font-size: 0.875rem;
75
79
  line-height: 1.25rem;
76
80
  color: var(--color-on-surface-variant);
@@ -84,6 +88,21 @@
84
88
  color: var(--color-on-surface-variant);
85
89
  }
86
90
 
91
+ .list-item-leading, .list-item-trailing { display: inline-flex; align-items: center; gap: 0.5rem; flex: none; }
92
+ .list-item-trailing { margin-inline-start: auto; }
93
+ .list-item-two-line, .list-item-three-line { align-items: flex-start; }
94
+ .list-item-interactive { cursor: pointer; }
95
+ .list-item-interactive:hover { background: var(--color-surface-container); }
96
+ .list-item-interactive:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
97
+ .list-item-active-primary { background: var(--color-primary-container); color: var(--color-on-primary-container); }
98
+ .list-item-active-secondary { background: var(--color-secondary-container); color: var(--color-on-secondary-container); }
99
+ .list-item-active-tertiary { background: var(--color-tertiary-container); color: var(--color-on-tertiary-container); }
100
+ .list-comfortable > .list-item { padding-block: 1rem; }
101
+ .list-surface-container { background: var(--color-surface-container); }
102
+ .list-surface-container-low { background: var(--color-surface-container-low); }
103
+ .list-surface-container-high { background: var(--color-surface-container-high); }
104
+ .list-nested { margin-inline-start: 1rem; }
105
+
87
106
  /* List Variants */
88
107
  .list-bordered {
89
108
  border: 1px solid var(--color-outline);
@@ -120,7 +139,7 @@
120
139
  }
121
140
 
122
141
  .list-divider-inset {
123
- margin-left: 3.5rem;
142
+ margin-inline-start: 3.5rem;
124
143
  }
125
144
 
126
145
  /* List Subheader */
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Loading Component Styles
3
+ * CSS-only activity indicators. State, aria-busy, and disabling remain consumer-owned.
4
+ */
5
+
6
+ @layer components {
7
+ .loading {
8
+ --loading-size: 1.25rem;
9
+ display: inline-flex;
10
+ width: var(--loading-size);
11
+ height: var(--loading-size);
12
+ flex: 0 0 var(--loading-size);
13
+ color: currentColor;
14
+ vertical-align: middle;
15
+ }
16
+
17
+ .loading-spinner {
18
+ border: max(2px, calc(var(--loading-size) / 8)) solid color-mix(in oklch, currentColor 25%, transparent);
19
+ border-top-color: currentColor;
20
+ border-radius: var(--radius-full);
21
+ animation: loading-spin 800ms linear infinite;
22
+ }
23
+
24
+ .loading-dots,
25
+ .loading-bars {
26
+ align-items: center;
27
+ justify-content: center;
28
+ gap: max(2px, calc(var(--loading-size) / 8));
29
+ }
30
+
31
+ .loading-dots::before,
32
+ .loading-dots::after,
33
+ .loading-bars::before,
34
+ .loading-bars::after {
35
+ content: "";
36
+ display: block;
37
+ background: currentColor;
38
+ }
39
+
40
+ .loading-dots::before,
41
+ .loading-dots::after {
42
+ width: 25%;
43
+ height: 25%;
44
+ border-radius: var(--radius-full);
45
+ box-shadow: calc(var(--loading-size) / 3) 0 currentColor;
46
+ animation: loading-dots 900ms ease-in-out infinite;
47
+ }
48
+
49
+ .loading-dots::after {
50
+ display: none;
51
+ }
52
+
53
+ .loading-bars {
54
+ padding-inline: 10%;
55
+ }
56
+
57
+ .loading-bars::before,
58
+ .loading-bars::after {
59
+ width: 18%;
60
+ height: 65%;
61
+ border-radius: var(--radius-xs);
62
+ box-shadow: calc(var(--loading-size) / 3) 0 currentColor, calc(var(--loading-size) * 2 / 3) 0 currentColor;
63
+ animation: loading-bars 900ms ease-in-out infinite;
64
+ }
65
+
66
+ .loading-bars::after {
67
+ display: none;
68
+ }
69
+
70
+ .loading-xs { --loading-size: 0.75rem; }
71
+ .loading-sm { --loading-size: 1rem; }
72
+ .loading-md { --loading-size: 1.25rem; }
73
+ .loading-lg { --loading-size: 1.75rem; }
74
+ .loading-xl { --loading-size: 2.5rem; }
75
+
76
+ .loading-primary { color: var(--color-primary); }
77
+ .loading-secondary { color: var(--color-secondary); }
78
+ .loading-tertiary { color: var(--color-tertiary); }
79
+ .loading-info { color: var(--color-info); }
80
+ .loading-success { color: var(--color-success); }
81
+ .loading-warning { color: var(--color-warning); }
82
+ .loading-error { color: var(--color-error); }
83
+
84
+ @keyframes loading-spin {
85
+ to { transform: rotate(360deg); }
86
+ }
87
+
88
+ @keyframes loading-dots {
89
+ 0%, 100% { opacity: 0.35; transform: scale(0.75); }
90
+ 50% { opacity: 1; transform: scale(1); }
91
+ }
92
+
93
+ @keyframes loading-bars {
94
+ 0%, 100% { opacity: 0.35; transform: scaleY(0.65); }
95
+ 50% { opacity: 1; transform: scaleY(1); }
96
+ }
97
+
98
+ @media (prefers-reduced-motion: reduce) {
99
+ .loading-spinner,
100
+ .loading-dots::before,
101
+ .loading-bars::before {
102
+ animation: none;
103
+ }
104
+ }
105
+ }
@@ -62,7 +62,8 @@
62
62
  }
63
63
 
64
64
  /* Modal Action */
65
- .modal-action {
65
+ .modal-action,
66
+ .modal-footer {
66
67
  display: flex;
67
68
  justify-content: flex-end;
68
69
  gap: 0.5rem;
@@ -93,10 +94,18 @@
93
94
  max-width: 20rem;
94
95
  }
95
96
 
97
+ .modal-md .modal-box {
98
+ max-width: 32rem;
99
+ }
100
+
96
101
  .modal-lg .modal-box {
97
102
  max-width: 48rem;
98
103
  }
99
104
 
105
+ .modal-xl .modal-box {
106
+ max-width: 64rem;
107
+ }
108
+
100
109
  .modal-full .modal-box {
101
110
  max-width: calc(100vw - 2rem);
102
111
  max-height: calc(100vh - 2rem);
@@ -119,6 +128,38 @@
119
128
  align-items: center;
120
129
  }
121
130
 
131
+ /* Animation Variants */
132
+ .modal-slide-up .modal-box {
133
+ transform: translateY(6rem) scale(0.95);
134
+ }
135
+
136
+ .modal-slide-down .modal-box {
137
+ transform: translateY(-6rem) scale(0.95);
138
+ }
139
+
140
+ .modal-zoom .modal-box {
141
+ transform: scale(0.75);
142
+ }
143
+
144
+ :is(.modal-slide-up, .modal-slide-down, .modal-zoom).modal-open .modal-box,
145
+ :is(.modal-slide-up, .modal-slide-down, .modal-zoom):target .modal-box {
146
+ transform: translateY(0) scale(1);
147
+ }
148
+
149
+ /* Backdrop Variants */
150
+ .modal-backdrop-light {
151
+ background-color: color-mix(in srgb, white 80%, transparent);
152
+ }
153
+
154
+ .modal-backdrop-blur {
155
+ background-color: color-mix(in srgb, var(--color-scrim) 30%, transparent);
156
+ backdrop-filter: blur(8px);
157
+ }
158
+
159
+ .modal-no-backdrop {
160
+ background-color: transparent;
161
+ }
162
+
122
163
  /* Modal Close Button */
123
164
  .modal-close {
124
165
  position: absolute;
@@ -167,6 +208,22 @@
167
208
  line-height: 1.5;
168
209
  }
169
210
 
211
+ .modal-scrollable .modal-body {
212
+ max-height: 60vh;
213
+ overflow-y: auto;
214
+ }
215
+
216
+ .modal-no-padding .modal-body {
217
+ padding: 0;
218
+ }
219
+
220
+ .modal-centered .modal-body {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ text-align: center;
225
+ }
226
+
170
227
  /* Responsive Modal */
171
228
  @media (max-width: 640px) {
172
229
  .modal-box {
@@ -288,4 +345,8 @@
288
345
  white-space: nowrap;
289
346
  border: 0;
290
347
  }
348
+
349
+ body.modal-open {
350
+ overflow: hidden;
351
+ }
291
352
  }
@@ -1,11 +1,82 @@
1
+ /** Opt-in native presentation. Applications own errors, messages and announcements. */
2
+ @layer components {
3
+ /* Compatibility wrapper states are intentionally immediate. */
4
+ :is(.form-group-info, .form-control.info) { --dm-validation-color: var(--color-info); }
5
+ :is(.form-group-warning, .form-control.warning) { --dm-validation-color: var(--color-warning); }
6
+ :is(.form-group-success, .form-control.success) { --dm-validation-color: var(--color-success); }
7
+
8
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :valid, .validate:valid, .validator.validator-success:user-valid):not([aria-invalid="true"]):not(:where(.form-group-error *, .form-control.error *)):not(:disabled) {
9
+ --dm-validation-color: var(--color-success);
10
+ --dm-validation-content: var(--color-success-content);
11
+ }
12
+
13
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :invalid, .validate:invalid, .validator:user-invalid):not(:disabled) {
14
+ --dm-validation-color: var(--color-error);
15
+ --dm-validation-content: var(--color-error-content);
16
+ }
17
+
18
+ :is(.form-group-error, .form-control.error) {
19
+ --dm-validation-color: var(--color-error);
20
+ --dm-validation-content: var(--color-error-content);
21
+ }
22
+
23
+ /* Higher specificity than native states, including server-invalid + user-valid. */
24
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .form-group-error *, .form-control.error *):not(:disabled):is(:focus, :not(:focus)) {
25
+ --dm-validation-color: var(--color-error);
26
+ --dm-validation-content: var(--color-error-content);
27
+ }
28
+
29
+ /* This shared selector wins over filled/ghost focus and semantic modifiers. */
30
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled) {
31
+ color: var(--dm-validation-color);
32
+ border-color: var(--dm-validation-color);
33
+ --checkbox-color: var(--dm-validation-color);
34
+ --radio-color: var(--dm-validation-color);
35
+ --switch-color: var(--dm-validation-color);
36
+ --switch-content-color: var(--dm-validation-content, var(--color-primary-content));
37
+ --toggle-color: var(--dm-validation-color);
38
+ }
39
+
40
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled):focus {
41
+ border-color: var(--dm-validation-color);
42
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--dm-validation-color) 20%, transparent);
43
+ }
44
+
45
+ :is(.form-group-error, .form-control.error) :is(.form-label, .label-text) { color: var(--color-error); }
46
+ :is(.form-group-success, .form-control.success) :is(.form-label, .label-text) { color: var(--color-success); }
47
+ :is(.form-group-warning, .form-control.warning) :is(.form-label, .label-text) { color: var(--color-warning); }
48
+ :is(.form-group-info, .form-control.info) :is(.form-label, .label-text) { color: var(--color-info); }
49
+
50
+ .validator-hint { font-size: 0.75rem; color: var(--color-on-surface-variant); }
51
+ .validator-error { display: none; font-size: 0.75rem; color: var(--color-error); }
52
+ /* Keep OTP actions stationary when blur reveals an error during a click. */
53
+ .otp-code ~ .validator-error { display: block; visibility: hidden; }
54
+ :is(.form-group, .form-control):has(:is([aria-invalid="true"], .validator:user-invalid, .validate :invalid, .validate:invalid):not(:disabled)) > .validator-error,
55
+ :is(.form-group-error, .form-control.error) > .validator-error { display: block; visibility: visible; }
56
+
57
+ @media (forced-colors: active) {
58
+ :is(input, select, textarea):is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):focus-visible {
59
+ outline: 2px solid Highlight;
60
+ outline-offset: 2px;
61
+ }
62
+ input:is(.checkbox, .radio, .switch, .toggle) { appearance: auto; }
63
+ input:is(.checkbox, .radio, .switch, .toggle)::before,
64
+ input:is(.checkbox, .radio, .switch, .toggle)::after { content: none; }
65
+ }
66
+ }
67
+
68
+
1
69
  /**
2
70
  * OTP Input Component Styles
3
71
  * DuskMoonUI - One-Time Password input for verification codes
4
72
  */
5
73
 
6
74
  @layer components {
75
+ :where(.otp-input, .otp-input-field) { box-sizing: border-box; }
76
+
7
77
  /* Base OTP Container */
8
78
  .otp-input {
79
+ flex-wrap: wrap;
9
80
  display: inline-flex;
10
81
  align-items: center;
11
82
  gap: 0.5rem;
@@ -54,7 +125,8 @@
54
125
  }
55
126
 
56
127
  /* Separator */
57
- .otp-separator {
128
+ .otp-separator,
129
+ .otp-input-separator {
58
130
  display: flex;
59
131
  align-items: center;
60
132
  justify-content: center;
@@ -108,14 +180,16 @@
108
180
  }
109
181
 
110
182
  /* Underline Variant */
111
- .otp-input-underline .otp-input-field {
183
+ .otp-input-underline .otp-input-field,
184
+ .otp-input-underlined .otp-input-field {
112
185
  background-color: transparent;
113
186
  border: none;
114
187
  border-bottom: 2px solid var(--color-outline);
115
188
  border-radius: 0;
116
189
  }
117
190
 
118
- .otp-input-underline .otp-input-field:focus {
191
+ .otp-input-underline .otp-input-field:focus,
192
+ .otp-input-underlined .otp-input-field:focus {
119
193
  border-bottom-color: var(--color-primary);
120
194
  box-shadow: none;
121
195
  }
@@ -232,17 +306,61 @@
232
306
  color: var(--color-on-surface);
233
307
  }
234
308
 
235
- .otp-helper {
309
+ .otp-helper,
310
+ .otp-input-helper {
236
311
  font-size: 0.75rem;
237
312
  color: var(--color-on-surface-variant);
238
313
  }
239
314
 
240
- .otp-error-message {
315
+ .otp-error-message,
316
+ .otp-input-helper-error {
241
317
  font-size: 0.75rem;
242
318
  color: var(--color-error);
243
319
  }
244
320
 
245
- /* Reduce Motion */
321
+ /* One complete native code. Length guides never create separate fields. */
322
+ .otp-code {
323
+ --otp-length: 6;
324
+ display: block;
325
+ box-sizing: border-box;
326
+ /* The extra two content pixels keep the trailing native caret in view. */
327
+ inline-size: calc(var(--otp-length) * 1ch + 1.5rem + 4px);
328
+ max-inline-size: 100%;
329
+ min-inline-size: 0;
330
+ padding: 0.75rem;
331
+ font: 1.5rem / 1.5 ui-monospace, monospace;
332
+ font-variant-numeric: tabular-nums;
333
+ direction: ltr;
334
+ text-align: start;
335
+ color: var(--color-on-surface);
336
+ caret-color: currentColor;
337
+ background-color: var(--color-surface);
338
+ border: 1px solid currentColor;
339
+ border-radius: var(--radius-sm);
340
+ }
341
+ .otp-code-4 { --otp-length: 4; }
342
+ .otp-code:focus { outline: 2px solid currentColor; outline-offset: 3px; }
343
+ .otp-code:disabled { opacity: 0.38; cursor: not-allowed; }
344
+ .otp-code-primary { color: var(--color-primary); }
345
+ .otp-code-secondary { color: var(--color-secondary); }
346
+ .otp-code-tertiary { color: var(--color-tertiary); }
347
+ .otp-code-info { color: var(--color-info); }
348
+ .otp-code-success { color: var(--color-success); }
349
+ .otp-code-warning { color: var(--color-warning); }
350
+ .otp-code-error { color: var(--color-error); }
351
+ .otp-input-helper-success { font-size: 0.75rem; color: var(--color-success); }
352
+
353
+ @supports (background-image: repeating-linear-gradient(to right, transparent 0 1ch)) {
354
+ .otp-code {
355
+ background-image: repeating-linear-gradient(to right, transparent 0 0.125ch, var(--color-outline-variant) 0.125ch 0.875ch, transparent 0.875ch 1ch);
356
+ background-size: calc(var(--otp-length) * 1ch) 3px;
357
+ background-repeat: no-repeat;
358
+ background-origin: content-box;
359
+ background-position: left bottom;
360
+ }
361
+ }
362
+ @media (forced-colors: active) { .otp-code { background-image: none; } }
363
+
246
364
  @media (prefers-reduced-motion: reduce) {
247
365
  .otp-input-field {
248
366
  transition: none;