@scalewing/react 1.16.0 → 1.18.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/CHANGELOG.md +21 -0
- package/README.md +27 -4
- package/dist/components/ActionBar.d.ts +16 -6
- package/dist/components/ActionBar.js +1 -1
- package/dist/components/DenominationGrid.d.ts +2 -2
- package/dist/components/DenominationGrid.js +4 -2
- package/dist/components/Dialog.d.ts +32 -1
- package/dist/components/Dialog.js +4 -3
- package/dist/components/ScrollRegion.d.ts +4 -1
- package/dist/components/ScrollRegion.js +11 -2
- package/dist/components/Tabs.d.ts +22 -2
- package/dist/components/Tabs.js +4 -3
- package/dist/components/denomination-grid/DenominationStrip.js +2 -1
- package/dist/components/denomination-grid/DenominationTiles.d.ts +2 -2
- package/dist/components/denomination-grid/DenominationTiles.js +12 -7
- package/dist/components/denomination-grid/types.d.ts +19 -0
- package/dist/components/denomination-grid/validate.js +17 -0
- package/dist/components/dialog/DialogTitle.d.ts +15 -0
- package/dist/components/dialog/DialogTitle.js +14 -0
- package/dist/components/use-scroll-overflow.d.ts +9 -0
- package/dist/components/use-scroll-overflow.js +44 -0
- package/dist/css/css-action-bar.js +7 -0
- package/dist/css/css-denomination-cell-tones.d.ts +9 -0
- package/dist/css/css-denomination-cell-tones.js +52 -0
- package/dist/css/css-denomination-grid.js +10 -1
- package/dist/css/css-dialog.d.ts +2 -0
- package/dist/css/css-dialog.js +125 -3
- package/dist/css/css-scroll-region.d.ts +16 -2
- package/dist/css/css-scroll-region.js +68 -2
- package/dist/css/css-tabs.d.ts +1 -0
- package/dist/css/css-tabs.js +42 -1
- package/dist/css/stacking.js +3 -2
- package/dist/css/stylesheet.js +2 -0
- package/dist/denomination-cells.d.ts +3 -1
- package/dist/denomination-cells.js +6 -2
- package/dist/index.d.ts +2 -2
- package/dist/scroll-overflow.d.ts +14 -0
- package/dist/scroll-overflow.js +26 -0
- package/dist/styles.css +158 -0
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type BadgeTone } from './css-data.js';
|
|
2
|
+
/**
|
|
3
|
+
* The classes that tone one count: a marker that applies the shared rules and
|
|
4
|
+
* the tone that sets their color. A strip cell (`td`) and a tile (`li`) take
|
|
5
|
+
* the same pair; null or `neutral` leaves the cell untoned.
|
|
6
|
+
*/
|
|
7
|
+
export declare function denominationCellToneClassNames(tone: BadgeTone | null | undefined): string[];
|
|
8
|
+
export declare function cssDenominationCellToneClasses(): string;
|
|
9
|
+
export declare function denominationCellToneClassCatalog(): string[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { badgeTones } from './css-data.js';
|
|
2
|
+
/**
|
|
3
|
+
* The tones a single count can take. `neutral` is the cell's own look, so it
|
|
4
|
+
* adds no class: a cell is toned only by a tone that says something.
|
|
5
|
+
*/
|
|
6
|
+
const cellTones = badgeTones.filter((tone) => tone !== 'neutral');
|
|
7
|
+
/**
|
|
8
|
+
* The classes that tone one count: a marker that applies the shared rules and
|
|
9
|
+
* the tone that sets their color. A strip cell (`td`) and a tile (`li`) take
|
|
10
|
+
* the same pair; null or `neutral` leaves the cell untoned.
|
|
11
|
+
*/
|
|
12
|
+
export function denominationCellToneClassNames(tone) {
|
|
13
|
+
if (!tone || tone === 'neutral')
|
|
14
|
+
return [];
|
|
15
|
+
return ['sw-denomination-cell-toned', `sw-denomination-cell-tone-${tone}`];
|
|
16
|
+
}
|
|
17
|
+
/*
|
|
18
|
+
* A toned count is set in its tone, over a row's signed coloring and a
|
|
19
|
+
* zero's quiet opacity, and a toned tile draws its border in the tone, one
|
|
20
|
+
* hairline thicker with an inset shadow so no tile moves. The tone never
|
|
21
|
+
* carries meaning alone: the consumer's words say why a count is toned.
|
|
22
|
+
* Forced colors replace the tone with the system text color, and a toned
|
|
23
|
+
* zero keeps the zero's GrayText there, so it does not read as a count.
|
|
24
|
+
*/
|
|
25
|
+
export function cssDenominationCellToneClasses() {
|
|
26
|
+
const toneRules = cellTones
|
|
27
|
+
.map((tone) => `.sw-denomination-cell-tone-${tone} { --sw-denomination-cell-tone: var(--sw-color-${tone}); }`)
|
|
28
|
+
.join('\n');
|
|
29
|
+
return `${toneRules}
|
|
30
|
+
|
|
31
|
+
.sw-denomination-grid .sw-denomination-cell.sw-denomination-cell-toned,
|
|
32
|
+
.sw-denomination-grid .sw-denomination-cell-toned .sw-denomination-cell {
|
|
33
|
+
color: var(--sw-denomination-cell-tone);
|
|
34
|
+
opacity: 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sw-denomination-tile.sw-denomination-cell-toned {
|
|
38
|
+
border-color: var(--sw-denomination-cell-tone);
|
|
39
|
+
box-shadow: inset 0 0 0 1px var(--sw-denomination-cell-tone);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (forced-colors: active) {
|
|
43
|
+
.sw-denomination-grid .sw-denomination-cell-zero.sw-denomination-cell-toned,
|
|
44
|
+
.sw-denomination-grid .sw-denomination-cell-toned .sw-denomination-cell-zero { color: GrayText; }
|
|
45
|
+
}`;
|
|
46
|
+
}
|
|
47
|
+
export function denominationCellToneClassCatalog() {
|
|
48
|
+
return [
|
|
49
|
+
'sw-denomination-cell-toned',
|
|
50
|
+
...cellTones.map((tone) => `sw-denomination-cell-tone-${tone}`),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { breakpointQuery } from './breakpoints.js';
|
|
3
3
|
import { badgeTones } from './css-data.js';
|
|
4
|
-
import {
|
|
4
|
+
import { cssDenominationCellToneClasses, denominationCellToneClassCatalog, } from './css-denomination-cell-tones.js';
|
|
5
|
+
import { pinnedStartShadeRules, scrollRegionRules, } from './css-scroll-region.js';
|
|
5
6
|
import { visuallyHiddenDeclarations } from './css-utilities.js';
|
|
6
7
|
import { zIndex } from './stacking.js';
|
|
7
8
|
const caption = typographyVariants.caption;
|
|
@@ -25,6 +26,11 @@ ${toneRules()}
|
|
|
25
26
|
|
|
26
27
|
${scrollRegionRules('.sw-denomination-scroll')}
|
|
27
28
|
|
|
29
|
+
${pinnedStartShadeRules('.sw-denomination-scroll', [
|
|
30
|
+
'.sw-denomination-strip .sw-denomination-label',
|
|
31
|
+
'.sw-denomination-strip thead .sw-denomination-corner:first-child',
|
|
32
|
+
])}
|
|
33
|
+
|
|
28
34
|
.sw-denomination-strip {
|
|
29
35
|
border-collapse: collapse;
|
|
30
36
|
width: 100%;
|
|
@@ -239,6 +245,8 @@ ${scrollRegionRules('.sw-denomination-scroll')}
|
|
|
239
245
|
.sw-denomination-tile-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
240
246
|
}
|
|
241
247
|
|
|
248
|
+
${cssDenominationCellToneClasses()}
|
|
249
|
+
|
|
242
250
|
@media (forced-colors: active) {
|
|
243
251
|
.sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
|
|
244
252
|
.sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
|
|
@@ -276,5 +284,6 @@ export function denominationGridClassCatalog() {
|
|
|
276
284
|
'sw-denomination-tile-list',
|
|
277
285
|
'sw-denomination-tile',
|
|
278
286
|
'sw-denomination-subtotal',
|
|
287
|
+
...denominationCellToneClassCatalog(),
|
|
279
288
|
];
|
|
280
289
|
}
|
package/dist/css/css-dialog.d.ts
CHANGED
package/dist/css/css-dialog.js
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { breakpointQuery, breakpoints, } from './breakpoints.js';
|
|
3
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
4
|
+
const title = typographyVariants.title;
|
|
5
|
+
/* The modal's height cap: a space-8 strip of backdrop stays in view. */
|
|
6
|
+
const maxHeight = 'min(100vh - var(--sw-space-8), 100dvh - var(--sw-space-8))';
|
|
7
|
+
export function dialogSheetClass(sheetBelow) {
|
|
8
|
+
return `sw-dialog-sheet-below-${sheetBelow}`;
|
|
9
|
+
}
|
|
10
|
+
/*
|
|
11
|
+
* Below the breakpoint the dialog is a bottom sheet: docked to the bottom
|
|
12
|
+
* edge at the full viewport width, the top corners rounded and the bottom
|
|
13
|
+
* ones square, the same height cap (so the whole space-8 strip of backdrop
|
|
14
|
+
* shows above it), and padding that clears the safe areas. Emitted after
|
|
15
|
+
* `sw-dialog-lg` so it wins at equal specificity. No grabber: there is no
|
|
16
|
+
* drag gesture.
|
|
17
|
+
*/
|
|
18
|
+
function sheetRules() {
|
|
19
|
+
return breakpoints
|
|
20
|
+
.map((breakpoint) => `@media ${breakpointQuery('below', breakpoint)} {
|
|
21
|
+
.${dialogSheetClass(breakpoint)} {
|
|
22
|
+
border-radius: var(--sw-radius-lg) var(--sw-radius-lg) 0 0;
|
|
23
|
+
border-width: 1px 0 0;
|
|
24
|
+
margin: auto 0 0;
|
|
25
|
+
max-height: ${maxHeight};
|
|
26
|
+
max-width: none;
|
|
27
|
+
padding-bottom: calc(var(--sw-space-5) + env(safe-area-inset-bottom, 0px));
|
|
28
|
+
padding-left: max(var(--sw-space-5), env(safe-area-inset-left, 0px));
|
|
29
|
+
padding-right: max(var(--sw-space-5), env(safe-area-inset-right, 0px));
|
|
30
|
+
width: 100%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.${dialogSheetClass(breakpoint)}[open] {
|
|
34
|
+
animation: sw-dialog-sheet-in var(--sw-motion-default) var(--sw-motion-easing);
|
|
35
|
+
}
|
|
36
|
+
}`)
|
|
37
|
+
.join('\n\n');
|
|
38
|
+
}
|
|
39
|
+
function sheetMotionRules() {
|
|
40
|
+
const still = breakpoints
|
|
41
|
+
.map((breakpoint) => ` .${dialogSheetClass(breakpoint)}[open] { animation: none; }`)
|
|
42
|
+
.join('\n');
|
|
43
|
+
return `@keyframes sw-dialog-sheet-in {
|
|
44
|
+
from { translate: 0 100%; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (prefers-reduced-motion: reduce) {
|
|
48
|
+
${still}
|
|
49
|
+
}`;
|
|
50
|
+
}
|
|
51
|
+
/*
|
|
52
|
+
* The title row when the dialog has a close button: the title takes the
|
|
53
|
+
* row and the button ends it. The button's negative margins keep the row
|
|
54
|
+
* as tall as one title line and put the glyph's end on the padding edge,
|
|
55
|
+
* at the sm control size and at the 44 px touch target alike.
|
|
56
|
+
*/
|
|
57
|
+
function closeButtonRules() {
|
|
58
|
+
const square = (size) => `min-width: ${size};
|
|
59
|
+
margin-block: calc((${title.lineHeight}px - ${size}) / 2);
|
|
60
|
+
margin-inline-end: calc((var(--sw-space-4) - ${size}) / 2);`;
|
|
61
|
+
return `.sw-dialog-header {
|
|
62
|
+
align-items: flex-start;
|
|
63
|
+
display: flex;
|
|
64
|
+
gap: var(--sw-space-2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.sw-dialog-header > :first-child {
|
|
68
|
+
flex: 1 1 auto;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.sw-button.sw-dialog-close {
|
|
73
|
+
flex: none;
|
|
74
|
+
padding-inline: 0;
|
|
75
|
+
${square('var(--sw-control-sm-min-height)')}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@media ${coarsePointerQuery} {
|
|
79
|
+
.sw-button.sw-dialog-close {
|
|
80
|
+
min-height: ${touchTarget};
|
|
81
|
+
${square(touchTarget)}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Private control chrome: two strokes crossed in a space-4 square, in the
|
|
86
|
+
button's color; borders, so forced colors keep them. */
|
|
87
|
+
.sw-dialog-close-glyph {
|
|
88
|
+
flex: none;
|
|
89
|
+
height: var(--sw-space-4);
|
|
90
|
+
position: relative;
|
|
91
|
+
width: var(--sw-space-4);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.sw-dialog-close-glyph::before,
|
|
95
|
+
.sw-dialog-close-glyph::after {
|
|
96
|
+
border-top: 2px solid currentColor;
|
|
97
|
+
border-radius: 1px;
|
|
98
|
+
box-sizing: border-box;
|
|
99
|
+
content: '';
|
|
100
|
+
left: 0;
|
|
101
|
+
margin-top: -1px;
|
|
102
|
+
position: absolute;
|
|
103
|
+
top: 50%;
|
|
104
|
+
width: 100%;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.sw-dialog-close-glyph::before { transform: rotate(45deg); }
|
|
108
|
+
.sw-dialog-close-glyph::after { transform: rotate(-45deg); }`;
|
|
109
|
+
}
|
|
1
110
|
export function cssDialogClasses() {
|
|
2
111
|
return `.sw-dialog {
|
|
3
112
|
background: var(--sw-glass-fill);
|
|
@@ -9,7 +118,7 @@ export function cssDialogClasses() {
|
|
|
9
118
|
box-sizing: border-box;
|
|
10
119
|
color: var(--sw-color-text);
|
|
11
120
|
margin: auto;
|
|
12
|
-
max-height:
|
|
121
|
+
max-height: ${maxHeight};
|
|
13
122
|
max-width: min(var(--sw-dialog-max), calc(100vw - var(--sw-space-8)));
|
|
14
123
|
overflow: auto;
|
|
15
124
|
width: calc(100% - var(--sw-space-8));
|
|
@@ -19,6 +128,10 @@ export function cssDialogClasses() {
|
|
|
19
128
|
max-width: min(var(--sw-dialog-max-lg), calc(100vw - var(--sw-space-8)));
|
|
20
129
|
}
|
|
21
130
|
|
|
131
|
+
${sheetRules()}
|
|
132
|
+
|
|
133
|
+
${sheetMotionRules()}
|
|
134
|
+
|
|
22
135
|
.sw-dialog::backdrop {
|
|
23
136
|
background: color-mix(in srgb, var(--sw-color-text) 28%, transparent);
|
|
24
137
|
}
|
|
@@ -26,8 +139,17 @@ export function cssDialogClasses() {
|
|
|
26
139
|
.sw-dialog:focus-visible {
|
|
27
140
|
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
28
141
|
outline-offset: var(--sw-focus-ring-offset);
|
|
29
|
-
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
${closeButtonRules()}`;
|
|
30
145
|
}
|
|
31
146
|
export function dialogClassCatalog() {
|
|
32
|
-
return [
|
|
147
|
+
return [
|
|
148
|
+
'sw-dialog',
|
|
149
|
+
'sw-dialog-lg',
|
|
150
|
+
...breakpoints.map((breakpoint) => dialogSheetClass(breakpoint)),
|
|
151
|
+
'sw-dialog-header',
|
|
152
|
+
'sw-dialog-close',
|
|
153
|
+
'sw-dialog-close-glyph',
|
|
154
|
+
];
|
|
33
155
|
}
|
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
* A scroll region is a full-width box that scrolls its own content when the
|
|
3
3
|
* content is wider (or taller) than the box, so a wide table never widens the
|
|
4
4
|
* page. It is a keyboard stop, so its focus ring is drawn inside its edge
|
|
5
|
-
* where an ancestor's clipping cannot hide it.
|
|
6
|
-
*
|
|
5
|
+
* where an ancestor's clipping cannot hide it. While its content is scrolled
|
|
6
|
+
* out past an inline edge, `ScrollRegion` adds `sw-scroll-more-start` or
|
|
7
|
+
* `sw-scroll-more-end`, and that edge draws a shade: the cue that the content
|
|
8
|
+
* goes on, which overlay scrollbars (phones, macOS) do not give. In forced
|
|
9
|
+
* colors the shade is not drawn and the system's scrollbar is the cue. Table
|
|
10
|
+
* and the DenominationGrid strip share it.
|
|
7
11
|
*/
|
|
8
12
|
export declare function scrollRegionRules(selector: string): string;
|
|
13
|
+
export declare function scrollRegionClassCatalog(): string[];
|
|
14
|
+
/**
|
|
15
|
+
* A region whose start column is pinned (sticky, on an opaque fill) hides
|
|
16
|
+
* its own start shade under that column, so the column casts the shade
|
|
17
|
+
* instead: a gradient a spacing step wide just past its inline end, drawn by
|
|
18
|
+
* its `::after` (the pinned cell is positioned, so it holds the gradient).
|
|
19
|
+
* `pinned` lists the pinned cells' selectors inside the region. Forced colors
|
|
20
|
+
* draw no shade here either.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pinnedStartShadeRules(region: string, pinned: readonly string[]): string;
|
|
@@ -1,9 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The shade an edge draws while content is scrolled out past it: an inset
|
|
3
|
+
* shadow a spacing step wide in the text color at low strength, so it reads
|
|
4
|
+
* on every palette and in dark mode. It is the box's own shadow, painted
|
|
5
|
+
* under the content, so no cell moves. A box scrolled to its middle shades
|
|
6
|
+
* both edges. The rules name the shadows outright rather than through
|
|
7
|
+
* inherited variables, so a scroll region inside another never takes its
|
|
8
|
+
* ancestor's shade.
|
|
9
|
+
*/
|
|
10
|
+
const shadeColor = 'color-mix(in srgb, var(--sw-color-text) 28%, transparent)';
|
|
11
|
+
function edgeShade(side) {
|
|
12
|
+
const offset = side === 'left' ? 'var(--sw-space-6)' : 'calc(-1 * var(--sw-space-6))';
|
|
13
|
+
return `inset ${offset} 0 var(--sw-space-5) calc(-1 * var(--sw-space-5)) ${shadeColor}`;
|
|
14
|
+
}
|
|
15
|
+
/** The start edge is the left in a left-to-right box and the right in RTL. */
|
|
16
|
+
function edgeShadeRules(selector) {
|
|
17
|
+
const more = (edge) => `${selector}.sw-scroll-more-${edge}`;
|
|
18
|
+
const both = `${more('start')}.sw-scroll-more-end`;
|
|
19
|
+
return [
|
|
20
|
+
`${more('start')} { box-shadow: ${edgeShade('left')}; }`,
|
|
21
|
+
`${more('end')} { box-shadow: ${edgeShade('right')}; }`,
|
|
22
|
+
`${more('start')}:dir(rtl) { box-shadow: ${edgeShade('right')}; }`,
|
|
23
|
+
`${more('end')}:dir(rtl) { box-shadow: ${edgeShade('left')}; }`,
|
|
24
|
+
// Last: it ties the :dir(rtl) rules on specificity and must win them.
|
|
25
|
+
`${both} { box-shadow: ${edgeShade('left')}, ${edgeShade('right')}; }`,
|
|
26
|
+
].join('\n');
|
|
27
|
+
}
|
|
1
28
|
/**
|
|
2
29
|
* A scroll region is a full-width box that scrolls its own content when the
|
|
3
30
|
* content is wider (or taller) than the box, so a wide table never widens the
|
|
4
31
|
* page. It is a keyboard stop, so its focus ring is drawn inside its edge
|
|
5
|
-
* where an ancestor's clipping cannot hide it.
|
|
6
|
-
*
|
|
32
|
+
* where an ancestor's clipping cannot hide it. While its content is scrolled
|
|
33
|
+
* out past an inline edge, `ScrollRegion` adds `sw-scroll-more-start` or
|
|
34
|
+
* `sw-scroll-more-end`, and that edge draws a shade: the cue that the content
|
|
35
|
+
* goes on, which overlay scrollbars (phones, macOS) do not give. In forced
|
|
36
|
+
* colors the shade is not drawn and the system's scrollbar is the cue. Table
|
|
37
|
+
* and the DenominationGrid strip share it.
|
|
7
38
|
*/
|
|
8
39
|
export function scrollRegionRules(selector) {
|
|
9
40
|
return `${selector} {
|
|
@@ -14,5 +45,40 @@ export function scrollRegionRules(selector) {
|
|
|
14
45
|
${selector}:focus-visible {
|
|
15
46
|
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
16
47
|
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
${edgeShadeRules(selector)}`;
|
|
51
|
+
}
|
|
52
|
+
export function scrollRegionClassCatalog() {
|
|
53
|
+
return ['sw-scroll-more-start', 'sw-scroll-more-end'];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A region whose start column is pinned (sticky, on an opaque fill) hides
|
|
57
|
+
* its own start shade under that column, so the column casts the shade
|
|
58
|
+
* instead: a gradient a spacing step wide just past its inline end, drawn by
|
|
59
|
+
* its `::after` (the pinned cell is positioned, so it holds the gradient).
|
|
60
|
+
* `pinned` lists the pinned cells' selectors inside the region. Forced colors
|
|
61
|
+
* draw no shade here either.
|
|
62
|
+
*/
|
|
63
|
+
export function pinnedStartShadeRules(region, pinned) {
|
|
64
|
+
const after = (direction) => pinned
|
|
65
|
+
.map((cell) => `${region}.sw-scroll-more-start${direction} ${cell}::after`)
|
|
66
|
+
.join(',\n');
|
|
67
|
+
return `${after('')} {
|
|
68
|
+
background: linear-gradient(to right, ${shadeColor}, transparent);
|
|
69
|
+
content: '';
|
|
70
|
+
inset-block: 0;
|
|
71
|
+
inset-inline-start: 100%;
|
|
72
|
+
pointer-events: none;
|
|
73
|
+
position: absolute;
|
|
74
|
+
width: var(--sw-space-5);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
${after(':dir(rtl)')} {
|
|
78
|
+
background: linear-gradient(to left, ${shadeColor}, transparent);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (forced-colors: active) {
|
|
82
|
+
${after('').replaceAll('\n', '\n ')} { display: none; }
|
|
17
83
|
}`;
|
|
18
84
|
}
|
package/dist/css/css-tabs.d.ts
CHANGED
package/dist/css/css-tabs.js
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { zIndex } from './stacking.js';
|
|
2
3
|
const label = typographyVariants.label;
|
|
4
|
+
/*
|
|
5
|
+
* The stuck strip is a full-bleed band of the page canvas, not a glass card:
|
|
6
|
+
* nine parts canvas color to one part see-through, with the glass blur
|
|
7
|
+
* behind it, so a panel scrolling under it shows as a faint smear, never as
|
|
8
|
+
* words beside the labels. At nine parts the muted labels keep at least
|
|
9
|
+
* 4.1:1, and the accent label 3.6:1, in every palette even over a solid
|
|
10
|
+
* block of the text, accent or danger color; over the canvas and ordinary
|
|
11
|
+
* type, which the blur spreads, they keep their canvas contrast. The first
|
|
12
|
+
* tab's own inline padding (the md control's, spacing step 4) lines its
|
|
13
|
+
* label up with a space-4 page gutter, so the strip adds no padding of its
|
|
14
|
+
* own. It rides the AppHeader's layer.
|
|
15
|
+
*/
|
|
16
|
+
export const stuckCanvasShare = 0.9;
|
|
17
|
+
function stickyRules() {
|
|
18
|
+
return `.sw-tabs-sticky {
|
|
19
|
+
background: color-mix(in srgb, var(--sw-color-background) ${stuckCanvasShare * 100}%, transparent);
|
|
20
|
+
backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
21
|
+
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
22
|
+
position: sticky;
|
|
23
|
+
top: env(safe-area-inset-top, 0px);
|
|
24
|
+
${zIndex('topChrome')}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
28
|
+
.sw-tabs-sticky {
|
|
29
|
+
background: var(--sw-color-background);
|
|
30
|
+
backdrop-filter: none;
|
|
31
|
+
-webkit-backdrop-filter: none;
|
|
32
|
+
}
|
|
33
|
+
}`;
|
|
34
|
+
}
|
|
3
35
|
/** The tab strip: a scrollable row of tabs with an accent underline under the current one. */
|
|
4
36
|
export function cssTabsClasses() {
|
|
5
37
|
return `.sw-tabs {
|
|
@@ -55,10 +87,19 @@ export function cssTabsClasses() {
|
|
|
55
87
|
outline-offset: var(--sw-focus-ring-offset);
|
|
56
88
|
}
|
|
57
89
|
|
|
90
|
+
${stickyRules()}
|
|
91
|
+
|
|
58
92
|
@media (forced-colors: active) {
|
|
59
93
|
.sw-tab-selected { border-bottom-color: Highlight; color: Highlight; }
|
|
94
|
+
.sw-tabs-sticky { background: Canvas; backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
60
95
|
}`;
|
|
61
96
|
}
|
|
62
97
|
export function tabsClassCatalog() {
|
|
63
|
-
return [
|
|
98
|
+
return [
|
|
99
|
+
'sw-tabs',
|
|
100
|
+
'sw-tabs-sticky',
|
|
101
|
+
'sw-tab',
|
|
102
|
+
'sw-tab-selected',
|
|
103
|
+
'sw-tab-panel',
|
|
104
|
+
];
|
|
64
105
|
}
|
package/dist/css/stacking.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* its popup can only paint as high as the surface does; lifting the surface
|
|
11
11
|
* over the ActionBar keeps an open list tappable where the bar would cover
|
|
12
12
|
* it, and over the next surface in the flow.
|
|
13
|
-
* 4. `topChrome`: the sticky AppHeader, over a lifted
|
|
14
|
-
* under it (the header is the page's way back,
|
|
13
|
+
* 4. `topChrome`: the sticky AppHeader or sticky Tabs strip, over a lifted
|
|
14
|
+
* surface that scrolls up under it (the header is the page's way back, the
|
|
15
|
+
* strip its way between sections, and a popup opens downward).
|
|
15
16
|
* 5. `popup`: a popup outside any lifted surface (Select list, ActionMenu
|
|
16
17
|
* list, Tooltip, the DateField and CalendarButton calendar) over
|
|
17
18
|
* everything in the page.
|
package/dist/css/stylesheet.js
CHANGED
|
@@ -18,6 +18,7 @@ import { filterChipsClassCatalog } from './css-filter-chips.js';
|
|
|
18
18
|
import { gridClassCatalog } from './css-grid.js';
|
|
19
19
|
import { gridSpanClassCatalog } from './css-grid-span.js';
|
|
20
20
|
import { denominationGridClassCatalog } from './css-denomination-grid.js';
|
|
21
|
+
import { scrollRegionClassCatalog } from './css-scroll-region.js';
|
|
21
22
|
import { fieldClassCatalog } from './css-field.js';
|
|
22
23
|
import { boxBorderClassCatalog } from './css-box-border.js';
|
|
23
24
|
import { cssComponentClasses } from './css-components.js';
|
|
@@ -53,6 +54,7 @@ export function utilityClassCatalog() {
|
|
|
53
54
|
...dataClassCatalog(),
|
|
54
55
|
...segmentedClassCatalog(),
|
|
55
56
|
...tableClassCatalog(),
|
|
57
|
+
...scrollRegionClassCatalog(),
|
|
56
58
|
...chartClassCatalog(),
|
|
57
59
|
...chromeClassCatalog(),
|
|
58
60
|
...dialogClassCatalog(),
|
|
@@ -6,7 +6,9 @@ export type DenominationCellView = {
|
|
|
6
6
|
/**
|
|
7
7
|
* Classifies one count for display. A null or zero count renders the zero
|
|
8
8
|
* label; a signed row prefixes positive counts with "+" so movement reads as
|
|
9
|
-
* a delta
|
|
9
|
+
* a delta, and every negative count takes the typographic minus "−", the
|
|
10
|
+
* sign a consumer's formatted money uses, never a hyphen. Formatting stays
|
|
11
|
+
* plain digits: the consumer owns locale grouping.
|
|
10
12
|
*/
|
|
11
13
|
export declare function denominationCellView(count: number | null, signed: boolean, zeroLabel: string): DenominationCellView;
|
|
12
14
|
export declare function isDenominationCount(count: number | null): boolean;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
/** The typographic minus (U+2212), not the hyphen-minus a number prints. */
|
|
2
|
+
const minusSign = '\u2212';
|
|
1
3
|
/**
|
|
2
4
|
* Classifies one count for display. A null or zero count renders the zero
|
|
3
5
|
* label; a signed row prefixes positive counts with "+" so movement reads as
|
|
4
|
-
* a delta
|
|
6
|
+
* a delta, and every negative count takes the typographic minus "−", the
|
|
7
|
+
* sign a consumer's formatted money uses, never a hyphen. Formatting stays
|
|
8
|
+
* plain digits: the consumer owns locale grouping.
|
|
5
9
|
*/
|
|
6
10
|
export function denominationCellView(count, signed, zeroLabel) {
|
|
7
11
|
if (count === null || count === 0)
|
|
8
12
|
return { state: 'zero', text: zeroLabel };
|
|
9
13
|
if (count < 0)
|
|
10
|
-
return { state: 'negative', text:
|
|
14
|
+
return { state: 'negative', text: `${minusSign}${Math.abs(count)}` };
|
|
11
15
|
return { state: 'positive', text: signed ? `+${count}` : String(count) };
|
|
12
16
|
}
|
|
13
17
|
export function isDenominationCount(count) {
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, } from '
|
|
|
17
17
|
export { Box, type BoxBorder, type BoxElement, type BoxProps, } from './components/Box.js';
|
|
18
18
|
export { ButtonGroup, type ButtonGroupJustify, type ButtonGroupProps, } from './components/ButtonGroup.js';
|
|
19
19
|
export { Card, type CardProps, type CardVariant } from './components/Card.js';
|
|
20
|
-
export { Dialog, type DialogProps, type DialogSize, } from './components/Dialog.js';
|
|
20
|
+
export { Dialog, type DialogProps, type DialogSize, type DialogTitleLevel, } from './components/Dialog.js';
|
|
21
21
|
export { StatTile, type StatTileEmphasis, type StatTileProps, type StatTileTone, statTileTones, } from './components/StatTile.js';
|
|
22
22
|
export { TabPanel, type TabItem, type TabPanelProps, Tabs, type TabsProps, tabId, tabPanelId, } from './components/Tabs.js';
|
|
23
23
|
export { Toast, toastTones } from './components/Toast.js';
|
|
@@ -44,6 +44,6 @@ export type { GridColumns, GridProps } from './components/Grid.js';
|
|
|
44
44
|
export type { GridColumnSpan } from './css/css-grid-span.js';
|
|
45
45
|
export type { ColumnSpanProps } from './column-span-classes.js';
|
|
46
46
|
export { DenominationGrid } from './components/DenominationGrid.js';
|
|
47
|
-
export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridTone, } from './components/DenominationGrid.js';
|
|
47
|
+
export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowRole, DenominationGridTone, } from './components/DenominationGrid.js';
|
|
48
48
|
export { FilterChips } from './components/FilterChips.js';
|
|
49
49
|
export type { FilterChipOption, FilterChipsProps, } from './components/FilterChips.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Which inline edges of a scroll box have content scrolled out past them. */
|
|
2
|
+
export type ScrollOverflow = {
|
|
3
|
+
start: boolean;
|
|
4
|
+
end: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare const noScrollOverflow: ScrollOverflow;
|
|
7
|
+
/**
|
|
8
|
+
* Reads a box's horizontal overflow from its scroll metrics. `scrollLeft` is
|
|
9
|
+
* 0 at the inline start and grows toward the end in a left-to-right box; in
|
|
10
|
+
* a right-to-left box it is 0 at the start and negative toward the end, so
|
|
11
|
+
* the distance travelled is its magnitude in both.
|
|
12
|
+
*/
|
|
13
|
+
export declare function scrollOverflow(scrollLeft: number, scrollWidth: number, clientWidth: number): ScrollOverflow;
|
|
14
|
+
export declare function sameScrollOverflow(a: ScrollOverflow, b: ScrollOverflow): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const noScrollOverflow = { start: false, end: false };
|
|
2
|
+
/**
|
|
3
|
+
* Sub-pixel layout can leave a box a fraction of a pixel wider than its
|
|
4
|
+
* content, or a scroll position a fraction short of its end; within this
|
|
5
|
+
* many pixels an edge counts as reached.
|
|
6
|
+
*/
|
|
7
|
+
const edgeTolerance = 1;
|
|
8
|
+
/**
|
|
9
|
+
* Reads a box's horizontal overflow from its scroll metrics. `scrollLeft` is
|
|
10
|
+
* 0 at the inline start and grows toward the end in a left-to-right box; in
|
|
11
|
+
* a right-to-left box it is 0 at the start and negative toward the end, so
|
|
12
|
+
* the distance travelled is its magnitude in both.
|
|
13
|
+
*/
|
|
14
|
+
export function scrollOverflow(scrollLeft, scrollWidth, clientWidth) {
|
|
15
|
+
const hidden = scrollWidth - clientWidth;
|
|
16
|
+
if (hidden <= edgeTolerance)
|
|
17
|
+
return noScrollOverflow;
|
|
18
|
+
const travelled = Math.abs(scrollLeft);
|
|
19
|
+
return {
|
|
20
|
+
start: travelled > edgeTolerance,
|
|
21
|
+
end: travelled < hidden - edgeTolerance,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function sameScrollOverflow(a, b) {
|
|
25
|
+
return a.start === b.start && a.end === b.end;
|
|
26
|
+
}
|