@nexterajs/ui 0.0.1 → 0.0.2

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 (44) hide show
  1. package/fesm2022/nexterajs-ui-core.mjs +189 -5
  2. package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
  3. package/fesm2022/nexterajs-ui-dropdowns.mjs +314 -50
  4. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
  5. package/fesm2022/nexterajs-ui-file-uploader.mjs +6 -3
  6. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
  7. package/fesm2022/nexterajs-ui-forms.mjs +729 -370
  8. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
  9. package/fesm2022/nexterajs-ui-grid.mjs +1188 -208
  10. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
  11. package/fesm2022/nexterajs-ui-intl.mjs +15 -5
  12. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
  13. package/fesm2022/nexterajs-ui-notify.mjs +351 -0
  14. package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
  15. package/fesm2022/nexterajs-ui-pivot.mjs +146 -116
  16. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
  17. package/fesm2022/nexterajs-ui-styling.mjs +568 -3
  18. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
  19. package/fesm2022/nexterajs-ui-tree-view.mjs +147 -18
  20. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
  21. package/fesm2022/nexterajs-ui-validation.mjs +247 -92
  22. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
  23. package/fesm2022/nexterajs-ui.mjs +1 -0
  24. package/fesm2022/nexterajs-ui.mjs.map +1 -1
  25. package/package.json +5 -1
  26. package/styles/_dropdown.scss +149 -0
  27. package/styles/_editor.scss +23 -4
  28. package/styles/_icons.scss +92 -0
  29. package/styles/_mixins.scss +104 -1
  30. package/styles/_scrollbar.scss +134 -0
  31. package/styles/_toast.scss +243 -0
  32. package/styles/_tokens.scss +48 -0
  33. package/styles/index.scss +4 -0
  34. package/types/nexterajs-ui-core.d.ts +142 -22
  35. package/types/nexterajs-ui-dropdowns.d.ts +145 -27
  36. package/types/nexterajs-ui-file-uploader.d.ts +4 -1
  37. package/types/nexterajs-ui-forms.d.ts +103 -19
  38. package/types/nexterajs-ui-grid.d.ts +445 -57
  39. package/types/nexterajs-ui-notify.d.ts +147 -0
  40. package/types/nexterajs-ui-pivot.d.ts +35 -22
  41. package/types/nexterajs-ui-styling.d.ts +163 -3
  42. package/types/nexterajs-ui-tree-view.d.ts +50 -1
  43. package/types/nexterajs-ui-validation.d.ts +94 -15
  44. package/types/nexterajs-ui.d.ts +1 -0
@@ -2,6 +2,9 @@
2
2
  // Reproduces the DevExtreme `.dx-dropdownlist-popup`. Global (loaded via styling/index.scss) so it
3
3
  // applies inside the CDK overlay regardless of which component opened it.
4
4
  //
5
+ // The file also holds the editors' trailing-button rule set (`nxe-dropdown-editor-buttons`, below),
6
+ // which is NOT global: each dropdown editor includes it in its own component stylesheet.
7
+ //
5
8
  // Every value below is checked against the reference deployment's bundle
6
9
  // (tools/visual-parity/reference/bright-styles.css); the source rule is named in a comment.
7
10
  // Typography deliberately comes from `.nxe-widget` on the PANE (styling/_widget.scss) — the panel
@@ -110,4 +113,150 @@
110
113
  min-height: 3em;
111
114
  color: var(--nxe-ui-text-muted);
112
115
  }
116
+
117
+ // The panes that replace DevExtreme popups sit flush against their editor (dx measures the
118
+ // overlay at the editor's bottom edge). Host apps migrated from broker carry
119
+ // `.cdk-overlay-pane { margin-top: 5px }` (styles/material/_menu.scss), which was written for
120
+ // mat-menu — dx popups were never CDK panes, so it never reached them. At (0,2,0) this beats it.
121
+ // `.nxe-autocomplete-pane` is deliberately absent: it replaces mat-autocomplete, which did get
122
+ // the 5px, and so did the mat-menu panes that host nxe-list action menus.
123
+ .cdk-overlay-pane.nxe-select-pane,
124
+ .cdk-overlay-pane.nxe-dropdownbox-pane,
125
+ .cdk-overlay-pane.nxe-autocomplete-box-pane,
126
+ .cdk-overlay-pane.nxe-datebox-pane {
127
+ margin-top: 0;
128
+ }
129
+ }
130
+
131
+ // ------------------------------------------------------------------------------------------------
132
+ // Trailing buttons of the dropdown editors — one rule set for select-box, tag-box, drop-down-box
133
+ // and auto-complete. Include it INSIDE the component's host selector:
134
+ //
135
+ // nxe-select-box { @include dropdown.nxe-dropdown-editor-buttons; }
136
+ //
137
+ // so every selector starts with the host element. That keeps them in component styles (present in
138
+ // karma, and loaded after the app's global sheet) while staying LOW enough for an app to recolour:
139
+ // `nxe-select-box .nxe-select-arrow` is (0,1,1), so e.g. a sign-in screen's
140
+ // `.sign-in-company .nxe-select-arrow { color }` at (0,2,0) still wins.
141
+ //
142
+ // Colours are dx.light literals, like the rest of the editor chrome (styles/_editor.scss) — there
143
+ // are no dark editor tokens to route them through.
144
+ // ------------------------------------------------------------------------------------------------
145
+
146
+ // The 34px drop button box, shared with the date box's calendar button.
147
+ // dx: `.dx-dropdowneditor-button { width:34px; min-width:34px; padding:1px; border-radius:4px;
148
+ // height:100% }` around `.dx-dropdowneditor-icon { width:32px; height:100%;
149
+ // border:1px solid transparent; border-radius:3px }`. The state fill is painted on that 32px
150
+ // inner icon, never on the button — which is why measuring `.dx-dropdowneditor-button` found it
151
+ // transparent. `background-clip: content-box` paints the same 32px box (corner radius 4 - 1 = 3px)
152
+ // without an extra element.
153
+ @mixin nxe-drop-button {
154
+ box-sizing: border-box;
155
+ position: relative;
156
+ flex: 0 0 34px;
157
+ // Full editor height rather than a fixed 34px: in a grid cell whose editor is shorter the fill
158
+ // becomes 32 x (height - 2), as dx's does, and the glyph stays centred.
159
+ align-self: stretch;
160
+ display: inline-flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ width: 34px;
164
+ min-width: 34px;
165
+ padding: 1px;
166
+ border-radius: 4px;
167
+ background-clip: content-box;
168
+ color: #333;
169
+ cursor: pointer;
170
+
171
+ svg {
172
+ display: block;
173
+ width: 18px;
174
+ height: 18px;
175
+ fill: currentColor;
176
+ }
177
+ }
178
+
179
+ // dx: `.dx-dropdowneditor.dx-state-active .dx-dropdowneditor-icon,
180
+ // .dx-dropdowneditor.dx-state-hover:not(.dx-custom-button-hovered) .dx-dropdowneditor-icon
181
+ // { background-color:#e6e6e6 }` and
182
+ // `.dx-dropdowneditor.dx-dropdowneditor-active .dx-dropdowneditor-icon
183
+ // { background-color:#b3b3b3; opacity:1 }`.
184
+ //
185
+ // PRECEDENCE — do not reorder, do not add !important. dx's hover selector is (0,4,0) (the :not()
186
+ // counts as a class) and beats the open one at (0,3,0), so an open editor under the pointer shows
187
+ // #e6e6e6 and turns #b3b3b3 only once the pointer leaves. Here hover/active is (0,3,1) against open
188
+ // at (0,2,1) — the same relationship, so source order does not decide it and must not be made to.
189
+ // (The library's panes open with a transparent CDK backdrop, so the host stops matching :hover
190
+ // while open and the fill reads #b3b3b3 for the whole open period. That residual is known.)
191
+ @mixin nxe-drop-button-states($button, $open) {
192
+ &:hover:not(.nxe-disabled) #{$button},
193
+ &:active:not(.nxe-disabled) #{$button} {
194
+ background-color: #e6e6e6;
195
+ }
196
+
197
+ #{$open} {
198
+ background-color: #b3b3b3;
199
+ }
200
+ }
201
+
202
+ // The clear (×) area — one definition for the dropdown editors' `.nxe-select-clear` (below) and the
203
+ // text editors' `.nxe-clear-button-area` (styles/_editor.scss: text-box, number-box, date-box).
204
+ // dx: `.dx-show-clear-button .dx-clear-button-area { width:34px; min-width:34px; height:100% }`
205
+ // holding `.dx-icon-clear { color:#999; top:50%; margin-top:-17px; padding:8px; width:34px;
206
+ // height:34px; font-size:18px }` — an 18px glyph box centred in the area. #999 in every state: dx
207
+ // has no hover colour for it.
208
+ @mixin nxe-clear-button {
209
+ box-sizing: border-box;
210
+ flex: 0 0 34px;
211
+ align-self: stretch;
212
+ display: inline-flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ width: 34px;
216
+ min-width: 34px;
217
+ padding: 0;
218
+ color: #999;
219
+ cursor: pointer;
220
+
221
+ svg {
222
+ display: block;
223
+ width: 18px;
224
+ height: 18px;
225
+ fill: currentColor;
226
+ }
227
+ }
228
+
229
+ @mixin nxe-dropdown-editor-buttons {
230
+ // Clear (×).
231
+ .nxe-select-clear {
232
+ @include nxe-clear-button;
233
+ }
234
+
235
+ // Drop-down arrow: an 8x4 #333 triangle centred in its 18px box.
236
+ .nxe-select-arrow {
237
+ @include nxe-drop-button;
238
+ }
239
+
240
+ @include nxe-drop-button-states('.nxe-select-arrow', '&.nxe-open .nxe-select-arrow');
241
+
242
+ // dx: `.dx-dropdowneditor-button-visible .dx-texteditor-input { padding-inline-end: 0 }` — the
243
+ // arrow supplies the trailing gap. Descendant, not child, so the tag box's input (inside its
244
+ // tag container) is covered too.
245
+ .nxe-editor-container:has(> .nxe-select-arrow) .nxe-editor-input {
246
+ padding-inline-end: 0;
247
+ }
248
+
249
+ // Invalid badge (styles/_editor.scss) clears the trailing buttons. dx draws it 4px inside
250
+ // `.dx-texteditor-input-container` (`.dx-texteditor.dx-invalid .dx-texteditor-input-container
251
+ // ::after { inset-inline-end:4px }`), and that container ends where the 34px buttons begin:
252
+ // 34 + 4 with one button, 34 + 34 + 4 with both. (dx's own 38px/68px rules target a
253
+ // `.dx-texteditor-container::after` that has no content, so they never paint.)
254
+ .nxe-editor-container:has(> .nxe-select-clear),
255
+ .nxe-editor-container:has(> .nxe-select-arrow) {
256
+ --nxe-invalid-badge-inset: 38px;
257
+ }
258
+
259
+ .nxe-editor-container:has(> .nxe-select-clear):has(> .nxe-select-arrow) {
260
+ --nxe-invalid-badge-inset: 72px;
261
+ }
113
262
  }
@@ -3,6 +3,7 @@
3
3
  //
4
4
  // Typography is NOT set here — it comes from `.nxe-widget` on the host (styling/_widget.scss), so
5
5
  // it resolves per host app instead of assuming orbit's 14.4px root.
6
+ @use 'dropdown';
6
7
 
7
8
  @mixin nxe-editor-chrome {
8
9
  // Base box at ZERO specificity (`:where`) so per-instance Tailwind utilities applied on the
@@ -79,10 +80,12 @@
79
80
  }
80
81
 
81
82
  // dx `.dx-texteditor.dx-invalid .dx-texteditor-input { padding-inline-end: 34px }` — keeps
82
- // a long value from running under the badge. Only for a container holding nothing but the
83
- // input: where a trailing button exists the value already stops short of it, and dx leaves
84
- // those inputs unpadded too (`.dx-dropdowneditor-button-visible` keeps padding-end 0).
85
- > .nxe-editor-container:not(:has(> :not(.nxe-editor-input))) .nxe-editor-input {
83
+ // a long value from running under the badge. For a container holding the input alone or
84
+ // with the text editors' clear area: the badge sits inside the input's own box in both
85
+ // (a dx text or number box with its clear button shown keeps the 34px — probed). The
86
+ // dropdown editors' buttons are left to their own rules.
87
+ > .nxe-editor-container:not(:has(> :not(.nxe-editor-input, .nxe-clear-button-area)))
88
+ .nxe-editor-input {
86
89
  padding-inline-end: 34px;
87
90
  }
88
91
  }
@@ -96,9 +99,25 @@
96
99
  width: 100%;
97
100
  }
98
101
 
102
+ // `showClearButton` on the text editors (text-box, number-box, date-box): the same 34px area and
103
+ // #999 glyph as the dropdown editors' clear (styles/_dropdown.scss), after the input.
104
+ .nxe-clear-button-area {
105
+ @include dropdown.nxe-clear-button;
106
+ }
107
+
108
+ // The badge moves off the clear area: dx draws it 4px inside `.dx-texteditor-input-container`,
109
+ // which ends where the 34px area begins (38px). The date box adds its own 72px case for the
110
+ // clear area plus its calendar button.
111
+ .nxe-editor-container:has(> .nxe-clear-button-area) {
112
+ --nxe-invalid-badge-inset: 38px;
113
+ }
114
+
99
115
  .nxe-editor-input {
100
116
  -webkit-appearance: none;
101
117
  appearance: none;
118
+ // dx: `.dx-widget * { box-sizing: border-box }`. Without it, a host with no preflight grew the
119
+ // 34px min-height by the 15px of padding and every editor rendered 51px tall, not 36px.
120
+ box-sizing: border-box;
102
121
  flex: 1 1 auto;
103
122
  margin: 0;
104
123
  width: 100%;
@@ -0,0 +1,92 @@
1
+ // @nexterajs/ui — the icon layer: `<i class="nxe-icon nxe-icon-NAME">`, the element nxe-button's
2
+ // `icon` input renders in place of dx's `<i class="dx-icon dx-icon-NAME">`.
3
+ //
4
+ // dx draws these from its DXIcons font. The library does not ship or reference that font
5
+ // (DevExtreme is not Apache-licensed), so each glyph below is redrawn: an 18-unit SVG (1 unit =
6
+ // 1px, unless a glyph shares another box's geometry and says so) of rectangles and straight-edged
7
+ // polygons laid out from the metrics measured off dx's rendering (ink box, wall and stroke widths,
8
+ // position), and used as a MASK painted in `currentColor`, so it
9
+ // takes the button's text colour as dx's glyphs do. Where two shapes meet they overlap, so no
10
+ // anti-aliased seam shows at a fractional position.
11
+ //
12
+ // An unknown name draws nothing in its 18x18 box, as dx does (`caret-down`, which DXIcons lacks,
13
+ // measures 18x18 with no ink). That is what the transparent default mask is for: `mask:
14
+ // var(--nxe-icon)` with the property unset is invalid at computed-value time, the mask drops to
15
+ // `none`, and the box paints as a solid square of `currentColor`.
16
+ //
17
+ // Scoped to <i>. The grid's filter-row `<span class="nxe-icon …">` text glyphs and the pivot field
18
+ // chooser's `<span class="nxe-area-icon nxe-icon-filter">` draw themselves and must not match.
19
+
20
+ // $box is the viewBox side: 18 draws in px, a smaller one scales that glyph up to the 18px box.
21
+ @function nxe-icon-mask($body, $box: 18) {
22
+ @return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 #{$box} #{$box}'%3E#{$body}%3C/svg%3E");
23
+ }
24
+
25
+ @mixin nxe-icons {
26
+ // dx: `.dx-icon { display:inline-block }`, `.dx-button .dx-icon { width:18px; height:18px;
27
+ // vertical-align:middle; margin-inline:0 }`.
28
+ i.nxe-icon {
29
+ --nxe-icon: linear-gradient(transparent, transparent);
30
+ display: inline-block;
31
+ flex-shrink: 0;
32
+ box-sizing: border-box;
33
+ width: 18px;
34
+ height: 18px;
35
+ vertical-align: middle;
36
+ background-color: currentColor;
37
+ -webkit-mask: var(--nxe-icon) center / 18px 18px no-repeat;
38
+ mask: var(--nxe-icon) center / 18px 18px no-repeat;
39
+ }
40
+
41
+ // `filter` (dx U+F050): the grid header's funnel (nxe-data-grid.component.scss
42
+ // `.nxe-grid-hf-icon`), the same geometry every funnel in the library uses, drawn in its 14-unit
43
+ // box and scaled to fill the 18px one: a band 3/14 deep across the full width whose lower
44
+ // corners run in 45° shoulders down to a 4/14 waist, and a 4/14 stem rect standing inside the
45
+ // shoulders from the band down to the bottom edge.
46
+ i.nxe-icon.nxe-icon-filter {
47
+ --nxe-icon: #{nxe-icon-mask(
48
+ "%3Cpolygon points='0 0 14 0 14 3 9 8 5 8 0 3'/%3E"
49
+ + "%3Crect x='5' y='3' width='4' height='11'/%3E",
50
+ 14
51
+ )};
52
+ }
53
+
54
+ // `columnchooser` (dx U+F04D): a 16x15 ink box, 1px in and 2px down. A 12x12 frame of 2px walls
55
+ // (x 1-13, y 2-14) whose lower right corner is covered by an 8x11 panel (x 9-17, y 6-17) with
56
+ // three 4x1 slots through it (x 11-15, every third row from y 8). All rects: the frame's four
57
+ // walls, stopped where the panel starts; the panel's two 2px stiles and its four 2px rails.
58
+ i.nxe-icon.nxe-icon-columnchooser {
59
+ --nxe-icon: #{nxe-icon-mask(
60
+ "%3Crect x='1' y='2' width='12' height='2'/%3E"
61
+ + "%3Crect x='1' y='2' width='2' height='12'/%3E"
62
+ + "%3Crect x='1' y='12' width='9' height='2'/%3E"
63
+ + "%3Crect x='11' y='2' width='2' height='5'/%3E"
64
+ + "%3Crect x='9' y='6' width='2' height='11'/%3E"
65
+ + "%3Crect x='15' y='6' width='2' height='11'/%3E"
66
+ + "%3Crect x='9' y='6' width='8' height='2'/%3E"
67
+ + "%3Crect x='9' y='9' width='8' height='2'/%3E"
68
+ + "%3Crect x='9' y='12' width='8' height='2'/%3E"
69
+ + "%3Crect x='9' y='15' width='8' height='2'/%3E"
70
+ )};
71
+ }
72
+
73
+ // Forced colours (Windows high contrast) replace every background-color with Canvas — the colour
74
+ // behind the icon — so the masked glyph would vanish. `preserve-parent-color` opts the icon out
75
+ // of that forcing while its `color` still takes the parent's forced text colour (CanvasText on
76
+ // nxe-button's host, ButtonText in a native button), so the glyph matches the label beside it.
77
+ // Where that value is unknown, `none` with CanvasText: plain `none` alone would leave
78
+ // currentColor at the author's unforced colour (#333 on a black canvas).
79
+ @media (forced-colors: active) {
80
+ i.nxe-icon {
81
+ forced-color-adjust: none;
82
+ background-color: CanvasText;
83
+ }
84
+
85
+ @supports (forced-color-adjust: preserve-parent-color) {
86
+ i.nxe-icon {
87
+ forced-color-adjust: preserve-parent-color;
88
+ background-color: currentColor;
89
+ }
90
+ }
91
+ }
92
+ }
@@ -1,2 +1,105 @@
1
1
  // @nexterajs/ui — shared SCSS mixins.
2
- // Grows as components are added (e.g. form-control framing, grid theming). Empty for now.
2
+ @use 'sass:math';
3
+
4
+ // Keeps a mark drawn as a background visible under forced colours (Windows high contrast). There
5
+ // the UA replaces every background-color with Canvas, the colour behind the mark, so the tick and
6
+ // the square would vanish and a checked box would read as unchecked (dx's font tick is text and
7
+ // survives). The pseudo-element opts out of the forcing and paints in CanvasText — the colour the
8
+ // box's border is forced to — so the mark keeps the user's contrast.
9
+ @mixin nxe-forced-colors-mark {
10
+ @media (forced-colors: active) {
11
+ forced-color-adjust: none;
12
+ background-color: CanvasText;
13
+ }
14
+ }
15
+
16
+ // The checked glyph of a dx 22px check box (`.dx-checkbox-checked .dx-checkbox-icon::before`, the
17
+ // DXIcons check at 16px), for a pseudo-element of a 22px border-box box with a 1px border.
18
+ //
19
+ // Redrawn, not taken from the font: a 2px stroke bent through a right angle — the short arm falling
20
+ // 5.4px, the long arm rising 9.6px — written as the outline of that stroke, which gives a 15x11 ink
21
+ // box. It is placed 3.5px in and 6px down from the box's outer edge, where dx's measured tick sits
22
+ // (15x11 at +3.5/+6.2, its centre 0.5px below the box's). Every length is stated, so the glyph does
23
+ // not move with the host's box-sizing. Not mirrored under RTL, like dx's: 2.5px from either inline
24
+ // edge is the same place in the 20px padding box.
25
+ @mixin nxe-checkmark($color: var(--nxe-ui-accent, #337ab7)) {
26
+ content: '';
27
+ position: absolute;
28
+ box-sizing: border-box;
29
+ top: 5px;
30
+ inset-inline-start: 2.5px;
31
+ width: 15px;
32
+ height: 11px;
33
+ background-color: $color;
34
+ clip-path: polygon(
35
+ 0 5.586px,
36
+ 5.414px 11px,
37
+ 15px 1.414px,
38
+ 13.586px 0,
39
+ 5.414px 8.172px,
40
+ 1.414px 4.172px
41
+ );
42
+
43
+ @include nxe-forced-colors-mark;
44
+ }
45
+
46
+ // The indeterminate mark of the same box — dx: `.dx-checkbox-indeterminate .dx-checkbox-icon::before
47
+ // { width:.545455em; height:.545455em (12px at 22px); top:50%; margin-top:-.2727275em;
48
+ // inset-inline-start:50%; margin-inline-start:-.2727275em; background-color:#337ab7;
49
+ // border-radius:2px }` — a 12px square centred in the box.
50
+ @mixin nxe-check-indeterminate($color: var(--nxe-ui-accent, #337ab7)) {
51
+ content: '';
52
+ position: absolute;
53
+ box-sizing: border-box;
54
+ top: 50%;
55
+ inset-inline-start: 50%;
56
+ width: 12px;
57
+ height: 12px;
58
+ margin-top: -6px;
59
+ margin-inline-start: -6px;
60
+ border-radius: 2px;
61
+ background-color: $color;
62
+
63
+ @include nxe-forced-colors-mark;
64
+ }
65
+
66
+ // A thin, unobtrusive scrollbar on a natively-scrolling element.
67
+ //
68
+ // Read this before adding `::-webkit-scrollbar` rules anywhere else. From Chrome 121, setting
69
+ // either standard property (`scrollbar-width` / `scrollbar-color`) makes Chromium ignore the
70
+ // webkit pseudo-elements **on that same element**. The two do not compose: an element that sets
71
+ // both gets the standard rendering and the webkit block becomes dead code that still reads as
72
+ // live, which is the worst of both — it looks maintained and changes nothing.
73
+ //
74
+ // So the pseudo-elements live behind `@supports not (scrollbar-width: thin)`, which is true only
75
+ // on the engines that never implemented the standard properties (Chrome < 121, Safari < 18.2).
76
+ // Exactly one of the two branches applies anywhere.
77
+ @mixin nxe-thin-scrollbar(
78
+ $thumb: rgba(0, 0, 0, 0.5),
79
+ $track: transparent,
80
+ $size: 8px,
81
+ $thumb-hover: $thumb
82
+ ) {
83
+ scrollbar-width: thin;
84
+ scrollbar-color: $thumb $track;
85
+
86
+ @supports not (scrollbar-width: thin) {
87
+ &::-webkit-scrollbar {
88
+ width: $size;
89
+ height: $size;
90
+ }
91
+
92
+ &::-webkit-scrollbar-track {
93
+ background: $track;
94
+ }
95
+
96
+ &::-webkit-scrollbar-thumb {
97
+ background-color: $thumb;
98
+ border-radius: math.div($size, 2);
99
+ }
100
+
101
+ &:hover::-webkit-scrollbar-thumb {
102
+ background-color: $thumb-hover;
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,134 @@
1
+ // @nexterajs/ui — [nxeScrollbar] chrome.
2
+ //
3
+ // A value-for-value port of `perfect-scrollbar.css`, which this replaces. Applications style these
4
+ // rails heavily, so every number and easing is copied rather than chosen — the selector names are
5
+ // the only thing that changed, one-for-one:
6
+ //
7
+ // .ps -> .nxe-scrollbar .ps__rail-x -> .nxe-scrollbar__rail-x
8
+ // .ps--active-x -> .nxe-scrollbar--active-x .ps__thumb-x -> .nxe-scrollbar__thumb-x
9
+ // .ps--focus -> .nxe-scrollbar--focus .ps--clicking -> .nxe-scrollbar--clicking
10
+ // .ps--scrolling-x -> .nxe-scrollbar--scrolling-x (and the -y twins of each)
11
+ //
12
+ // Load this where `perfect-scrollbar/css/perfect-scrollbar.css` used to sit in the cascade —
13
+ // BEFORE the application's own rail overrides — or those overrides stop winning.
14
+
15
+ //
16
+ // Container style
17
+ //
18
+ // The original set `overflow: hidden !important` and drove scrolling entirely from JavaScript:
19
+ // wheel, touch, keyboard and momentum were all reimplemented. This keeps `overflow: auto` and
20
+ // merely hides the native bar, so the browser still does the scrolling — which is more faithful
21
+ // on touch and with a trackpad, and is why the directive needs no input handling of its own.
22
+ // Layout is unchanged either way: a hidden native scrollbar occupies no width, exactly as the
23
+ // simulated rails did.
24
+ .nxe-scrollbar {
25
+ overflow: auto;
26
+ overflow-anchor: none;
27
+ touch-action: auto;
28
+ position: relative;
29
+
30
+ scrollbar-width: none;
31
+ -ms-overflow-style: none;
32
+
33
+ &::-webkit-scrollbar {
34
+ width: 0;
35
+ height: 0;
36
+ }
37
+ }
38
+
39
+ //
40
+ // Scrollbar rail styles
41
+ //
42
+ .nxe-scrollbar__rail-x {
43
+ display: none;
44
+ opacity: 0;
45
+ transition:
46
+ background-color 0.2s linear,
47
+ opacity 0.2s linear;
48
+ height: 15px;
49
+ // there must be 'bottom' or 'top' for the x rail
50
+ bottom: 0px;
51
+ // please don't change 'position'
52
+ position: absolute;
53
+ }
54
+
55
+ .nxe-scrollbar__rail-y {
56
+ display: none;
57
+ opacity: 0;
58
+ transition:
59
+ background-color 0.2s linear,
60
+ opacity 0.2s linear;
61
+ width: 15px;
62
+ // there must be 'right' or 'left' for the y rail
63
+ right: 0;
64
+ // please don't change 'position'
65
+ position: absolute;
66
+ }
67
+
68
+ .nxe-scrollbar--active-x > .nxe-scrollbar__rail-x,
69
+ .nxe-scrollbar--active-y > .nxe-scrollbar__rail-y {
70
+ display: block;
71
+ background-color: transparent;
72
+ }
73
+
74
+ .nxe-scrollbar:hover > .nxe-scrollbar__rail-x,
75
+ .nxe-scrollbar:hover > .nxe-scrollbar__rail-y,
76
+ .nxe-scrollbar--focus > .nxe-scrollbar__rail-x,
77
+ .nxe-scrollbar--focus > .nxe-scrollbar__rail-y,
78
+ .nxe-scrollbar--scrolling-x > .nxe-scrollbar__rail-x,
79
+ .nxe-scrollbar--scrolling-y > .nxe-scrollbar__rail-y {
80
+ opacity: 0.6;
81
+ }
82
+
83
+ .nxe-scrollbar .nxe-scrollbar__rail-x:hover,
84
+ .nxe-scrollbar .nxe-scrollbar__rail-y:hover,
85
+ .nxe-scrollbar .nxe-scrollbar__rail-x:focus,
86
+ .nxe-scrollbar .nxe-scrollbar__rail-y:focus,
87
+ .nxe-scrollbar .nxe-scrollbar__rail-x.nxe-scrollbar--clicking,
88
+ .nxe-scrollbar .nxe-scrollbar__rail-y.nxe-scrollbar--clicking {
89
+ background-color: #eee;
90
+ opacity: 0.9;
91
+ }
92
+
93
+ //
94
+ // Scrollbar thumb styles
95
+ //
96
+ .nxe-scrollbar__thumb-x {
97
+ background-color: #aaa;
98
+ border-radius: 6px;
99
+ transition:
100
+ background-color 0.2s linear,
101
+ height 0.2s ease-in-out;
102
+ height: 6px;
103
+ // there must be 'bottom' for the x thumb
104
+ bottom: 2px;
105
+ // please don't change 'position'
106
+ position: absolute;
107
+ }
108
+
109
+ .nxe-scrollbar__thumb-y {
110
+ background-color: #aaa;
111
+ border-radius: 6px;
112
+ transition:
113
+ background-color 0.2s linear,
114
+ width 0.2s ease-in-out;
115
+ width: 6px;
116
+ // there must be 'right' for the y thumb
117
+ right: 2px;
118
+ // please don't change 'position'
119
+ position: absolute;
120
+ }
121
+
122
+ .nxe-scrollbar__rail-x:hover > .nxe-scrollbar__thumb-x,
123
+ .nxe-scrollbar__rail-x:focus > .nxe-scrollbar__thumb-x,
124
+ .nxe-scrollbar__rail-x.nxe-scrollbar--clicking .nxe-scrollbar__thumb-x {
125
+ background-color: #999;
126
+ height: 11px;
127
+ }
128
+
129
+ .nxe-scrollbar__rail-y:hover > .nxe-scrollbar__thumb-y,
130
+ .nxe-scrollbar__rail-y:focus > .nxe-scrollbar__thumb-y,
131
+ .nxe-scrollbar__rail-y.nxe-scrollbar--clicking .nxe-scrollbar__thumb-y {
132
+ background-color: #999;
133
+ width: 11px;
134
+ }