ngwr 12.2.0 → 13.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.
Files changed (174) hide show
  1. package/README.md +11 -1
  2. package/border-glow/border-glow.scss +1 -1
  3. package/border-glow/styles/_index.scss +15 -233
  4. package/border-glow/styles/_rules.scss +228 -0
  5. package/breadcrumbs/breadcrumbs.scss +1 -1
  6. package/breadcrumbs/styles/_index.scss +15 -78
  7. package/breadcrumbs/styles/_rules.scss +84 -0
  8. package/card/card.scss +1 -1
  9. package/card/styles/_index.scss +15 -96
  10. package/card/styles/_rules.scss +96 -0
  11. package/cascader/cascader.scss +1 -1
  12. package/cascader/styles/_index.scss +15 -244
  13. package/cascader/styles/_rules.scss +261 -0
  14. package/checkbox/styles/_index.scss +7 -0
  15. package/circular-text/circular-text.scss +1 -1
  16. package/circular-text/styles/_index.scss +15 -63
  17. package/circular-text/styles/_rules.scss +58 -0
  18. package/color-picker/styles/_index.scss +8 -0
  19. package/drag-drop/sortable-list.scss +1 -1
  20. package/drag-drop/styles/_index.scss +14 -143
  21. package/drag-drop/styles/_rules.scss +151 -0
  22. package/falling-text/falling-text.scss +1 -1
  23. package/falling-text/styles/_index.scss +15 -36
  24. package/falling-text/styles/_rules.scss +31 -0
  25. package/fesm2022/ngwr-border-glow.mjs +2 -2
  26. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  27. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  28. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  29. package/fesm2022/ngwr-card.mjs +2 -2
  30. package/fesm2022/ngwr-card.mjs.map +1 -1
  31. package/fesm2022/ngwr-cascader.mjs +27 -9
  32. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  33. package/fesm2022/ngwr-checkbox.mjs +86 -8
  34. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  35. package/fesm2022/ngwr-circular-text.mjs +2 -2
  36. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  37. package/fesm2022/ngwr-color-picker.mjs +57 -7
  38. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  39. package/fesm2022/ngwr-date-picker.mjs +8 -0
  40. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  41. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  42. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  43. package/fesm2022/ngwr-falling-text.mjs +2 -2
  44. package/fesm2022/ngwr-falling-text.mjs.map +1 -1
  45. package/fesm2022/ngwr-file-upload.mjs +29 -8
  46. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  47. package/fesm2022/ngwr-form.mjs +48 -1
  48. package/fesm2022/ngwr-form.mjs.map +1 -1
  49. package/fesm2022/ngwr-glitch-text.mjs +2 -2
  50. package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
  51. package/fesm2022/ngwr-gradient-text.mjs +2 -2
  52. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  53. package/fesm2022/ngwr-input-otp.mjs +41 -3
  54. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  55. package/fesm2022/ngwr-knob.mjs +17 -2
  56. package/fesm2022/ngwr-knob.mjs.map +1 -1
  57. package/fesm2022/ngwr-lightbox.mjs +2 -2
  58. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  59. package/fesm2022/ngwr-list.mjs +2 -2
  60. package/fesm2022/ngwr-list.mjs.map +1 -1
  61. package/fesm2022/ngwr-loading-bar.mjs +2 -2
  62. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  63. package/fesm2022/ngwr-marquee.mjs +2 -2
  64. package/fesm2022/ngwr-marquee.mjs.map +1 -1
  65. package/fesm2022/ngwr-pagination.mjs +1 -1
  66. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  67. package/fesm2022/ngwr-radio.mjs +63 -8
  68. package/fesm2022/ngwr-radio.mjs.map +1 -1
  69. package/fesm2022/ngwr-rating.mjs +17 -2
  70. package/fesm2022/ngwr-rating.mjs.map +1 -1
  71. package/fesm2022/ngwr-segmented.mjs +22 -3
  72. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  73. package/fesm2022/ngwr-select.mjs +46 -12
  74. package/fesm2022/ngwr-select.mjs.map +1 -1
  75. package/fesm2022/ngwr-shiny-text.mjs +2 -2
  76. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  77. package/fesm2022/ngwr-slider.mjs +21 -6
  78. package/fesm2022/ngwr-slider.mjs.map +1 -1
  79. package/fesm2022/ngwr-spotlight-card.mjs +2 -2
  80. package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
  81. package/fesm2022/ngwr-star-border.mjs +2 -2
  82. package/fesm2022/ngwr-star-border.mjs.map +1 -1
  83. package/fesm2022/ngwr-switch.mjs +44 -4
  84. package/fesm2022/ngwr-switch.mjs.map +1 -1
  85. package/fesm2022/ngwr-table.mjs +1 -1
  86. package/fesm2022/ngwr-table.mjs.map +1 -1
  87. package/fesm2022/ngwr-textarea.mjs +5 -2
  88. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  89. package/fesm2022/ngwr-theme.mjs +41 -1
  90. package/fesm2022/ngwr-theme.mjs.map +1 -1
  91. package/fesm2022/ngwr-tilt-card.mjs +2 -2
  92. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  93. package/fesm2022/ngwr-transfer.mjs +38 -6
  94. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  95. package/fesm2022/ngwr-tree.mjs +33 -7
  96. package/fesm2022/ngwr-tree.mjs.map +1 -1
  97. package/fesm2022/ngwr-typewriter.mjs +2 -2
  98. package/fesm2022/ngwr-typewriter.mjs.map +1 -1
  99. package/fesm2022/ngwr-version.mjs +1 -1
  100. package/fesm2022/ngwr-version.mjs.map +1 -1
  101. package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
  102. package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
  103. package/fesm2022/ngwr-waves.mjs +2 -2
  104. package/fesm2022/ngwr-waves.mjs.map +1 -1
  105. package/file-upload/styles/_index.scss +6 -0
  106. package/glitch-text/glitch-text.scss +1 -1
  107. package/glitch-text/styles/_index.scss +14 -164
  108. package/glitch-text/styles/_rules.scss +160 -0
  109. package/gradient-text/gradient-text.scss +1 -1
  110. package/gradient-text/styles/_index.scss +15 -118
  111. package/gradient-text/styles/_rules.scss +113 -0
  112. package/input/styles/_index.scss +13 -0
  113. package/input-otp/styles/_index.scss +8 -0
  114. package/lightbox/lightbox.scss +1 -1
  115. package/lightbox/styles/_index.scss +15 -191
  116. package/lightbox/styles/_rules.scss +198 -0
  117. package/list/list.scss +1 -1
  118. package/list/styles/_index.scss +15 -101
  119. package/list/styles/_rules.scss +108 -0
  120. package/llms-full.txt +3 -3
  121. package/loading-bar/loading-bar.scss +1 -1
  122. package/loading-bar/styles/_index.scss +15 -33
  123. package/loading-bar/styles/_rules.scss +28 -0
  124. package/marquee/marquee.scss +1 -1
  125. package/marquee/styles/_index.scss +15 -155
  126. package/marquee/styles/_rules.scss +150 -0
  127. package/package.json +1 -1
  128. package/segmented/styles/_index.scss +8 -0
  129. package/select/styles/_index.scss +17 -1
  130. package/shiny-text/shiny-text.scss +1 -1
  131. package/shiny-text/styles/_index.scss +14 -105
  132. package/shiny-text/styles/_rules.scss +101 -0
  133. package/slider/styles/_index.scss +8 -0
  134. package/spotlight-card/spotlight-card.scss +1 -1
  135. package/spotlight-card/styles/_index.scss +15 -52
  136. package/spotlight-card/styles/_rules.scss +47 -0
  137. package/star-border/star-border.scss +1 -1
  138. package/star-border/styles/_index.scss +15 -125
  139. package/star-border/styles/_rules.scss +120 -0
  140. package/switch/styles/_index.scss +7 -0
  141. package/theme/styles/_dark.scss +81 -4
  142. package/theme/styles/_index.scss +1 -0
  143. package/theme/styles/_motion.scss +94 -0
  144. package/tilt-card/styles/_index.scss +15 -45
  145. package/tilt-card/styles/_rules.scss +40 -0
  146. package/tilt-card/tilt-card.scss +1 -1
  147. package/tree/styles/_index.scss +15 -1
  148. package/types/ngwr-cascader.d.ts +17 -1
  149. package/types/ngwr-checkbox.d.ts +51 -3
  150. package/types/ngwr-color-picker.d.ts +19 -1
  151. package/types/ngwr-file-upload.d.ts +18 -1
  152. package/types/ngwr-form.d.ts +51 -2
  153. package/types/ngwr-input-otp.d.ts +23 -1
  154. package/types/ngwr-knob.d.ts +14 -0
  155. package/types/ngwr-radio.d.ts +37 -2
  156. package/types/ngwr-rating.d.ts +14 -0
  157. package/types/ngwr-segmented.d.ts +18 -1
  158. package/types/ngwr-select.d.ts +17 -1
  159. package/types/ngwr-slider.d.ts +14 -1
  160. package/types/ngwr-switch.d.ts +26 -2
  161. package/types/ngwr-textarea.d.ts +3 -0
  162. package/types/ngwr-theme.d.ts +24 -0
  163. package/types/ngwr-transfer.d.ts +17 -1
  164. package/types/ngwr-tree.d.ts +21 -1
  165. package/types/ngwr-version.d.ts +1 -1
  166. package/typewriter/styles/_index.scss +15 -38
  167. package/typewriter/styles/_rules.scss +33 -0
  168. package/typewriter/typewriter.scss +1 -1
  169. package/virtual-scroll/styles/_index.scss +15 -32
  170. package/virtual-scroll/styles/_rules.scss +27 -0
  171. package/virtual-scroll/virtual-scroll.scss +1 -1
  172. package/waves/styles/_index.scss +15 -50
  173. package/waves/styles/_rules.scss +45 -0
  174. package/waves/waves.scss +1 -1
@@ -85,6 +85,14 @@
85
85
  font-variant-numeric: tabular-nums;
86
86
  }
87
87
 
88
+ // A read-only slider still takes focus and still announces its value, so it
89
+ // keeps the visual and only stops inviting the drag — `grab` on something that
90
+ // cannot move is the part that looks broken. Matches `wr-knob--readonly`.
91
+ &--readonly &__track,
92
+ &--readonly &__thumb {
93
+ cursor: default;
94
+ }
95
+
88
96
  &--disabled {
89
97
  --wr-slider-thumb-border: var(--wr-color-on-surface-muted);
90
98
  --wr-slider-fill: var(--wr-color-on-surface-muted);
@@ -1 +1 @@
1
- @forward './styles';
1
+ @forward './styles/rules';
@@ -1,52 +1,15 @@
1
- // Pull the whole theme so a standalone `@use 'ngwr/<name>'` brings in every
2
- // design token this stylesheet references. Sass deduplicates `@use` per
3
- // compilation, so the umbrella sheet still emits the theme only once.
4
- @use '../../theme/styles' as theme;
5
-
6
- // Cursor-tracked spotlight card (reactbits port). The `--wr-spotlight-x/y`
7
- // vars come from the host's mousemove handler; `--wr-spotlight-color` is
8
- // set inline from the `[spotlightColor]` input when provided — otherwise
9
- // the theme picks the default below. `--wr-color-on-surface-rgb` flips between
10
- // themes (dark on light surfaces, light on dark), so a single tint reads
11
- // as a dark-ish glow in light mode and a light beam in dark mode.
12
-
13
- .wr-spotlight-card {
14
- --wr-spotlight-x: 50%;
15
- --wr-spotlight-y: 50%;
16
- --wr-spotlight-color: rgba(var(--wr-color-on-surface-rgb), 0.15);
17
-
18
- position: relative;
19
- display: block;
20
- border-radius: 1.5rem;
21
- border: 1px solid var(--wr-color-outline);
22
- background-color: var(--wr-color-surface);
23
- padding: 2rem;
24
- overflow: hidden;
25
- color: var(--wr-color-on-surface);
26
-
27
- &::before {
28
- content: '';
29
- position: absolute;
30
- inset: 0;
31
- background: radial-gradient(
32
- circle at var(--wr-spotlight-x) var(--wr-spotlight-y),
33
- var(--wr-spotlight-color),
34
- transparent var(--wr-spotlight-radius, 80%)
35
- );
36
- opacity: 0;
37
- transition: opacity 0.5s ease;
38
- pointer-events: none;
39
- }
40
-
41
- &:hover::before,
42
- &:focus-within::before {
43
- opacity: 0.6;
44
- }
45
- }
46
-
47
- // Dark mode: `--wr-color-on-surface-rgb` already flips to a light value, so the
48
- // default tint above becomes a light beam on the navy surface — nudge the
49
- // alpha up a touch so it reads as bright as the classic spotlight.
50
- [data-theme='dark'] .wr-spotlight-card {
51
- --wr-spotlight-color: rgba(var(--wr-color-on-surface-rgb), 0.22);
52
- }
1
+ // Public Sass entry for `@use 'ngwr/spotlight-card'`.
2
+ //
3
+ // Pulls the whole theme so a standalone import still brings in every design
4
+ // token these rules reference, then loads the rules themselves from `_rules`.
5
+ //
6
+ // The split is what keeps the token layer out of the JavaScript bundle.
7
+ // `spotlight-card.scss` is the component's `styleUrl`, and a component stylesheet is
8
+ // its OWN Sass compilation — `@use` only deduplicates within one. So while the
9
+ // umbrella sheet emits the theme once, forwarding it from here would inline a
10
+ // second, byte-identical copy of `:root`, `[data-theme='dark']` and the
11
+ // box-sizing reset into the compiled component styles, which ng-packagr then
12
+ // embeds in the FESM bundle as a string. `spotlight-card.scss` forwards `_rules`
13
+ // instead, so it carries this component's own CSS and nothing else.
14
+ @use '../../theme/styles';
15
+ @forward 'rules';
@@ -0,0 +1,47 @@
1
+ // Cursor-tracked spotlight card (reactbits port). The `--wr-spotlight-x/y`
2
+ // vars come from the host's mousemove handler; `--wr-spotlight-color` is
3
+ // set inline from the `[spotlightColor]` input when provided — otherwise
4
+ // the theme picks the default below. `--wr-color-on-surface-rgb` flips between
5
+ // themes (dark on light surfaces, light on dark), so a single tint reads
6
+ // as a dark-ish glow in light mode and a light beam in dark mode.
7
+
8
+ .wr-spotlight-card {
9
+ --wr-spotlight-x: 50%;
10
+ --wr-spotlight-y: 50%;
11
+ --wr-spotlight-color: rgba(var(--wr-color-on-surface-rgb), 0.15);
12
+
13
+ position: relative;
14
+ display: block;
15
+ border-radius: 1.5rem;
16
+ border: 1px solid var(--wr-color-outline);
17
+ background-color: var(--wr-color-surface);
18
+ padding: 2rem;
19
+ overflow: hidden;
20
+ color: var(--wr-color-on-surface);
21
+
22
+ &::before {
23
+ content: '';
24
+ position: absolute;
25
+ inset: 0;
26
+ background: radial-gradient(
27
+ circle at var(--wr-spotlight-x) var(--wr-spotlight-y),
28
+ var(--wr-spotlight-color),
29
+ transparent var(--wr-spotlight-radius, 80%)
30
+ );
31
+ opacity: 0;
32
+ transition: opacity 0.5s ease;
33
+ pointer-events: none;
34
+ }
35
+
36
+ &:hover::before,
37
+ &:focus-within::before {
38
+ opacity: 0.6;
39
+ }
40
+ }
41
+
42
+ // Dark mode: `--wr-color-on-surface-rgb` already flips to a light value, so the
43
+ // default tint above becomes a light beam on the navy surface — nudge the
44
+ // alpha up a touch so it reads as bright as the classic spotlight.
45
+ [data-theme='dark'] .wr-spotlight-card {
46
+ --wr-spotlight-color: rgba(var(--wr-color-on-surface-rgb), 0.22);
47
+ }
@@ -1 +1 @@
1
- @forward './styles';
1
+ @forward './styles/rules';
@@ -1,125 +1,15 @@
1
- // Pull the whole theme so a standalone `@use 'ngwr/<name>'` brings in every
2
- // design token this stylesheet references. Sass deduplicates `@use` per
3
- // compilation, so the umbrella sheet still emits the theme only once.
4
- @use '../../theme/styles' as theme;
5
-
6
- // Star border (reactbits port). Two oversized radial-gradient "comets"
7
- // sweep along the top and bottom edges of the rounded container; the
8
- // projected content sits on a token-themed inner panel above them.
9
-
10
- .wr-star-border {
11
- // Light surfaces get a primary comet; dark theme flips to the classic
12
- // white (see block below). `[color]` overrides both inline. The light
13
- // comet gets a wider core + full opacity — a thin 10% tail washes out
14
- // to gray on white surfaces.
15
- --wr-star-border-color: var(--wr-color-primary);
16
- --wr-star-border-speed: 6s;
17
- --wr-star-border-radius: 20px;
18
- --wr-star-border-stop: 16%;
19
- --wr-star-border-opacity: 1;
20
-
21
- display: inline-block;
22
- position: relative;
23
- border: 0;
24
- background: transparent;
25
- border-radius: var(--wr-star-border-radius);
26
- overflow: hidden;
27
- cursor: inherit;
28
-
29
- &__ray {
30
- position: absolute;
31
- display: block;
32
- width: 300%;
33
- height: 50%;
34
- opacity: var(--wr-star-border-opacity);
35
- border-radius: 50%;
36
- background: radial-gradient(circle, var(--wr-star-border-color), transparent var(--wr-star-border-stop));
37
- animation: wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;
38
- z-index: 0;
39
-
40
- &--bottom {
41
- bottom: -12px;
42
- // rtl-ok: the comet's off-screen start mark — `wr-star-border-bottom` sweeps it by `translate(-100%, 0%)`
43
- right: -250%;
44
- }
45
-
46
- &--top {
47
- top: -12px;
48
- // rtl-ok: the comet's off-screen start mark — `wr-star-border-top` sweeps it by `translate(100%, 0%)`
49
- left: -250%;
50
- animation-name: wr-star-border-top;
51
- }
52
- }
53
-
54
- // Single-ray variant — drop the top comet.
55
- &--single &__ray--top {
56
- display: none;
57
- }
58
-
59
- // Hover-only variant — rays sit hidden until the pointer is in. The
60
- // idle state must drop the animation entirely (not pause it): a paused
61
- // animation pins `opacity` at its 0% keyframe value, which beats the
62
- // cascade and leaves a frozen comet visible.
63
- &--hover &__ray {
64
- opacity: 0;
65
- animation: none;
66
- }
67
-
68
- &--hover:hover &__ray {
69
- animation: wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;
70
- }
71
-
72
- &--hover:hover &__ray--top {
73
- animation-name: wr-star-border-top;
74
- }
75
-
76
- &__inner {
77
- position: relative;
78
- display: block;
79
- border: 1px solid var(--wr-color-outline);
80
- background: var(--wr-color-surface);
81
- color: var(--wr-color-on-surface);
82
- font-size: 1rem;
83
- text-align: center;
84
- padding: 1rem 1.625rem;
85
- border-radius: var(--wr-star-border-radius);
86
- z-index: 1;
87
- }
88
- }
89
-
90
- [data-theme='dark'] .wr-star-border {
91
- --wr-star-border-color: #ffffff;
92
- --wr-star-border-stop: 10%;
93
- --wr-star-border-opacity: 0.7;
94
- }
95
-
96
- @keyframes wr-star-border-bottom {
97
- 0% {
98
- transform: translate(0%, 0%);
99
- opacity: 1;
100
- }
101
- 100% {
102
- transform: translate(-100%, 0%);
103
- opacity: 0;
104
- }
105
- }
106
-
107
- @keyframes wr-star-border-top {
108
- 0% {
109
- transform: translate(0%, 0%);
110
- opacity: 1;
111
- }
112
- 100% {
113
- transform: translate(100%, 0%);
114
- opacity: 0;
115
- }
116
- }
117
-
118
- // Reduced motion: no sweeping comets — a frozen ray reads as a smudge,
119
- // so hide them and keep the panel border as the resting look.
120
- @media (prefers-reduced-motion: reduce) {
121
- .wr-star-border__ray {
122
- animation: none !important;
123
- opacity: 0 !important;
124
- }
125
- }
1
+ // Public Sass entry for `@use 'ngwr/star-border'`.
2
+ //
3
+ // Pulls the whole theme so a standalone import still brings in every design
4
+ // token these rules reference, then loads the rules themselves from `_rules`.
5
+ //
6
+ // The split is what keeps the token layer out of the JavaScript bundle.
7
+ // `star-border.scss` is the component's `styleUrl`, and a component stylesheet is
8
+ // its OWN Sass compilation — `@use` only deduplicates within one. So while the
9
+ // umbrella sheet emits the theme once, forwarding it from here would inline a
10
+ // second, byte-identical copy of `:root`, `[data-theme='dark']` and the
11
+ // box-sizing reset into the compiled component styles, which ng-packagr then
12
+ // embeds in the FESM bundle as a string. `star-border.scss` forwards `_rules`
13
+ // instead, so it carries this component's own CSS and nothing else.
14
+ @use '../../theme/styles';
15
+ @forward 'rules';
@@ -0,0 +1,120 @@
1
+ // Star border (reactbits port). Two oversized radial-gradient "comets"
2
+ // sweep along the top and bottom edges of the rounded container; the
3
+ // projected content sits on a token-themed inner panel above them.
4
+
5
+ .wr-star-border {
6
+ // Light surfaces get a primary comet; dark theme flips to the classic
7
+ // white (see block below). `[color]` overrides both inline. The light
8
+ // comet gets a wider core + full opacity — a thin 10% tail washes out
9
+ // to gray on white surfaces.
10
+ --wr-star-border-color: var(--wr-color-primary);
11
+ --wr-star-border-speed: 6s;
12
+ --wr-star-border-radius: 20px;
13
+ --wr-star-border-stop: 16%;
14
+ --wr-star-border-opacity: 1;
15
+
16
+ display: inline-block;
17
+ position: relative;
18
+ border: 0;
19
+ background: transparent;
20
+ border-radius: var(--wr-star-border-radius);
21
+ overflow: hidden;
22
+ cursor: inherit;
23
+
24
+ &__ray {
25
+ position: absolute;
26
+ display: block;
27
+ width: 300%;
28
+ height: 50%;
29
+ opacity: var(--wr-star-border-opacity);
30
+ border-radius: 50%;
31
+ background: radial-gradient(circle, var(--wr-star-border-color), transparent var(--wr-star-border-stop));
32
+ animation: wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;
33
+ z-index: 0;
34
+
35
+ &--bottom {
36
+ bottom: -12px;
37
+ // rtl-ok: the comet's off-screen start mark — `wr-star-border-bottom` sweeps it by `translate(-100%, 0%)`
38
+ right: -250%;
39
+ }
40
+
41
+ &--top {
42
+ top: -12px;
43
+ // rtl-ok: the comet's off-screen start mark — `wr-star-border-top` sweeps it by `translate(100%, 0%)`
44
+ left: -250%;
45
+ animation-name: wr-star-border-top;
46
+ }
47
+ }
48
+
49
+ // Single-ray variant — drop the top comet.
50
+ &--single &__ray--top {
51
+ display: none;
52
+ }
53
+
54
+ // Hover-only variant — rays sit hidden until the pointer is in. The
55
+ // idle state must drop the animation entirely (not pause it): a paused
56
+ // animation pins `opacity` at its 0% keyframe value, which beats the
57
+ // cascade and leaves a frozen comet visible.
58
+ &--hover &__ray {
59
+ opacity: 0;
60
+ animation: none;
61
+ }
62
+
63
+ &--hover:hover &__ray {
64
+ animation: wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;
65
+ }
66
+
67
+ &--hover:hover &__ray--top {
68
+ animation-name: wr-star-border-top;
69
+ }
70
+
71
+ &__inner {
72
+ position: relative;
73
+ display: block;
74
+ border: 1px solid var(--wr-color-outline);
75
+ background: var(--wr-color-surface);
76
+ color: var(--wr-color-on-surface);
77
+ font-size: 1rem;
78
+ text-align: center;
79
+ padding: 1rem 1.625rem;
80
+ border-radius: var(--wr-star-border-radius);
81
+ z-index: 1;
82
+ }
83
+ }
84
+
85
+ [data-theme='dark'] .wr-star-border {
86
+ --wr-star-border-color: #ffffff;
87
+ --wr-star-border-stop: 10%;
88
+ --wr-star-border-opacity: 0.7;
89
+ }
90
+
91
+ @keyframes wr-star-border-bottom {
92
+ 0% {
93
+ transform: translate(0%, 0%);
94
+ opacity: 1;
95
+ }
96
+ 100% {
97
+ transform: translate(-100%, 0%);
98
+ opacity: 0;
99
+ }
100
+ }
101
+
102
+ @keyframes wr-star-border-top {
103
+ 0% {
104
+ transform: translate(0%, 0%);
105
+ opacity: 1;
106
+ }
107
+ 100% {
108
+ transform: translate(100%, 0%);
109
+ opacity: 0;
110
+ }
111
+ }
112
+
113
+ // Reduced motion: no sweeping comets — a frozen ray reads as a smudge,
114
+ // so hide them and keep the panel border as the resting look.
115
+ @media (prefers-reduced-motion: reduce) {
116
+ .wr-star-border__ray {
117
+ animation: none !important;
118
+ opacity: 0 !important;
119
+ }
120
+ }
@@ -95,6 +95,13 @@
95
95
  }
96
96
  }
97
97
 
98
+ // A read-only switch still takes focus and still submits its value, so it
99
+ // keeps the full visual and only stops inviting the click — dimming it would
100
+ // say "disabled", which is the other state.
101
+ &--readonly &__label {
102
+ cursor: default;
103
+ }
104
+
98
105
  &--disabled {
99
106
  --wr-switch-color: var(--wr-color-on-surface-muted);
100
107
  pointer-events: none;
@@ -3,13 +3,90 @@
3
3
  @use 'colors';
4
4
  @use 'variables';
5
5
 
6
- // Dark theme overrides — opt-in via `[data-theme="dark"]` on <html>.
7
- // Drive the toggle from `WrTheme` (provided automatically with
8
- // `provideWrTheme(...)`).
6
+ // Dark theme overrides — opt-in via `[data-theme="dark"]` on <html>, or
7
+ // whatever `$theme-attribute` below is configured to. Drive the toggle from
8
+ // `WrTheme` (provided automatically with `provideWrTheme(...)`).
9
9
  //
10
10
  // Only semantic tokens flip — palette hues stay so brand-coloured
11
11
  // elements look the same regardless of theme.
12
12
 
13
+ /// The attribute the dark block keys on. Must match the `attribute` passed to
14
+ /// `provideWrTheme(...)`, and that pairing is the whole reason this variable
15
+ /// exists: `WrThemeConfig.attribute` is a RUNTIME value, and no CSS selector can
16
+ /// read one. Until v13 the selector below was the literal `[data-theme='dark']`,
17
+ /// so an app that set `provideWrTheme({ attribute: 'data-color-mode' })` got a
18
+ /// service reporting `resolved() === 'dark'`, the attribute written correctly
19
+ /// onto `<html>`, and a page that stayed light forever — no error, no warning.
20
+ ///
21
+ /// Configure the two together:
22
+ ///
23
+ /// ```scss
24
+ /// // styles.scss
25
+ /// @use 'ngwr' with ($theme-attribute: 'data-color-mode');
26
+ /// ```
27
+ /// ```ts
28
+ /// provideWrTheme({ attribute: 'data-color-mode' })
29
+ /// ```
30
+ ///
31
+ /// Only the configured attribute is emitted — the default is NOT kept as a
32
+ /// second selector. Renaming it is what decouples ngwr from another design
33
+ /// system that already owns `data-theme`, and emitting both would re-couple
34
+ /// them: that system's own `data-theme="dark"` would flip ngwr's tokens behind
35
+ /// the app's back, which is the exact collision the rename exists to avoid.
36
+ ///
37
+ /// If the two disagree the page silently stays light, so `WrTheme` publishes a
38
+ /// dev-mode check for it: the light block below writes the configured name into
39
+ /// `--wr-theme-attribute`, and the service warns when what it reads there is not
40
+ /// what it was configured with. A stylesheet that is missing or predates v13
41
+ /// leaves the property empty and the check stays quiet — it can only fire on a
42
+ /// real disagreement.
43
+ $theme-attribute: 'data-theme' !default;
44
+
45
+ /// The dark-theme selector, for a stylesheet that has to build its own.
46
+ ///
47
+ /// @example scss
48
+ /// #{dark.dark-selector()} .my-thing { ... }
49
+ @function dark-selector() {
50
+ @return '[#{$theme-attribute}=\'dark\']';
51
+ }
52
+
53
+ /// Wrap a declaration block so it only applies in dark mode, honouring
54
+ /// `$theme-attribute`. Prefer this over writing `[data-theme='dark']` by hand —
55
+ /// a hand-written literal is exactly what BUG-3 was.
56
+ ///
57
+ /// @example scss
58
+ /// @use 'ngwr/theme' as theme;
59
+ ///
60
+ /// .my-card {
61
+ /// background: #fff;
62
+ ///
63
+ /// @include theme.dark {
64
+ /// background: #0b1120;
65
+ /// }
66
+ /// }
67
+ @mixin dark() {
68
+ @if & {
69
+ @at-root #{dark-selector()} #{&} {
70
+ @content;
71
+ }
72
+ } @else {
73
+ #{dark-selector()} {
74
+ @content;
75
+ }
76
+ }
77
+ }
78
+
79
+ :root {
80
+ // The attribute this compiled stylesheet keys its dark block on, published so
81
+ // `WrTheme` can compare it against `WrThemeConfig.attribute` in dev mode and
82
+ // say so when the two were configured apart. Read with
83
+ // `getPropertyValue('--wr-theme-attribute')`, never with `var()`.
84
+ //
85
+ // unused-ok: read by `WrTheme` through the CSSOM rather than painted with —
86
+ // it carries a name, not a value, so no declaration can reference it.
87
+ --wr-theme-attribute: #{$theme-attribute};
88
+ }
89
+
13
90
  // Intents re-tuned for the dark canvas. Looped (below) so each gets the
14
91
  // FULL symmetric shade set — the hand blocks used to ship only base+rgb,
15
92
  // leaving -light/-dark/-lighter/-darker baked from the LIGHT palette.
@@ -37,7 +114,7 @@ $_light: #262f44;
37
114
  $_medium: #6d7682;
38
115
  $_dark: #e6ebf3;
39
116
 
40
- [data-theme='dark'] {
117
+ #{dark-selector()} {
41
118
  // Canvas (was `--wr-color-white` in light mode)
42
119
  // Deeper than slate-900 so an elevated surface in slate-800 reads
43
120
  // as a real layer rather than vanishing into the background.
@@ -4,3 +4,4 @@
4
4
  @forward 'dark';
5
5
  @forward 'focus';
6
6
  @forward 'mixins';
7
+ @forward 'motion';
@@ -0,0 +1,94 @@
1
+ // `prefers-reduced-motion` for the always-on chrome.
2
+ //
3
+ // Why this lives in the theme layer rather than in each component. The
4
+ // animation catalogue (`ngwr/glitch-text`, `ngwr/gradient-text`, …) guards
5
+ // itself, component by component — those are opt-in effects, and each one knows
6
+ // what its own stillness should look like. The ten blocks below are the
7
+ // opposite: chrome nobody opted into. A spinner and a skeleton animate on
8
+ // essentially every page of every app, and four of the animations here never
9
+ // stop at all (`wr-spinner-rotate`, `wr-spinner-dash`, `wr-skeleton-shimmer`,
10
+ // `wr-card-spin`, `wr-lightbox-shimmer`), which is the case vestibular guidance
11
+ // is most emphatic about. Collecting them in one place is what makes the set
12
+ // reviewable — the gap this closes existed because nine separate stylesheets
13
+ // each forgot, and nine separate stylesheets can each forget again.
14
+ //
15
+ // **Every declaration here is `!important`, and that is structural rather than
16
+ // lazy.** The theme layer is loaded first — `styles.scss` forwards it before any
17
+ // component, and a standalone `@use 'ngwr/spinner'` pulls it in ahead of the
18
+ // spinner's own rules — so at equal specificity the component always wins on
19
+ // source order. `!important` is the only lever a stylesheet that must come first
20
+ // has. `.wr-overlay-sheet` needs one more: its animation is itself `!important`
21
+ // (it has to beat four components' panel sizing), so the class is doubled here
22
+ // to out-specify it.
23
+ //
24
+ // What reduced motion MEANS is decided per animation, not globally:
25
+ //
26
+ // - An ENTER animation is removed. The panel still arrives, it just does not
27
+ // travel to get there. Every keyframe set below animates from a transparent,
28
+ // offset or scaled-down state TO the element's resting one, with no
29
+ // `forwards`, so removing it lands on exactly the frame the animation was
30
+ // heading for. Nothing in the library waits on `animationend`, so nothing
31
+ // is left un-dismissed.
32
+ // - A decorative INFINITE loop is removed outright — a skeleton's shimmer and
33
+ // a lightbox's shimmer say nothing the static placeholder does not.
34
+ // - A SPINNER is slowed, not stopped. Its rotation IS the message: a frozen
35
+ // spinner does not read as "reduced motion", it reads as "hung". So the
36
+ // rotation stays at half speed and the pulsing arc — the part that carries
37
+ // no information — is frozen at a fixed sweep. Same call Bootstrap makes,
38
+ // and the same reading as WCAG 2.3.3, which is about non-essential motion.
39
+ // - The TOAST progress bar is deliberately left alone. It is a countdown: it
40
+ // is the only thing on screen saying how long is left before the toast
41
+ // leaves, and freezing it would not remove information, it would state a
42
+ // falsehood — a full bar that never drains. It is also 2px tall and moves
43
+ // along one axis at constant speed, the mildest shape motion sensitivity
44
+ // takes. If that trade is ever revisited, revisit it here.
45
+
46
+ @media (prefers-reduced-motion: reduce) {
47
+ // Overlay enter choreography — dialog, dropdown, popconfirm, toast, lightbox
48
+ // viewer, and all four drawer edges.
49
+ .wr-dialog-panel,
50
+ .wr-dropdown-menu,
51
+ .wr-popconfirm-overlay > .wr-popconfirm,
52
+ .wr-toast,
53
+ .wr-lightbox-viewer,
54
+ .wr-drawer__panel--left,
55
+ .wr-drawer__panel--right,
56
+ .wr-drawer__panel--top,
57
+ .wr-drawer__panel--bottom {
58
+ animation: none !important;
59
+ }
60
+
61
+ // The responsive bottom sheet, which slides a full panel height up the
62
+ // screen — the largest single translation the library performs. Doubled
63
+ // class: `overlay/styles/_index.scss` sets this animation `!important`, and it
64
+ // is loaded after this file.
65
+ .wr-overlay-sheet.wr-overlay-sheet {
66
+ animation: none !important;
67
+ }
68
+
69
+ // Decorative loading shimmers. Both ride a pseudo-element whose resting
70
+ // position is off-screen or at its gradient origin, so stopping them leaves a
71
+ // plain tinted placeholder rather than a half-drawn band.
72
+ .wr-skeleton--animated::after,
73
+ .wr-lightbox--loading::after {
74
+ animation: none !important;
75
+ }
76
+
77
+ // Spinners. The rotation survives at half speed; the arc stops pulsing and
78
+ // holds the sweep the dash animation spends most of its cycle at, so the
79
+ // shape stays a recognisable spinner instead of collapsing to the 1-unit dot
80
+ // its first keyframe starts from.
81
+ .wr-spinner svg {
82
+ animation-duration: 4s !important;
83
+ }
84
+
85
+ .wr-spinner__path {
86
+ animation: none !important;
87
+ stroke-dasharray: 90, 150 !important;
88
+ stroke-dashoffset: 0 !important;
89
+ }
90
+
91
+ .wr-card__spinner {
92
+ animation-duration: 1.4s !important;
93
+ }
94
+ }