@scalewing/react 1.48.0 → 1.50.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 +22 -0
- package/README.md +75 -2
- package/dist/components/EmptyState.d.ts +23 -0
- package/dist/components/EmptyState.js +40 -0
- package/dist/components/Grid.d.ts +11 -3
- package/dist/components/Grid.js +13 -4
- package/dist/components/Hero.d.ts +26 -0
- package/dist/components/Hero.js +23 -0
- package/dist/components/ScreenPreview.d.ts +25 -0
- package/dist/components/ScreenPreview.js +20 -0
- package/dist/components/Stepper.d.ts +11 -1
- package/dist/components/Stepper.js +10 -8
- package/dist/components/Text.js +3 -0
- package/dist/components/screen-preview/PreviewGroup.d.ts +8 -0
- package/dist/components/screen-preview/PreviewGroup.js +8 -0
- package/dist/components/screen-preview/PreviewRow.d.ts +15 -0
- package/dist/components/screen-preview/PreviewRow.js +11 -0
- package/dist/components/screen-preview/PreviewTiles.d.ts +16 -0
- package/dist/components/screen-preview/PreviewTiles.js +15 -0
- package/dist/components/stepper/StepperCountFrame.d.ts +15 -0
- package/dist/components/stepper/StepperCountFrame.js +25 -0
- package/dist/css/css-components.js +9 -0
- package/dist/css/css-document.js +3 -0
- package/dist/css/css-empty-state.d.ts +2 -0
- package/dist/css/css-empty-state.js +77 -0
- package/dist/css/css-grid-span.js +5 -2
- package/dist/css/css-grid.d.ts +13 -0
- package/dist/css/css-grid.js +17 -1
- package/dist/css/css-hero.d.ts +2 -0
- package/dist/css/css-hero.js +62 -0
- package/dist/css/css-screen-preview.d.ts +10 -0
- package/dist/css/css-screen-preview.js +232 -0
- package/dist/css/css-stepper-unit.d.ts +9 -0
- package/dist/css/css-stepper-unit.js +102 -0
- package/dist/css/css-stepper.js +5 -1
- package/dist/css/css-theme-frame.d.ts +10 -0
- package/dist/css/css-theme-frame.js +26 -0
- package/dist/css/stylesheet.js +6 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +8 -1
- package/dist/stepper-count.d.ts +8 -0
- package/dist/stepper-count.js +17 -0
- package/dist/styles.css +393 -1
- package/dist/theme/ThemeProvider.d.ts +10 -1
- package/dist/theme/ThemeProvider.js +4 -2
- package/package.json +2 -2
|
@@ -43,6 +43,7 @@ import { cssSwitchClasses } from './css-switch.js';
|
|
|
43
43
|
import { cssTextClasses } from './css-text.js';
|
|
44
44
|
import { cssFontMonoClasses } from './css-font-mono.js';
|
|
45
45
|
import { cssSkeletonClasses } from './css-skeleton.js';
|
|
46
|
+
import { cssEmptyStateClasses } from './css-empty-state.js';
|
|
46
47
|
import { cssSpinnerClasses } from './css-spinner.js';
|
|
47
48
|
import { cssProgressClasses } from './css-progress.js';
|
|
48
49
|
import { cssNoticeClasses } from './css-notice.js';
|
|
@@ -51,6 +52,8 @@ import { cssTooltipClasses } from './css-tooltip.js';
|
|
|
51
52
|
import { cssInfoTipClasses } from './css-info-tip.js';
|
|
52
53
|
import { cssSeparatorClasses } from './css-separator.js';
|
|
53
54
|
import { cssBandClasses } from './css-band.js';
|
|
55
|
+
import { cssScreenPreviewClasses } from './css-screen-preview.js';
|
|
56
|
+
import { cssHeroClasses } from './css-hero.js';
|
|
54
57
|
import { cssSiteFooterClasses } from './css-site-footer.js';
|
|
55
58
|
import { cssSiteHeaderClasses } from './css-site-header.js';
|
|
56
59
|
import { cssStatTileClasses } from './css-stat-tile.js';
|
|
@@ -200,6 +203,10 @@ ${cssSectionNavClasses()}
|
|
|
200
203
|
|
|
201
204
|
${cssBandClasses()}
|
|
202
205
|
|
|
206
|
+
${cssScreenPreviewClasses()}
|
|
207
|
+
|
|
208
|
+
${cssHeroClasses()}
|
|
209
|
+
|
|
203
210
|
${cssSiteFooterClasses()}
|
|
204
211
|
|
|
205
212
|
${cssSiteHeaderClasses()}
|
|
@@ -237,6 +244,8 @@ ${cssSpinnerClasses()}
|
|
|
237
244
|
|
|
238
245
|
${cssSkeletonClasses()}
|
|
239
246
|
|
|
247
|
+
${cssEmptyStateClasses()}
|
|
248
|
+
|
|
240
249
|
${cssProgressClasses()}
|
|
241
250
|
|
|
242
251
|
${cssNoticeClasses()}
|
package/dist/css/css-document.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { cssBoxLinkClasses } from './css-box-link.js';
|
|
3
3
|
import { cssCanvasGroupedRules } from './css-canvas-grouped.js';
|
|
4
|
+
import { cssThemeFrameRules } from './css-theme-frame.js';
|
|
4
5
|
const body = typographyVariants.body;
|
|
5
6
|
/** The native controls that take typed text: the canvas's text controls. */
|
|
6
7
|
export const typedInputs = [
|
|
@@ -87,6 +88,8 @@ body {
|
|
|
87
88
|
|
|
88
89
|
${cssCanvasGroupedRules()}
|
|
89
90
|
|
|
91
|
+
${cssThemeFrameRules()}
|
|
92
|
+
|
|
90
93
|
[data-theme] a {
|
|
91
94
|
color: var(--sw-color-accent);
|
|
92
95
|
text-decoration: none;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
2
|
+
/*
|
|
3
|
+
* EmptyState: a centred column on the surface inside a dashed hairline
|
|
4
|
+
* (the md radius), spacing step 8 above and below and step 4 at the sides,
|
|
5
|
+
* its parts a step 3 apart. The glyph sits in a decorative step-8 square of
|
|
6
|
+
* the subtle fill in the muted colour, a direct-child SVG drawn at step 5.
|
|
7
|
+
* The title and line keep a readable measure and wrap; a long word breaks.
|
|
8
|
+
* The actions are a centred row that wraps, a step 2 apart, each Button a
|
|
9
|
+
* 44 px target on a coarse pointer. Forced colours keep the dashed edge in
|
|
10
|
+
* the system's border colour and draw the glyph in CanvasText.
|
|
11
|
+
*/
|
|
12
|
+
export function cssEmptyStateClasses() {
|
|
13
|
+
return `.sw-empty-state {
|
|
14
|
+
align-items: center;
|
|
15
|
+
background: var(--sw-color-surface);
|
|
16
|
+
border: 1px dashed var(--sw-color-border);
|
|
17
|
+
border-radius: var(--sw-radius-md);
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: var(--sw-space-3);
|
|
22
|
+
min-width: 0;
|
|
23
|
+
overflow-wrap: anywhere;
|
|
24
|
+
padding: var(--sw-space-8) var(--sw-space-4);
|
|
25
|
+
text-align: center;
|
|
26
|
+
width: 100%;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sw-empty-state-icon {
|
|
30
|
+
align-items: center;
|
|
31
|
+
background: var(--sw-color-subtle);
|
|
32
|
+
border-radius: var(--sw-radius-md);
|
|
33
|
+
color: var(--sw-color-muted);
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
flex: none;
|
|
36
|
+
height: var(--sw-space-8);
|
|
37
|
+
justify-content: center;
|
|
38
|
+
width: var(--sw-space-8);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sw-empty-state-icon > svg {
|
|
42
|
+
height: var(--sw-space-5);
|
|
43
|
+
width: var(--sw-space-5);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sw-empty-state-title,
|
|
47
|
+
.sw-empty-state-description {
|
|
48
|
+
margin: 0;
|
|
49
|
+
max-width: var(--sw-reading-max);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sw-empty-state-actions {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
gap: var(--sw-space-2);
|
|
56
|
+
justify-content: center;
|
|
57
|
+
margin-top: var(--sw-space-1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media ${coarsePointerQuery} {
|
|
61
|
+
.sw-empty-state-actions .sw-button { min-height: ${touchTarget}; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@media (forced-colors: active) {
|
|
65
|
+
.sw-empty-state { border-color: CanvasText; }
|
|
66
|
+
.sw-empty-state-icon { color: CanvasText; }
|
|
67
|
+
}`;
|
|
68
|
+
}
|
|
69
|
+
export function emptyStateClassCatalog() {
|
|
70
|
+
return [
|
|
71
|
+
'sw-empty-state',
|
|
72
|
+
'sw-empty-state-icon',
|
|
73
|
+
'sw-empty-state-title',
|
|
74
|
+
'sw-empty-state-description',
|
|
75
|
+
'sw-empty-state-actions',
|
|
76
|
+
];
|
|
77
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { breakpointQuery, breakpoints } from './breakpoints.js';
|
|
2
|
-
import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, } from './css-grid.js';
|
|
2
|
+
import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, gridFitClass, gridFits, } from './css-grid.js';
|
|
3
3
|
export const gridColumnSpans = gridColumnCounts;
|
|
4
4
|
export function gridSpanClass(span) {
|
|
5
5
|
return `sw-grid-span-${span}`;
|
|
@@ -21,6 +21,9 @@ function widerSpans(columns) {
|
|
|
21
21
|
export function cssGridSpanClasses() {
|
|
22
22
|
const spans = gridColumnSpans.map((span) => spanRule(`.${gridSpanClass(span)}`, span));
|
|
23
23
|
const caps = gridColumnCounts.flatMap((columns) => widerSpans(columns).map((span) => capRule(gridColumnsClass(columns), columns, span)));
|
|
24
|
+
// A fitted grid's column count follows its width, so a span there would
|
|
25
|
+
// add implicit columns: its children each take one column.
|
|
26
|
+
const fitted = gridFits.flatMap((fit) => widerSpans(1).map((span) => `:where(.${gridFitClass(fit)}) > .${gridSpanClass(span)} { grid-column: auto; }`));
|
|
24
27
|
// Below a breakpoint every span wider than one is restated for the phone
|
|
25
28
|
// column count, which also undoes a desktop cap when that count is larger.
|
|
26
29
|
const below = breakpoints.map((breakpoint) => `@media ${breakpointQuery('below', breakpoint)} {
|
|
@@ -28,7 +31,7 @@ ${gridColumnCounts
|
|
|
28
31
|
.flatMap((columns) => widerSpans(1).map((span) => ` ${capRule(gridColumnsBelowClass(breakpoint, columns), columns, span)}`))
|
|
29
32
|
.join('\n')}
|
|
30
33
|
}`);
|
|
31
|
-
return [...spans, ...caps, ...below].join('\n');
|
|
34
|
+
return [...spans, ...caps, ...fitted, ...below].join('\n');
|
|
32
35
|
}
|
|
33
36
|
export function gridSpanClassCatalog() {
|
|
34
37
|
return gridColumnSpans.map((span) => gridSpanClass(span));
|
package/dist/css/css-grid.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { type Breakpoint } from './breakpoints.js';
|
|
2
2
|
export declare const gridColumnCounts: readonly [1, 2, 3, 4, 6];
|
|
3
3
|
export type GridColumns = (typeof gridColumnCounts)[number];
|
|
4
|
+
/**
|
|
5
|
+
* A fitted grid's narrowest column: as many columns as fit the grid's width
|
|
6
|
+
* at this minimum, each stretched to share the row, and one column when
|
|
7
|
+
* the grid is narrower than the minimum.
|
|
8
|
+
*/
|
|
9
|
+
export declare const gridFitWidths: {
|
|
10
|
+
readonly narrow: "15rem";
|
|
11
|
+
readonly medium: "16.25rem";
|
|
12
|
+
readonly wide: "18.75rem";
|
|
13
|
+
};
|
|
14
|
+
export type GridFit = keyof typeof gridFitWidths;
|
|
15
|
+
export declare const gridFits: GridFit[];
|
|
16
|
+
export declare function gridFitClass(fit: GridFit): string;
|
|
4
17
|
export declare function gridColumnsClass(columns: GridColumns): string;
|
|
5
18
|
export declare function gridColumnsBelowClass(breakpoint: Breakpoint, columns: GridColumns): string;
|
|
6
19
|
export declare function cssGridClasses(): string;
|
package/dist/css/css-grid.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { breakpointQuery, breakpoints, } from './breakpoints.js';
|
|
2
2
|
export const gridColumnCounts = [1, 2, 3, 4, 6];
|
|
3
|
+
/**
|
|
4
|
+
* A fitted grid's narrowest column: as many columns as fit the grid's width
|
|
5
|
+
* at this minimum, each stretched to share the row, and one column when
|
|
6
|
+
* the grid is narrower than the minimum.
|
|
7
|
+
*/
|
|
8
|
+
export const gridFitWidths = {
|
|
9
|
+
narrow: '15rem',
|
|
10
|
+
medium: '16.25rem',
|
|
11
|
+
wide: '18.75rem',
|
|
12
|
+
};
|
|
13
|
+
export const gridFits = Object.keys(gridFitWidths);
|
|
14
|
+
export function gridFitClass(fit) {
|
|
15
|
+
return `sw-grid-fit-${fit}`;
|
|
16
|
+
}
|
|
3
17
|
export function gridColumnsClass(columns) {
|
|
4
18
|
return `sw-grid-cols-${columns}`;
|
|
5
19
|
}
|
|
@@ -20,12 +34,14 @@ ${gridColumnCounts
|
|
|
20
34
|
.map((count) => columnsRule(gridColumnsBelowClass(breakpoint, count), count))
|
|
21
35
|
.join('\n')}
|
|
22
36
|
}`);
|
|
23
|
-
|
|
37
|
+
const fitted = gridFits.map((fit) => `.${gridFitClass(fit)} { grid-template-columns: repeat(auto-fit, minmax(min(100%, ${gridFitWidths[fit]}), 1fr)); }`);
|
|
38
|
+
return [base, ...columns, ...below, ...fitted].join('\n');
|
|
24
39
|
}
|
|
25
40
|
export function gridClassCatalog() {
|
|
26
41
|
return [
|
|
27
42
|
'sw-grid',
|
|
28
43
|
...gridColumnCounts.map((count) => gridColumnsClass(count)),
|
|
29
44
|
...breakpoints.flatMap((breakpoint) => gridColumnCounts.map((count) => gridColumnsBelowClass(breakpoint, count))),
|
|
45
|
+
...gridFits.map(gridFitClass),
|
|
30
46
|
];
|
|
31
47
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
2
|
+
/*
|
|
3
|
+
* A public page's opening: the words in one column (an eyebrow, the hero
|
|
4
|
+
* title, the lead and the actions) and an optional picture beside them.
|
|
5
|
+
* From `md` the picture keeps its own width at the row's end and the words
|
|
6
|
+
* take the rest; below `md` the picture sits centred under the words and
|
|
7
|
+
* the actions stack to the full width, as a phone's thumb expects. With no
|
|
8
|
+
* picture the words keep the reading width, so a long title or lead does
|
|
9
|
+
* not run the page's full width. The actions sit one spacing step further
|
|
10
|
+
* from the lead than the text's gap, so the buttons read as their own row.
|
|
11
|
+
*/
|
|
12
|
+
export function cssHeroClasses() {
|
|
13
|
+
return `.sw-hero {
|
|
14
|
+
align-items: center;
|
|
15
|
+
display: grid;
|
|
16
|
+
gap: var(--sw-space-8);
|
|
17
|
+
min-width: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sw-hero-text {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: var(--sw-space-4);
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sw-hero:not(.sw-hero-with-media) .sw-hero-text {
|
|
28
|
+
max-width: var(--sw-reading-max);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.sw-hero-actions {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
gap: var(--sw-space-3);
|
|
35
|
+
padding-top: var(--sw-space-1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sw-hero-media {
|
|
39
|
+
justify-self: center;
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@media ${breakpointQuery('from', 'md')} {
|
|
45
|
+
.sw-hero-text { gap: var(--sw-space-5); }
|
|
46
|
+
.sw-hero-with-media { grid-template-columns: minmax(0, 1fr) auto; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
50
|
+
.sw-hero { gap: var(--sw-space-6); }
|
|
51
|
+
.sw-hero-actions { align-items: stretch; flex-direction: column; }
|
|
52
|
+
}`;
|
|
53
|
+
}
|
|
54
|
+
export function heroClassCatalog() {
|
|
55
|
+
return [
|
|
56
|
+
'sw-hero',
|
|
57
|
+
'sw-hero-with-media',
|
|
58
|
+
'sw-hero-text',
|
|
59
|
+
'sw-hero-actions',
|
|
60
|
+
'sw-hero-media',
|
|
61
|
+
];
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** How a drawn screen is framed: a plain card, or a phone from `md`. */
|
|
2
|
+
export declare const screenPreviewFrames: readonly ["card", "device"];
|
|
3
|
+
export type ScreenPreviewFrame = (typeof screenPreviewFrames)[number];
|
|
4
|
+
/** What a drawn row shows: a figure, a total, a note, a filled or an alert row. */
|
|
5
|
+
export declare const previewRowKinds: readonly ["plain", "total", "note", "filled", "alert"];
|
|
6
|
+
export type PreviewRowKind = (typeof previewRowKinds)[number];
|
|
7
|
+
export declare function screenPreviewFrameClass(frame: ScreenPreviewFrame): string;
|
|
8
|
+
export declare function previewRowKindClass(kind: PreviewRowKind): string;
|
|
9
|
+
export declare function cssScreenPreviewClasses(): string;
|
|
10
|
+
export declare function screenPreviewClassCatalog(): string[];
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
3
|
+
import { toneTintFill, toneTintInk } from './tone-tint.js';
|
|
4
|
+
const { caption, label, title } = typographyVariants;
|
|
5
|
+
/** How a drawn screen is framed: a plain card, or a phone from `md`. */
|
|
6
|
+
export const screenPreviewFrames = ['card', 'device'];
|
|
7
|
+
/** What a drawn row shows: a figure, a total, a note, a filled or an alert row. */
|
|
8
|
+
export const previewRowKinds = [
|
|
9
|
+
'plain',
|
|
10
|
+
'total',
|
|
11
|
+
'note',
|
|
12
|
+
'filled',
|
|
13
|
+
'alert',
|
|
14
|
+
];
|
|
15
|
+
export function screenPreviewFrameClass(frame) {
|
|
16
|
+
return `sw-screen-preview-${frame}`;
|
|
17
|
+
}
|
|
18
|
+
export function previewRowKindClass(kind) {
|
|
19
|
+
return `sw-preview-row-${kind}`;
|
|
20
|
+
}
|
|
21
|
+
/*
|
|
22
|
+
* The card frame, and the device frame below `md`: one surface card whose
|
|
23
|
+
* groups are parted by hairlines. Rows set their label at the start and
|
|
24
|
+
* their figure at the end in tabular numerals.
|
|
25
|
+
*/
|
|
26
|
+
function cardRules() {
|
|
27
|
+
return `.sw-screen-preview {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: var(--sw-space-2);
|
|
31
|
+
margin: 0;
|
|
32
|
+
max-width: 100%;
|
|
33
|
+
min-width: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sw-screen-preview-screen {
|
|
37
|
+
background: var(--sw-color-surface);
|
|
38
|
+
border-radius: var(--sw-radius-md);
|
|
39
|
+
color: var(--sw-color-text);
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
font-family: var(--sw-font-sans);
|
|
43
|
+
font-size: ${label.fontSize}px;
|
|
44
|
+
font-variant-numeric: tabular-nums;
|
|
45
|
+
gap: var(--sw-space-3);
|
|
46
|
+
line-height: ${label.lineHeight}px;
|
|
47
|
+
padding: var(--sw-space-4);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.sw-screen-preview-header {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.sw-screen-preview-eyebrow {
|
|
56
|
+
color: var(--sw-color-muted);
|
|
57
|
+
font-size: ${caption.fontSize}px;
|
|
58
|
+
line-height: ${caption.lineHeight}px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sw-screen-preview-title {
|
|
62
|
+
font-size: ${title.fontSize}px;
|
|
63
|
+
font-weight: ${title.fontWeight};
|
|
64
|
+
line-height: ${title.lineHeight}px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.sw-screen-preview-body {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
gap: var(--sw-space-2);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sw-preview-group {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: var(--sw-space-2);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sw-preview-group + .sw-preview-group {
|
|
80
|
+
border-top: 1px solid var(--sw-color-border);
|
|
81
|
+
padding-top: var(--sw-space-2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.sw-screen-preview-caption {
|
|
85
|
+
color: var(--sw-color-muted);
|
|
86
|
+
font-size: ${caption.fontSize}px;
|
|
87
|
+
line-height: ${caption.lineHeight}px;
|
|
88
|
+
}`;
|
|
89
|
+
}
|
|
90
|
+
function rowRules() {
|
|
91
|
+
return `.sw-preview-row {
|
|
92
|
+
display: flex;
|
|
93
|
+
gap: var(--sw-space-3);
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
min-width: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sw-preview-row-value {
|
|
99
|
+
text-align: end;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.${previewRowKindClass('total')} {
|
|
103
|
+
border-top: 1px solid var(--sw-color-border);
|
|
104
|
+
font-weight: ${label.fontWeight};
|
|
105
|
+
padding-top: var(--sw-space-2);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.${previewRowKindClass('note')} {
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.${previewRowKindClass('note')} .sw-preview-row-label {
|
|
114
|
+
color: var(--sw-color-muted);
|
|
115
|
+
font-size: ${caption.fontSize}px;
|
|
116
|
+
line-height: ${caption.lineHeight}px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.${previewRowKindClass('note')} .sw-preview-row-value {
|
|
120
|
+
text-align: start;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.${previewRowKindClass('filled')},
|
|
124
|
+
.${previewRowKindClass('alert')} {
|
|
125
|
+
border-radius: var(--sw-radius-sm);
|
|
126
|
+
padding: var(--sw-space-2) var(--sw-space-3);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.${previewRowKindClass('filled')} {
|
|
130
|
+
background: var(--sw-color-subtle);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.${previewRowKindClass('alert')} {
|
|
134
|
+
background: ${toneTintFill('danger')};
|
|
135
|
+
color: ${toneTintInk('danger')};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.sw-preview-tiles {
|
|
139
|
+
display: grid;
|
|
140
|
+
gap: var(--sw-space-2);
|
|
141
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.sw-preview-tile {
|
|
145
|
+
align-items: center;
|
|
146
|
+
background: var(--sw-color-subtle);
|
|
147
|
+
border-radius: var(--sw-radius-sm);
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
min-width: 0;
|
|
151
|
+
padding: var(--sw-space-2);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.sw-preview-tile-label {
|
|
155
|
+
color: var(--sw-color-muted);
|
|
156
|
+
font-size: ${caption.fontSize}px;
|
|
157
|
+
line-height: ${caption.lineHeight}px;
|
|
158
|
+
}`;
|
|
159
|
+
}
|
|
160
|
+
/*
|
|
161
|
+
* From `md` the device frame draws a phone: a dark bezel (the text colour)
|
|
162
|
+
* round a quiet screen whose header is a surface strip and whose groups
|
|
163
|
+
* are surface cards. Below `md` it is the card, since the page is already
|
|
164
|
+
* a phone.
|
|
165
|
+
*/
|
|
166
|
+
function wideRules() {
|
|
167
|
+
const device = `.${screenPreviewFrameClass('device')}`;
|
|
168
|
+
return `@media ${breakpointQuery('from', 'md')} {
|
|
169
|
+
.${screenPreviewFrameClass('card')} .sw-screen-preview-screen { padding: var(--sw-space-5); }
|
|
170
|
+
.sw-preview-tiles { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
171
|
+
${device} { width: 20rem; }
|
|
172
|
+
${device} .sw-screen-preview-bezel {
|
|
173
|
+
background: var(--sw-color-text);
|
|
174
|
+
border-radius: calc(var(--sw-radius-lg) + var(--sw-space-3));
|
|
175
|
+
padding: var(--sw-space-3);
|
|
176
|
+
}
|
|
177
|
+
${device} .sw-screen-preview-screen {
|
|
178
|
+
background: var(--sw-color-subtle);
|
|
179
|
+
border-radius: var(--sw-radius-lg);
|
|
180
|
+
gap: 0;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
padding: 0;
|
|
183
|
+
}
|
|
184
|
+
${device} .sw-screen-preview-header {
|
|
185
|
+
background: var(--sw-color-surface);
|
|
186
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
187
|
+
padding: var(--sw-space-4);
|
|
188
|
+
}
|
|
189
|
+
${device} .sw-screen-preview-body { gap: var(--sw-space-3); padding: var(--sw-space-3); }
|
|
190
|
+
${device} .sw-preview-group {
|
|
191
|
+
background: var(--sw-color-surface);
|
|
192
|
+
border-radius: var(--sw-radius-md);
|
|
193
|
+
padding: var(--sw-space-3);
|
|
194
|
+
}
|
|
195
|
+
${device} .sw-preview-group + .sw-preview-group { border-top: 0; }
|
|
196
|
+
${device} .sw-screen-preview-caption { text-align: center; }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media (forced-colors: active) {
|
|
200
|
+
.sw-screen-preview-screen { border: 1px solid CanvasText; }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@media (forced-colors: active) and ${breakpointQuery('from', 'md')} {
|
|
204
|
+
.${screenPreviewFrameClass('device')} .sw-screen-preview-screen { border: 0; }
|
|
205
|
+
.${screenPreviewFrameClass('device')} .sw-screen-preview-bezel { border: 1px solid CanvasText; }
|
|
206
|
+
}`;
|
|
207
|
+
}
|
|
208
|
+
export function cssScreenPreviewClasses() {
|
|
209
|
+
return [cardRules(), rowRules(), wideRules()].join('\n\n');
|
|
210
|
+
}
|
|
211
|
+
export function screenPreviewClassCatalog() {
|
|
212
|
+
return [
|
|
213
|
+
'sw-screen-preview',
|
|
214
|
+
...screenPreviewFrames.map(screenPreviewFrameClass),
|
|
215
|
+
'sw-screen-preview-bezel',
|
|
216
|
+
'sw-screen-preview-screen',
|
|
217
|
+
'sw-screen-preview-header',
|
|
218
|
+
'sw-screen-preview-eyebrow',
|
|
219
|
+
'sw-screen-preview-title',
|
|
220
|
+
'sw-screen-preview-body',
|
|
221
|
+
'sw-screen-preview-caption',
|
|
222
|
+
'sw-preview-group',
|
|
223
|
+
'sw-preview-row',
|
|
224
|
+
...previewRowKinds.map(previewRowKindClass),
|
|
225
|
+
'sw-preview-row-label',
|
|
226
|
+
'sw-preview-row-value',
|
|
227
|
+
'sw-preview-tiles',
|
|
228
|
+
'sw-preview-tile',
|
|
229
|
+
'sw-preview-tile-label',
|
|
230
|
+
'sw-preview-tile-value',
|
|
231
|
+
];
|
|
232
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Stepper with a `unit`: the count and its muted word ("90 min") inside
|
|
3
|
+
* one control frame, as Field's suffix. The frame draws the canvas control
|
|
4
|
+
* surface, the focus ring and the states; the input inside drops its own and
|
|
5
|
+
* keeps its few-digit width, its digits against the unit, so the frame never
|
|
6
|
+
* follows the count. The pair is centred, also in a full-width stepper.
|
|
7
|
+
*/
|
|
8
|
+
export declare function cssStepperUnitClasses(): string;
|
|
9
|
+
export declare function stepperUnitClassCatalog(): string[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { controlSurface, disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
|
|
3
|
+
import { changedTintLayer } from './css-field-changed.js';
|
|
4
|
+
const body = typographyVariants.body;
|
|
5
|
+
/*
|
|
6
|
+
* Outranks the stepper's own input rules and state rules (the invalid
|
|
7
|
+
* border, the changed shadow and tint), which the frame draws instead.
|
|
8
|
+
*/
|
|
9
|
+
const framedInput = '[data-theme] .sw-stepper .sw-stepper-count > input.sw-stepper-input';
|
|
10
|
+
const tintedFrame = '.sw-stepper-changed .sw-stepper-count:not(:has(> input:placeholder-shown))';
|
|
11
|
+
/**
|
|
12
|
+
* A Stepper with a `unit`: the count and its muted word ("90 min") inside
|
|
13
|
+
* one control frame, as Field's suffix. The frame draws the canvas control
|
|
14
|
+
* surface, the focus ring and the states; the input inside drops its own and
|
|
15
|
+
* keeps its few-digit width, its digits against the unit, so the frame never
|
|
16
|
+
* follows the count. The pair is centred, also in a full-width stepper.
|
|
17
|
+
*/
|
|
18
|
+
export function cssStepperUnitClasses() {
|
|
19
|
+
return `.sw-stepper-count {
|
|
20
|
+
${controlSurface}
|
|
21
|
+
align-items: center;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
cursor: text;
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
gap: var(--sw-space-1);
|
|
26
|
+
justify-content: center;
|
|
27
|
+
min-height: var(--sw-control-md-min-height);
|
|
28
|
+
min-width: 0;
|
|
29
|
+
padding-inline: var(--sw-space-2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sw-stepper-full .sw-stepper-count { display: flex; }
|
|
33
|
+
|
|
34
|
+
${framedInput} {
|
|
35
|
+
align-self: stretch;
|
|
36
|
+
background: transparent;
|
|
37
|
+
background-image: none;
|
|
38
|
+
border: 0;
|
|
39
|
+
border-radius: 0;
|
|
40
|
+
box-shadow: none;
|
|
41
|
+
inline-size: 5ch;
|
|
42
|
+
min-height: 0;
|
|
43
|
+
padding-inline: 0;
|
|
44
|
+
text-align: end;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
${framedInput}:focus-visible { outline: none; }
|
|
48
|
+
|
|
49
|
+
.sw-stepper-count:focus-within {
|
|
50
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
51
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sw-stepper-unit {
|
|
55
|
+
color: var(--sw-color-muted);
|
|
56
|
+
flex: none;
|
|
57
|
+
font-size: ${body.fontSize}px;
|
|
58
|
+
line-height: ${body.lineHeight}px;
|
|
59
|
+
user-select: none;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sw-stepper-invalid .sw-stepper-count { border-color: var(--sw-color-danger); }
|
|
64
|
+
|
|
65
|
+
.sw-stepper-changed .sw-stepper-count {
|
|
66
|
+
border-color: var(--sw-color-accent);
|
|
67
|
+
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
${tintedFrame} { background-image: ${changedTintLayer}; }
|
|
71
|
+
|
|
72
|
+
${tintedFrame} > .sw-stepper-unit { color: var(--sw-color-text); }
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* The frame draws the surface, so it carries the disabled look for its count,
|
|
76
|
+
* and only that look: a locked count drops the invalid and changed edges and
|
|
77
|
+
* tint, which name nothing a person can act on.
|
|
78
|
+
*/
|
|
79
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) {
|
|
80
|
+
${disabledControlSurface}
|
|
81
|
+
background-image: none;
|
|
82
|
+
border-color: var(--sw-color-border);
|
|
83
|
+
box-shadow: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) > .sw-stepper-unit {
|
|
87
|
+
color: var(--sw-color-muted);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@media (forced-colors: active) {
|
|
91
|
+
.sw-stepper-unit { color: CanvasText; }
|
|
92
|
+
.sw-stepper-invalid .sw-stepper-count { border-color: Mark; }
|
|
93
|
+
.sw-stepper-changed .sw-stepper-count { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; background-image: none; }
|
|
94
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) {
|
|
95
|
+
${disabledControlForcedColors}
|
|
96
|
+
box-shadow: none;
|
|
97
|
+
}
|
|
98
|
+
}`;
|
|
99
|
+
}
|
|
100
|
+
export function stepperUnitClassCatalog() {
|
|
101
|
+
return ['sw-stepper-count', 'sw-stepper-unit'];
|
|
102
|
+
}
|
package/dist/css/css-stepper.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { changedTintLayer } from './css-field-changed.js';
|
|
3
|
+
import { cssStepperUnitClasses, stepperUnitClassCatalog, } from './css-stepper-unit.js';
|
|
3
4
|
const body = typographyVariants.body;
|
|
4
5
|
const label = typographyVariants.label;
|
|
5
6
|
const title = typographyVariants.title;
|
|
@@ -116,7 +117,9 @@ ${input} {
|
|
|
116
117
|
|
|
117
118
|
.sw-stepper-full input.sw-stepper-input { inline-size: 100%; }
|
|
118
119
|
|
|
119
|
-
${stateRules()}
|
|
120
|
+
${stateRules()}
|
|
121
|
+
|
|
122
|
+
${cssStepperUnitClasses()}`;
|
|
120
123
|
}
|
|
121
124
|
export function stepperClassCatalog() {
|
|
122
125
|
return [
|
|
@@ -129,5 +132,6 @@ export function stepperClassCatalog() {
|
|
|
129
132
|
'sw-stepper-changed',
|
|
130
133
|
'sw-stepper-readonly',
|
|
131
134
|
'sw-stepper-limit-plain',
|
|
135
|
+
...stepperUnitClassCatalog(),
|
|
132
136
|
];
|
|
133
137
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the themed root lays out its children: `none` (default) leaves them
|
|
3
|
+
* in the document's flow; `site` is a public site's page, a column at least
|
|
4
|
+
* the window's height whose last child (the footer) is pushed to its foot,
|
|
5
|
+
* so a short page's footer still meets the window's foot.
|
|
6
|
+
*/
|
|
7
|
+
export declare const themeFrames: readonly ["none", "site"];
|
|
8
|
+
export type ThemeFrame = (typeof themeFrames)[number];
|
|
9
|
+
export declare function assertThemeFrame(frame: unknown): asserts frame is ThemeFrame;
|
|
10
|
+
export declare function cssThemeFrameRules(): string;
|