@scalewing/react 1.26.0 → 1.28.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 +74 -0
- package/README.md +147 -3
- package/dist/components/ActionBar.d.ts +14 -0
- package/dist/components/ActionBar.js +5 -2
- package/dist/components/DenominationGrid.d.ts +5 -2
- package/dist/components/DenominationGrid.js +11 -3
- package/dist/components/Dialog.d.ts +28 -4
- package/dist/components/Dialog.js +20 -2
- package/dist/components/Notice.d.ts +69 -0
- package/dist/components/Notice.js +48 -0
- package/dist/components/Progress.d.ts +3 -1
- package/dist/components/Progress.js +4 -0
- package/dist/components/Stepper.d.ts +44 -0
- package/dist/components/Stepper.js +77 -0
- package/dist/components/Steps.d.ts +18 -0
- package/dist/components/Steps.js +34 -0
- package/dist/components/Toast.d.ts +20 -4
- package/dist/components/Toast.js +44 -21
- package/dist/components/action-bar/ActionBarSummary.d.ts +22 -0
- package/dist/components/action-bar/ActionBarSummary.js +23 -0
- package/dist/components/denomination-grid/ColumnHeadText.d.ts +7 -0
- package/dist/components/denomination-grid/ColumnHeadText.js +13 -0
- package/dist/components/denomination-grid/DenominationStrip.d.ts +5 -2
- package/dist/components/denomination-grid/DenominationStrip.js +4 -8
- package/dist/components/denomination-grid/DenominationTiles.d.ts +5 -2
- package/dist/components/denomination-grid/DenominationTiles.js +4 -3
- package/dist/components/denomination-grid/EntryCell.d.ts +15 -0
- package/dist/components/denomination-grid/EntryCell.js +16 -0
- package/dist/components/denomination-grid/RowLabelLine.d.ts +3 -2
- package/dist/components/denomination-grid/RowLabelLine.js +5 -3
- package/dist/components/denomination-grid/StripRow.d.ts +13 -0
- package/dist/components/denomination-grid/StripRow.js +19 -0
- package/dist/components/denomination-grid/types.d.ts +53 -1
- package/dist/components/denomination-grid/validate-entry.d.ts +15 -0
- package/dist/components/denomination-grid/validate-entry.js +78 -0
- package/dist/components/dialog/DialogFooter.d.ts +14 -0
- package/dist/components/dialog/DialogFooter.js +11 -0
- package/dist/components/dialog/DialogTitle.d.ts +11 -4
- package/dist/components/dialog/DialogTitle.js +8 -6
- package/dist/components/steps/Step.d.ts +24 -0
- package/dist/components/steps/Step.js +62 -0
- package/dist/components/steps/step-status.d.ts +26 -0
- package/dist/components/steps/step-status.js +37 -0
- package/dist/components/steps/steps-context.d.ts +16 -0
- package/dist/components/steps/steps-context.js +3 -0
- package/dist/components/toast/ToastContent.d.ts +22 -0
- package/dist/components/toast/ToastContent.js +21 -0
- package/dist/components/toast/use-toast-hold.d.ts +28 -0
- package/dist/components/toast/use-toast-hold.js +53 -0
- package/dist/components/toast/use-toast-timer.d.ts +15 -0
- package/dist/components/toast/use-toast-timer.js +34 -0
- package/dist/css/css-action-bar-summary.d.ts +2 -0
- package/dist/css/css-action-bar-summary.js +48 -0
- package/dist/css/css-action-bar.js +4 -0
- package/dist/css/css-components.js +16 -0
- package/dist/css/css-denomination-entry.d.ts +2 -0
- package/dist/css/css-denomination-entry.js +78 -0
- package/dist/css/css-denomination-grid.js +8 -0
- package/dist/css/css-denomination-label.d.ts +10 -0
- package/dist/css/css-denomination-label.js +36 -0
- package/dist/css/css-dialog-frame.d.ts +2 -0
- package/dist/css/css-dialog-frame.js +83 -0
- package/dist/css/css-dialog.js +20 -4
- package/dist/css/css-notice.d.ts +22 -0
- package/dist/css/css-notice.js +168 -0
- package/dist/css/css-progress.d.ts +3 -0
- package/dist/css/css-progress.js +13 -6
- package/dist/css/css-stepper.d.ts +10 -0
- package/dist/css/css-stepper.js +126 -0
- package/dist/css/css-steps.d.ts +9 -0
- package/dist/css/css-steps.js +203 -0
- package/dist/css/css-toast-card.d.ts +5 -0
- package/dist/css/css-toast-card.js +122 -0
- package/dist/css/css-variables.js +1 -0
- package/dist/css/stylesheet.js +10 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +8 -5
- package/dist/stepper-count.d.ts +37 -0
- package/dist/stepper-count.js +50 -0
- package/dist/styles.css +623 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Closes the toast once `duration` ms have run, counting only the time it is
|
|
4
|
+
* not paused. Opening it again (or a new duration) starts the count over.
|
|
5
|
+
*/
|
|
6
|
+
export function useToastTimer({ duration, onExpire, paused, running, }) {
|
|
7
|
+
const leftRef = useRef(duration);
|
|
8
|
+
const expireRef = useRef(onExpire);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
expireRef.current = onExpire;
|
|
11
|
+
}, [onExpire]);
|
|
12
|
+
// A fresh count each time the toast opens or its duration changes.
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
leftRef.current = duration;
|
|
15
|
+
}, [duration, running]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!running || paused)
|
|
18
|
+
return;
|
|
19
|
+
const started = Date.now();
|
|
20
|
+
let fired = false;
|
|
21
|
+
const timer = window.setTimeout(() => {
|
|
22
|
+
fired = true;
|
|
23
|
+
leftRef.current = duration;
|
|
24
|
+
expireRef.current();
|
|
25
|
+
}, leftRef.current);
|
|
26
|
+
return () => {
|
|
27
|
+
window.clearTimeout(timer);
|
|
28
|
+
// After it fired the count is whole again (a consumer that keeps the
|
|
29
|
+
// toast open gets the full time on its next run, not none).
|
|
30
|
+
if (!fired)
|
|
31
|
+
leftRef.current = Math.max(0, leftRef.current - (Date.now() - started));
|
|
32
|
+
};
|
|
33
|
+
}, [duration, paused, running]);
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
const label = typographyVariants.label;
|
|
3
|
+
/*
|
|
4
|
+
* The bar's figure: label, value and detail on one baseline, a small gap
|
|
5
|
+
* apart, wrapping as one phrase. The value takes the heading variant and
|
|
6
|
+
* tabular figures from the text classes it carries (so it steps down below
|
|
7
|
+
* md with every heading); the label and detail are muted label-size words
|
|
8
|
+
* at regular weight. A tone colors the value only. Forced colors keep the
|
|
9
|
+
* system text color.
|
|
10
|
+
*/
|
|
11
|
+
export function cssActionBarSummaryClasses() {
|
|
12
|
+
return `.sw-action-bar-summary {
|
|
13
|
+
align-items: baseline;
|
|
14
|
+
column-gap: var(--sw-space-2);
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
min-width: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sw-action-bar-summary-label,
|
|
21
|
+
.sw-action-bar-summary-detail {
|
|
22
|
+
color: var(--sw-color-muted);
|
|
23
|
+
font-size: ${label.fontSize}px;
|
|
24
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
25
|
+
line-height: ${label.lineHeight}px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sw-action-bar-summary-value {
|
|
29
|
+
color: var(--sw-color-text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sw-action-bar-summary-danger .sw-action-bar-summary-value { color: var(--sw-color-danger); }
|
|
33
|
+
.sw-action-bar-summary-success .sw-action-bar-summary-value { color: var(--sw-color-success); }
|
|
34
|
+
|
|
35
|
+
@media (forced-colors: active) {
|
|
36
|
+
.sw-action-bar-summary-value { color: CanvasText; }
|
|
37
|
+
}`;
|
|
38
|
+
}
|
|
39
|
+
export function actionBarSummaryClassCatalog() {
|
|
40
|
+
return [
|
|
41
|
+
'sw-action-bar-summary',
|
|
42
|
+
'sw-action-bar-summary-label',
|
|
43
|
+
'sw-action-bar-summary-value',
|
|
44
|
+
'sw-action-bar-summary-detail',
|
|
45
|
+
'sw-action-bar-summary-danger',
|
|
46
|
+
'sw-action-bar-summary-success',
|
|
47
|
+
];
|
|
48
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { breakpointQuery, breakpoints, } from './breakpoints.js';
|
|
3
|
+
import { actionBarSummaryClassCatalog, cssActionBarSummaryClasses, } from './css-action-bar-summary.js';
|
|
3
4
|
import { zIndex } from './stacking.js';
|
|
4
5
|
const caption = typographyVariants.caption;
|
|
5
6
|
/* The bar floats a step above the bottom edge and clears the home indicator. */
|
|
@@ -93,6 +94,8 @@ export function cssActionBarClasses() {
|
|
|
93
94
|
.sw-action-bar-actions > * { flex: 1 1 auto; }
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
${cssActionBarSummaryClasses()}
|
|
98
|
+
|
|
96
99
|
${stickyRules()}`;
|
|
97
100
|
}
|
|
98
101
|
export function actionBarClassCatalog() {
|
|
@@ -101,6 +104,7 @@ export function actionBarClassCatalog() {
|
|
|
101
104
|
'sw-action-bar-status',
|
|
102
105
|
'sw-action-bar-actions',
|
|
103
106
|
'sw-action-bar-sticky',
|
|
107
|
+
...actionBarSummaryClassCatalog(),
|
|
104
108
|
...breakpoints.map((breakpoint) => actionBarStickyClass(breakpoint)),
|
|
105
109
|
];
|
|
106
110
|
}
|
|
@@ -13,12 +13,14 @@ import { cssSegmentedClasses } from './css-segmented.js';
|
|
|
13
13
|
import { cssTableClasses } from './css-table.js';
|
|
14
14
|
import { cssDateFieldClasses } from './css-date-field.js';
|
|
15
15
|
import { cssDialogClasses } from './css-dialog.js';
|
|
16
|
+
import { cssDialogFrameClasses } from './css-dialog-frame.js';
|
|
16
17
|
import { cssSelectClasses } from './css-select.js';
|
|
17
18
|
import { cssSplitClasses } from './css-split.js';
|
|
18
19
|
import { cssSwitchClasses } from './css-switch.js';
|
|
19
20
|
import { cssTextClasses } from './css-text.js';
|
|
20
21
|
import { cssSpinnerClasses } from './css-spinner.js';
|
|
21
22
|
import { cssProgressClasses } from './css-progress.js';
|
|
23
|
+
import { cssNoticeClasses } from './css-notice.js';
|
|
22
24
|
import { cssTooltipClasses } from './css-tooltip.js';
|
|
23
25
|
import { cssInfoTipClasses } from './css-info-tip.js';
|
|
24
26
|
import { cssSeparatorClasses } from './css-separator.js';
|
|
@@ -31,7 +33,10 @@ import { cssGridClasses } from './css-grid.js';
|
|
|
31
33
|
import { cssGridSpanClasses } from './css-grid-span.js';
|
|
32
34
|
import { cssDenominationGridClasses } from './css-denomination-grid.js';
|
|
33
35
|
import { cssFieldClasses } from './css-field.js';
|
|
36
|
+
import { cssStepperClasses } from './css-stepper.js';
|
|
37
|
+
import { cssStepsClasses } from './css-steps.js';
|
|
34
38
|
import { cssToastClasses } from './css-toast.js';
|
|
39
|
+
import { cssToastCardClasses } from './css-toast-card.js';
|
|
35
40
|
import { cssBoxBorderClasses } from './css-box-border.js';
|
|
36
41
|
import { cssPopupHostRules } from './stacking.js';
|
|
37
42
|
/*
|
|
@@ -44,6 +49,7 @@ function reducedTransparencyRules() {
|
|
|
44
49
|
.sw-card-glass,
|
|
45
50
|
.sw-app-header,
|
|
46
51
|
.sw-action-bar,
|
|
52
|
+
.sw-steps-item,
|
|
47
53
|
.sw-badge-neutral,
|
|
48
54
|
.sw-segmented,
|
|
49
55
|
.sw-table-sticky thead th,
|
|
@@ -130,6 +136,8 @@ ${cssSectionNavClasses()}
|
|
|
130
136
|
|
|
131
137
|
${cssDialogClasses()}
|
|
132
138
|
|
|
139
|
+
${cssDialogFrameClasses()}
|
|
140
|
+
|
|
133
141
|
${cssAccordionClasses()}
|
|
134
142
|
|
|
135
143
|
${cssSelectClasses()}
|
|
@@ -144,6 +152,8 @@ ${cssSpinnerClasses()}
|
|
|
144
152
|
|
|
145
153
|
${cssProgressClasses()}
|
|
146
154
|
|
|
155
|
+
${cssNoticeClasses()}
|
|
156
|
+
|
|
147
157
|
${cssTooltipClasses()}
|
|
148
158
|
|
|
149
159
|
${cssInfoTipClasses()}
|
|
@@ -164,10 +174,16 @@ ${cssDenominationGridClasses()}
|
|
|
164
174
|
|
|
165
175
|
${cssFieldClasses()}
|
|
166
176
|
|
|
177
|
+
${cssStepperClasses()}
|
|
178
|
+
|
|
179
|
+
${cssStepsClasses()}
|
|
180
|
+
|
|
167
181
|
${cssSplitClasses()}
|
|
168
182
|
|
|
169
183
|
${cssToastClasses()}
|
|
170
184
|
|
|
185
|
+
${cssToastCardClasses()}
|
|
186
|
+
|
|
171
187
|
${cssResponsiveClasses()}
|
|
172
188
|
|
|
173
189
|
${cssBoxBorderClasses()}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
const caption = typographyVariants.caption;
|
|
3
|
+
/** An enabled entry count, unless its Stepper shows an error (whose danger border wins). */
|
|
4
|
+
const tonedEntryInput = '[data-theme] .sw-denomination-cell-toned .sw-stepper:not(.sw-stepper-invalid) input.sw-stepper-input:not(:disabled)';
|
|
5
|
+
/*
|
|
6
|
+
* Count entry and the labels around it. An entry cell holds a full-width
|
|
7
|
+
* Stepper; its count keeps spacing step 8 (room for three digits), so a narrow strip scrolls
|
|
8
|
+
* in its region instead of crushing the counts. A cell tone draws the
|
|
9
|
+
* count's border in the tone, a hairline thicker through an inset shadow so
|
|
10
|
+
* nothing moves. A caption is a muted caption-size line: under a row label,
|
|
11
|
+
* or after a column label. A quiet row sits on the subtle fill, its pinned
|
|
12
|
+
* label too. A disabled row fades its label, counts and total (its entry is
|
|
13
|
+
* disabled by its Stepper); a pinned label fades its contents, not its
|
|
14
|
+
* fill, so counts never show through it.
|
|
15
|
+
*/
|
|
16
|
+
export function cssDenominationEntryClasses() {
|
|
17
|
+
return `.sw-denomination-strip td.sw-denomination-entry {
|
|
18
|
+
padding: var(--sw-space-1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.sw-denomination-entry .sw-stepper-full .sw-stepper-track {
|
|
22
|
+
grid-template-columns: var(--sw-space-6) minmax(var(--sw-space-8), 1fr) var(--sw-space-6);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* A strip with entry rows centres its heads and read-only counts over the steppers. */
|
|
26
|
+
.sw-denomination-strip-entry .sw-denomination-head:not(.sw-denomination-total-head),
|
|
27
|
+
.sw-denomination-strip-entry .sw-denomination-cell {
|
|
28
|
+
text-align: center;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
${tonedEntryInput} {
|
|
32
|
+
border-color: var(--sw-denomination-cell-tone);
|
|
33
|
+
box-shadow: inset 0 0 0 1px var(--sw-denomination-cell-tone);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sw-denomination-label-words {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sw-denomination-label-caption,
|
|
43
|
+
.sw-denomination-head-caption {
|
|
44
|
+
color: var(--sw-color-muted);
|
|
45
|
+
font-size: ${caption.fontSize}px;
|
|
46
|
+
font-weight: ${caption.fontWeight};
|
|
47
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
48
|
+
line-height: ${caption.lineHeight}px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.sw-denomination-row-quiet,
|
|
52
|
+
.sw-denomination-strip .sw-denomination-row-quiet .sw-denomination-label {
|
|
53
|
+
background: var(--sw-color-subtle);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.sw-denomination-strip .sw-denomination-row-disabled .sw-denomination-label-body,
|
|
57
|
+
.sw-denomination-strip .sw-denomination-row-disabled .sw-denomination-cell,
|
|
58
|
+
.sw-denomination-strip .sw-denomination-row-disabled .sw-denomination-total {
|
|
59
|
+
opacity: var(--sw-disabled-opacity);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media (forced-colors: active) {
|
|
63
|
+
${tonedEntryInput} { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; }
|
|
64
|
+
.sw-denomination-label-caption,
|
|
65
|
+
.sw-denomination-head-caption { color: CanvasText; }
|
|
66
|
+
}`;
|
|
67
|
+
}
|
|
68
|
+
export function denominationEntryClassCatalog() {
|
|
69
|
+
return [
|
|
70
|
+
'sw-denomination-entry',
|
|
71
|
+
'sw-denomination-strip-entry',
|
|
72
|
+
'sw-denomination-label-words',
|
|
73
|
+
'sw-denomination-label-caption',
|
|
74
|
+
'sw-denomination-head-caption',
|
|
75
|
+
'sw-denomination-row-quiet',
|
|
76
|
+
'sw-denomination-row-disabled',
|
|
77
|
+
];
|
|
78
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { breakpointQuery } from './breakpoints.js';
|
|
3
3
|
import { badgeTones } from './css-data.js';
|
|
4
|
+
import { cssDenominationLabelClasses, denominationLabelClassCatalog, } from './css-denomination-label.js';
|
|
4
5
|
import { cssDenominationAlignedClasses, denominationAlignedClassCatalog, } from './css-denomination-aligned.js';
|
|
6
|
+
import { cssDenominationEntryClasses, denominationEntryClassCatalog, } from './css-denomination-entry.js';
|
|
5
7
|
import { cssDenominationCellToneClasses, denominationCellToneClassCatalog, } from './css-denomination-cell-tones.js';
|
|
6
8
|
import { pinnedStartShadeRules, scrollRegionRules, } from './css-scroll-region.js';
|
|
7
9
|
import { visuallyHiddenDeclarations } from './css-utilities.js';
|
|
@@ -249,8 +251,12 @@ ${pinnedStartShadeRules('.sw-denomination-scroll', [
|
|
|
249
251
|
|
|
250
252
|
${cssDenominationCellToneClasses()}
|
|
251
253
|
|
|
254
|
+
${cssDenominationEntryClasses()}
|
|
255
|
+
|
|
252
256
|
${cssDenominationAlignedClasses()}
|
|
253
257
|
|
|
258
|
+
${cssDenominationLabelClasses()}
|
|
259
|
+
|
|
254
260
|
@media (forced-colors: active) {
|
|
255
261
|
.sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
|
|
256
262
|
.sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
|
|
@@ -290,6 +296,8 @@ export function denominationGridClassCatalog() {
|
|
|
290
296
|
'sw-denomination-tile',
|
|
291
297
|
'sw-denomination-subtotal',
|
|
292
298
|
...denominationCellToneClassCatalog(),
|
|
299
|
+
...denominationEntryClassCatalog(),
|
|
293
300
|
...denominationAlignedClassCatalog(),
|
|
301
|
+
...denominationLabelClassCatalog(),
|
|
294
302
|
];
|
|
295
303
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const denominationLabelAppearances: readonly ["tone", "plain"];
|
|
2
|
+
/**
|
|
3
|
+
* How a grid's row labels look: `tone` (the default) marks each label with
|
|
4
|
+
* its row's tone, a stripe at its start and the words in the tone; `plain`
|
|
5
|
+
* sets the words in the text color at label weight, with no stripe.
|
|
6
|
+
*/
|
|
7
|
+
export type DenominationLabelAppearance = (typeof denominationLabelAppearances)[number];
|
|
8
|
+
export declare const denominationLabelsPlainClass = "sw-denomination-labels-plain";
|
|
9
|
+
export declare function cssDenominationLabelClasses(): string;
|
|
10
|
+
export declare function denominationLabelClassCatalog(): string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
2
|
+
export const denominationLabelAppearances = ['tone', 'plain'];
|
|
3
|
+
export const denominationLabelsPlainClass = 'sw-denomination-labels-plain';
|
|
4
|
+
const plain = `.${denominationLabelsPlainClass}`;
|
|
5
|
+
/*
|
|
6
|
+
* Plain row labels: the text color, no stripe, and the strip's label cell
|
|
7
|
+
* back to its column's own padding. The row's tone still colors its total.
|
|
8
|
+
* Emitted after the grid's rules (and its narrow-screen padding), and one
|
|
9
|
+
* class more specific, so it wins at every width.
|
|
10
|
+
*/
|
|
11
|
+
export function cssDenominationLabelClasses() {
|
|
12
|
+
return `${plain} .sw-denomination-label {
|
|
13
|
+
border-inline-start: 0;
|
|
14
|
+
color: var(--sw-color-text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* The total under a phone's label keeps its row's tone. */
|
|
18
|
+
${plain} .sw-denomination-total-inline { color: var(--sw-denomination-tone); }
|
|
19
|
+
|
|
20
|
+
.sw-denomination-strip${plain} .sw-denomination-label {
|
|
21
|
+
padding-inline-start: var(--sw-space-2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.sw-denomination-strip${plain} .sw-denomination-label::before { content: none; }
|
|
25
|
+
|
|
26
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
27
|
+
.sw-denomination-strip${plain} .sw-denomination-label { padding-inline-start: var(--sw-space-1); }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (forced-colors: active) {
|
|
31
|
+
${plain} .sw-denomination-label { color: CanvasText; }
|
|
32
|
+
}`;
|
|
33
|
+
}
|
|
34
|
+
export function denominationLabelClassCatalog() {
|
|
35
|
+
return [denominationLabelsPlainClass];
|
|
36
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The dialog's frame beyond its title: a consumer icon before the title, a
|
|
3
|
+
* muted description under it, and a footer pinned to the bottom edge.
|
|
4
|
+
*
|
|
5
|
+
* With an icon or a description the header row centers its parts (the
|
|
6
|
+
* icon, the title over the description, the close button). The footer is
|
|
7
|
+
* sticky to the bottom of the dialog's own scrollport, bleeds to its
|
|
8
|
+
* edges under a hairline, and sits on the solid surface so content
|
|
9
|
+
* scrolling under it never shows through: the summary figure first, the
|
|
10
|
+
* actions at the end, wrapping under one another on a narrow sheet. In a
|
|
11
|
+
* bottom sheet its surface reaches the sheet's bottom edge and its own
|
|
12
|
+
* padding clears the home indicator (see the sheet rules in css-dialog.ts).
|
|
13
|
+
*/
|
|
14
|
+
export function cssDialogFrameClasses() {
|
|
15
|
+
return `.sw-dialog-header-framed {
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: var(--sw-space-3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sw-dialog-icon {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
flex: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sw-dialog-description { margin: 0; }
|
|
26
|
+
|
|
27
|
+
.sw-dialog-footer {
|
|
28
|
+
align-items: center;
|
|
29
|
+
background: var(--sw-color-surface);
|
|
30
|
+
border-top: 1px solid var(--sw-color-border);
|
|
31
|
+
/* Sticky insets count from the scrollport's padding edge: step past the
|
|
32
|
+
dialog's own padding to sit on its bottom edge. */
|
|
33
|
+
bottom: calc(-1 * var(--sw-space-5));
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
gap: var(--sw-space-2) var(--sw-space-4);
|
|
38
|
+
margin: 0 calc(-1 * var(--sw-space-5)) calc(-1 * var(--sw-space-5));
|
|
39
|
+
padding: var(--sw-space-3) var(--sw-space-5);
|
|
40
|
+
position: sticky;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sw-dialog-footer-status {
|
|
44
|
+
align-items: center;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex: 1 1 auto;
|
|
47
|
+
min-width: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* With no summary the live region stays in the dialog, so a first figure
|
|
51
|
+
is still announced, but it takes no room in the footer. */
|
|
52
|
+
.sw-dialog-footer-status:empty {
|
|
53
|
+
clip-path: inset(50%);
|
|
54
|
+
height: 1px;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
position: absolute;
|
|
57
|
+
width: 1px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.sw-dialog-footer-actions {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex: 0 1 auto;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
gap: var(--sw-space-2);
|
|
65
|
+
justify-content: flex-end;
|
|
66
|
+
margin-inline-start: auto;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (forced-colors: active) {
|
|
70
|
+
.sw-dialog-footer { border-top-color: CanvasText; }
|
|
71
|
+
}`;
|
|
72
|
+
}
|
|
73
|
+
export function dialogFrameClassCatalog() {
|
|
74
|
+
return [
|
|
75
|
+
'sw-dialog-header-framed',
|
|
76
|
+
'sw-dialog-icon',
|
|
77
|
+
'sw-dialog-titles',
|
|
78
|
+
'sw-dialog-description',
|
|
79
|
+
'sw-dialog-footer',
|
|
80
|
+
'sw-dialog-footer-status',
|
|
81
|
+
'sw-dialog-footer-actions',
|
|
82
|
+
];
|
|
83
|
+
}
|
package/dist/css/css-dialog.js
CHANGED
|
@@ -4,6 +4,8 @@ import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
|
4
4
|
const title = typographyVariants.title;
|
|
5
5
|
/* The modal's height cap: a space-8 strip of backdrop stays in view. */
|
|
6
6
|
const maxHeight = 'min(100vh - var(--sw-space-8), 100dvh - var(--sw-space-8))';
|
|
7
|
+
/* How far a sheet's pinned footer steps past the sheet's bottom padding. */
|
|
8
|
+
const sheetFooterOffset = 'calc(-1 * (var(--sw-space-5) + env(safe-area-inset-bottom, 0px)))';
|
|
7
9
|
export function dialogSheetClass(sheetBelow) {
|
|
8
10
|
return `sw-dialog-sheet-below-${sheetBelow}`;
|
|
9
11
|
}
|
|
@@ -11,9 +13,11 @@ export function dialogSheetClass(sheetBelow) {
|
|
|
11
13
|
* Below the breakpoint the dialog is a bottom sheet: docked to the bottom
|
|
12
14
|
* edge at the full viewport width, the top corners rounded and the bottom
|
|
13
15
|
* 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.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* shows above it), and padding that clears the safe areas. A pinned
|
|
17
|
+
* footer steps past that padding so its surface reaches the bottom edge,
|
|
18
|
+
* and pads itself clear of the home indicator. Emitted after
|
|
19
|
+
* `sw-dialog-lg` and `sw-dialog-xl` so it wins at equal specificity. No
|
|
20
|
+
* grabber: there is no drag gesture.
|
|
17
21
|
*/
|
|
18
22
|
function sheetRules() {
|
|
19
23
|
return breakpoints
|
|
@@ -33,6 +37,12 @@ function sheetRules() {
|
|
|
33
37
|
.${dialogSheetClass(breakpoint)}[open] {
|
|
34
38
|
animation: sw-dialog-sheet-in var(--sw-motion-default) var(--sw-motion-easing);
|
|
35
39
|
}
|
|
40
|
+
|
|
41
|
+
.${dialogSheetClass(breakpoint)} .sw-dialog-footer {
|
|
42
|
+
bottom: ${sheetFooterOffset};
|
|
43
|
+
margin-bottom: ${sheetFooterOffset};
|
|
44
|
+
padding-bottom: calc(var(--sw-space-3) + env(safe-area-inset-bottom, 0px));
|
|
45
|
+
}
|
|
36
46
|
}`)
|
|
37
47
|
.join('\n\n');
|
|
38
48
|
}
|
|
@@ -64,7 +74,8 @@ function closeButtonRules() {
|
|
|
64
74
|
gap: var(--sw-space-2);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
.sw-dialog-header > :
|
|
77
|
+
.sw-dialog-header > :is(h2, h3),
|
|
78
|
+
.sw-dialog-titles {
|
|
68
79
|
flex: 1 1 auto;
|
|
69
80
|
min-width: 0;
|
|
70
81
|
}
|
|
@@ -128,6 +139,10 @@ export function cssDialogClasses() {
|
|
|
128
139
|
max-width: min(var(--sw-dialog-max-lg), calc(100vw - var(--sw-space-8)));
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
.sw-dialog-xl {
|
|
143
|
+
max-width: min(var(--sw-dialog-max-xl), calc(100vw - var(--sw-space-8)));
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
${sheetRules()}
|
|
132
147
|
|
|
133
148
|
${sheetMotionRules()}
|
|
@@ -147,6 +162,7 @@ export function dialogClassCatalog() {
|
|
|
147
162
|
return [
|
|
148
163
|
'sw-dialog',
|
|
149
164
|
'sw-dialog-lg',
|
|
165
|
+
'sw-dialog-xl',
|
|
150
166
|
...breakpoints.map((breakpoint) => dialogSheetClass(breakpoint)),
|
|
151
167
|
'sw-dialog-header',
|
|
152
168
|
'sw-dialog-close',
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const noticeTones: readonly ["info", "success", "warning", "danger"];
|
|
2
|
+
export declare const noticeVariants: readonly ["default", "band"];
|
|
3
|
+
/** `default` is a rounded note in the flow; `band` a full-width strip of page chrome. */
|
|
4
|
+
export type NoticeVariant = (typeof noticeVariants)[number];
|
|
5
|
+
/** The notice's meaning: information, a good outcome, a caution, a refusal. */
|
|
6
|
+
export type NoticeTone = (typeof noticeTones)[number];
|
|
7
|
+
/**
|
|
8
|
+
* How much of a tone's color the notice's fill mixes into the surface. Info
|
|
9
|
+
* uses the derived accent tint instead (ADR 0011), which already keeps the
|
|
10
|
+
* accent readable on every palette.
|
|
11
|
+
*/
|
|
12
|
+
export declare const noticeTintShare = 0.1;
|
|
13
|
+
/**
|
|
14
|
+
* How far the notice's words move from the tone's color toward the text
|
|
15
|
+
* color, so they read darker than the tone on a light fill (lighter on a
|
|
16
|
+
* dark one) and keep 4.5:1 on the tint (`notice.test.tsx`).
|
|
17
|
+
*/
|
|
18
|
+
export declare const noticeInkTextShare = 0.3;
|
|
19
|
+
/** The semantic color a tone is drawn from. */
|
|
20
|
+
export declare function noticeToneColor(tone: NoticeTone): string;
|
|
21
|
+
export declare function cssNoticeClasses(): string;
|
|
22
|
+
export declare function noticeClassCatalog(): string[];
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
export const noticeTones = ['info', 'success', 'warning', 'danger'];
|
|
3
|
+
export const noticeVariants = ['default', 'band'];
|
|
4
|
+
/**
|
|
5
|
+
* How much of a tone's color the notice's fill mixes into the surface. Info
|
|
6
|
+
* uses the derived accent tint instead (ADR 0011), which already keeps the
|
|
7
|
+
* accent readable on every palette.
|
|
8
|
+
*/
|
|
9
|
+
export const noticeTintShare = 0.1;
|
|
10
|
+
/**
|
|
11
|
+
* How far the notice's words move from the tone's color toward the text
|
|
12
|
+
* color, so they read darker than the tone on a light fill (lighter on a
|
|
13
|
+
* dark one) and keep 4.5:1 on the tint (`notice.test.tsx`).
|
|
14
|
+
*/
|
|
15
|
+
export const noticeInkTextShare = 0.3;
|
|
16
|
+
/** The semantic color a tone is drawn from. */
|
|
17
|
+
export function noticeToneColor(tone) {
|
|
18
|
+
return tone === 'info' ? 'accent' : tone;
|
|
19
|
+
}
|
|
20
|
+
function toneFill(tone) {
|
|
21
|
+
if (tone === 'info')
|
|
22
|
+
return 'var(--sw-color-accentSubtle)';
|
|
23
|
+
return `color-mix(in srgb, var(--sw-color-${tone}) ${noticeTintShare * 100}%, var(--sw-color-surface))`;
|
|
24
|
+
}
|
|
25
|
+
function toneInk(tone) {
|
|
26
|
+
const toneShare = Math.round((1 - noticeInkTextShare) * 100);
|
|
27
|
+
return `color-mix(in srgb, var(--sw-color-${noticeToneColor(tone)}) ${toneShare}%, var(--sw-color-text))`;
|
|
28
|
+
}
|
|
29
|
+
function toneRules() {
|
|
30
|
+
return noticeTones
|
|
31
|
+
.map((tone) => `.sw-notice-${tone} { --sw-notice-fill: ${toneFill(tone)}; --sw-notice-ink: ${toneInk(tone)}; }`)
|
|
32
|
+
.join('\n');
|
|
33
|
+
}
|
|
34
|
+
const label = typographyVariants.label;
|
|
35
|
+
const body = typographyVariants.body;
|
|
36
|
+
const caption = typographyVariants.caption;
|
|
37
|
+
/*
|
|
38
|
+
* A tinted block in the page: the tone's fill, its words in the tone's ink,
|
|
39
|
+
* label-size type. An optional glyph leads; the title (a bold lead) and a
|
|
40
|
+
* text message share a row while they fit and wrap under one another when
|
|
41
|
+
* they do not; a message of elements (a field, a checkbox) is stacked under
|
|
42
|
+
* the title. An action ends the row (a small Button
|
|
43
|
+
* overhangs the padding rather than growing it) and drops under the text on
|
|
44
|
+
* a narrow column. Controls inside keep their own colors. Forced
|
|
45
|
+
* colors draw a border instead of the fill.
|
|
46
|
+
*/
|
|
47
|
+
export function cssNoticeClasses() {
|
|
48
|
+
return `${toneRules()}
|
|
49
|
+
|
|
50
|
+
.sw-notice {
|
|
51
|
+
align-items: flex-start;
|
|
52
|
+
background: var(--sw-notice-fill);
|
|
53
|
+
border-radius: var(--sw-radius-sm);
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
color: var(--sw-notice-ink);
|
|
56
|
+
column-gap: var(--sw-space-2);
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-wrap: wrap;
|
|
59
|
+
font-family: var(--sw-font-sans);
|
|
60
|
+
font-size: ${label.fontSize}px;
|
|
61
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
62
|
+
line-height: ${label.lineHeight}px;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
padding: var(--sw-space-2) var(--sw-space-3);
|
|
65
|
+
row-gap: var(--sw-space-2);
|
|
66
|
+
/* Muted words of a control inside (a field's or a checkbox's
|
|
67
|
+
description) take the ink, which keeps 4.5:1 on the fill. */
|
|
68
|
+
--sw-color-muted: var(--sw-notice-ink);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sw-notice-icon {
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
flex: none;
|
|
74
|
+
min-height: ${label.lineHeight}px;
|
|
75
|
+
align-items: center;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.sw-notice-content {
|
|
79
|
+
align-items: baseline;
|
|
80
|
+
column-gap: var(--sw-space-2);
|
|
81
|
+
display: flex;
|
|
82
|
+
flex: 1 1 16rem;
|
|
83
|
+
flex-wrap: wrap;
|
|
84
|
+
min-width: 0;
|
|
85
|
+
row-gap: var(--sw-space-1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sw-notice-stacked {
|
|
89
|
+
align-items: stretch;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
row-gap: var(--sw-space-2);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.sw-notice-title {
|
|
95
|
+
font-weight: ${label.fontWeight};
|
|
96
|
+
min-width: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.sw-notice-body {
|
|
100
|
+
flex: 1 1 auto;
|
|
101
|
+
font-weight: ${body.fontWeight};
|
|
102
|
+
min-width: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.sw-notice-action {
|
|
106
|
+
align-items: center;
|
|
107
|
+
align-self: flex-start;
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
flex: none;
|
|
110
|
+
min-height: ${label.lineHeight}px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* A small Button keeps the note one text line tall: it overhangs the
|
|
114
|
+
padding evenly instead of growing the row. */
|
|
115
|
+
.sw-notice-action > .sw-button {
|
|
116
|
+
margin-block: calc((${label.lineHeight}px - var(--sw-control-sm-min-height)) / 2);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* A band across the page's full width, such as an environment strip
|
|
120
|
+
above the app header: square, the xs control height, caption type,
|
|
121
|
+
its words centered on one line that wraps on a phone. A glyph or an
|
|
122
|
+
action stays on the words' row (no wrap): the words wrap instead. */
|
|
123
|
+
.sw-notice-band {
|
|
124
|
+
align-items: center;
|
|
125
|
+
border-radius: 0;
|
|
126
|
+
flex-wrap: nowrap;
|
|
127
|
+
font-size: ${caption.fontSize}px;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
130
|
+
line-height: ${caption.lineHeight}px;
|
|
131
|
+
min-height: var(--sw-control-xs-min-height);
|
|
132
|
+
padding: var(--sw-space-1) var(--sw-space-4);
|
|
133
|
+
text-align: center;
|
|
134
|
+
width: 100%;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.sw-notice-band .sw-notice-content {
|
|
138
|
+
flex: 0 1 auto;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.sw-notice-band .sw-notice-icon,
|
|
143
|
+
.sw-notice-band .sw-notice-action { min-height: ${caption.lineHeight}px; }
|
|
144
|
+
|
|
145
|
+
.sw-notice-band .sw-notice-action { align-self: center; }
|
|
146
|
+
|
|
147
|
+
.sw-notice-band .sw-notice-action > .sw-button {
|
|
148
|
+
margin-block: calc((${caption.lineHeight}px - var(--sw-control-sm-min-height)) / 2);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (forced-colors: active) {
|
|
152
|
+
.sw-notice { border: 1px solid CanvasText; }
|
|
153
|
+
.sw-notice-band { border-width: 0 0 1px; }
|
|
154
|
+
}`;
|
|
155
|
+
}
|
|
156
|
+
export function noticeClassCatalog() {
|
|
157
|
+
return [
|
|
158
|
+
'sw-notice',
|
|
159
|
+
...noticeTones.map((tone) => `sw-notice-${tone}`),
|
|
160
|
+
'sw-notice-icon',
|
|
161
|
+
'sw-notice-content',
|
|
162
|
+
'sw-notice-stacked',
|
|
163
|
+
'sw-notice-title',
|
|
164
|
+
'sw-notice-body',
|
|
165
|
+
'sw-notice-action',
|
|
166
|
+
'sw-notice-band',
|
|
167
|
+
];
|
|
168
|
+
}
|