@scalewing/react 1.28.0 → 1.30.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 +60 -0
- package/README.md +48 -4
- package/dist/components/ActionMenu.d.ts +14 -0
- package/dist/components/ActionMenu.js +4 -0
- package/dist/components/ButtonGroup.d.ts +9 -1
- package/dist/components/ButtonGroup.js +2 -2
- package/dist/components/Checkbox.js +5 -4
- package/dist/components/FilterChips.d.ts +17 -1
- package/dist/components/FilterChips.js +11 -6
- package/dist/components/GlyphTile.d.ts +21 -0
- package/dist/components/GlyphTile.js +17 -0
- package/dist/components/MultiSelect.d.ts +10 -0
- package/dist/components/MultiSelect.js +62 -0
- package/dist/components/ScrollRegion.d.ts +6 -2
- package/dist/components/ScrollRegion.js +9 -3
- package/dist/components/SectionNav.d.ts +25 -2
- package/dist/components/SectionNav.js +13 -4
- package/dist/components/SegmentedControl.d.ts +8 -0
- package/dist/components/SegmentedControl.js +4 -3
- package/dist/components/Table.d.ts +49 -5
- package/dist/components/Table.js +11 -4
- package/dist/components/action-menu/ActionMenuList.js +7 -7
- package/dist/components/checkbox/CheckboxControl.d.ts +9 -0
- package/dist/components/checkbox/CheckboxControl.js +9 -0
- package/dist/components/multi-select/MultiSelectPanel.d.ts +25 -0
- package/dist/components/multi-select/MultiSelectPanel.js +41 -0
- package/dist/components/multi-select/types.d.ts +35 -0
- package/dist/components/multi-select/types.js +1 -0
- package/dist/components/multi-select/validate.d.ts +3 -0
- package/dist/components/multi-select/validate.js +25 -0
- package/dist/components/table/TableCell.d.ts +8 -0
- package/dist/components/table/TableCell.js +4 -2
- package/dist/components/table/TableRow.d.ts +39 -0
- package/dist/components/table/TableRow.js +8 -0
- package/dist/components/use-scroll-overflow.d.ts +8 -3
- package/dist/components/use-scroll-overflow.js +15 -4
- package/dist/css/css-action-menu.js +41 -0
- package/dist/css/css-button-group-attached.d.ts +2 -0
- package/dist/css/css-button-group-attached.js +70 -0
- package/dist/css/css-button.js +5 -1
- package/dist/css/css-components.js +8 -0
- package/dist/css/css-filter-chips.js +26 -0
- package/dist/css/css-glyph-tile.d.ts +5 -0
- package/dist/css/css-glyph-tile.js +57 -0
- package/dist/css/css-multi-select.d.ts +2 -0
- package/dist/css/css-multi-select.js +156 -0
- package/dist/css/css-section-nav-filled.d.ts +6 -0
- package/dist/css/css-section-nav-filled.js +80 -0
- package/dist/css/css-section-nav.js +4 -0
- package/dist/css/css-segmented.js +58 -0
- package/dist/css/css-table-footer.d.ts +2 -0
- package/dist/css/css-table-footer.js +57 -0
- package/dist/css/css-table-ledger.d.ts +10 -0
- package/dist/css/css-table-ledger.js +122 -0
- package/dist/css/css-table.js +8 -0
- package/dist/css/css-variables.js +11 -1
- package/dist/css/stacking.d.ts +9 -3
- package/dist/css/stacking.js +29 -9
- package/dist/css/stylesheet.js +4 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/styles.css +501 -7
- package/package.json +2 -2
package/dist/css/css-button.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buttonSizes, buttonVariants, controlScale, typographyVariants, } from '@scalewing/tokens';
|
|
2
2
|
import { breakpointQuery } from './breakpoints.js';
|
|
3
|
+
import { buttonGroupAttachedClassCatalog, cssButtonGroupAttachedClasses, } from './css-button-group-attached.js';
|
|
3
4
|
const label = typographyVariants.label;
|
|
4
5
|
export function buttonClassNames(options) {
|
|
5
6
|
return [
|
|
@@ -93,7 +94,9 @@ export function cssButtonClasses() {
|
|
|
93
94
|
${variantRules()}
|
|
94
95
|
${pressedRules()}
|
|
95
96
|
${sizeRules()}
|
|
96
|
-
${groupRules()}
|
|
97
|
+
${groupRules()}
|
|
98
|
+
|
|
99
|
+
${cssButtonGroupAttachedClasses()}`;
|
|
97
100
|
}
|
|
98
101
|
export const buttonGroupJustifies = ['start', 'end', 'between'];
|
|
99
102
|
/** The action row: one line from md up, full-width stacked buttons below it. */
|
|
@@ -119,5 +122,6 @@ export function buttonClassCatalog() {
|
|
|
119
122
|
...buttonSizes.map((size) => `sw-button-${size}`),
|
|
120
123
|
'sw-button-group',
|
|
121
124
|
...buttonGroupJustifies.map((name) => `sw-button-group-${name}`),
|
|
125
|
+
...buttonGroupAttachedClassCatalog(),
|
|
122
126
|
];
|
|
123
127
|
}
|
|
@@ -15,12 +15,14 @@ import { cssDateFieldClasses } from './css-date-field.js';
|
|
|
15
15
|
import { cssDialogClasses } from './css-dialog.js';
|
|
16
16
|
import { cssDialogFrameClasses } from './css-dialog-frame.js';
|
|
17
17
|
import { cssSelectClasses } from './css-select.js';
|
|
18
|
+
import { cssMultiSelectClasses } from './css-multi-select.js';
|
|
18
19
|
import { cssSplitClasses } from './css-split.js';
|
|
19
20
|
import { cssSwitchClasses } from './css-switch.js';
|
|
20
21
|
import { cssTextClasses } from './css-text.js';
|
|
21
22
|
import { cssSpinnerClasses } from './css-spinner.js';
|
|
22
23
|
import { cssProgressClasses } from './css-progress.js';
|
|
23
24
|
import { cssNoticeClasses } from './css-notice.js';
|
|
25
|
+
import { cssGlyphTileClasses } from './css-glyph-tile.js';
|
|
24
26
|
import { cssTooltipClasses } from './css-tooltip.js';
|
|
25
27
|
import { cssInfoTipClasses } from './css-info-tip.js';
|
|
26
28
|
import { cssSeparatorClasses } from './css-separator.js';
|
|
@@ -53,6 +55,7 @@ function reducedTransparencyRules() {
|
|
|
53
55
|
.sw-badge-neutral,
|
|
54
56
|
.sw-segmented,
|
|
55
57
|
.sw-table-sticky thead th,
|
|
58
|
+
.sw-table .sw-table-footer-sticky :is(th, td),
|
|
56
59
|
.sw-denomination-strip .sw-denomination-label,
|
|
57
60
|
.sw-denomination-strip thead .sw-denomination-corner:first-child,
|
|
58
61
|
.sw-bar-chart-track,
|
|
@@ -60,6 +63,7 @@ function reducedTransparencyRules() {
|
|
|
60
63
|
.sw-accordion,
|
|
61
64
|
.sw-select-list,
|
|
62
65
|
.sw-action-menu-list,
|
|
66
|
+
.sw-multi-select-panel,
|
|
63
67
|
.sw-date-field-calendar,
|
|
64
68
|
.sw-toast {
|
|
65
69
|
background: var(--sw-color-surface);
|
|
@@ -144,6 +148,8 @@ ${cssSelectClasses()}
|
|
|
144
148
|
|
|
145
149
|
${cssActionMenuClasses()}
|
|
146
150
|
|
|
151
|
+
${cssMultiSelectClasses()}
|
|
152
|
+
|
|
147
153
|
${cssActionBarClasses()}
|
|
148
154
|
|
|
149
155
|
${cssSwitchClasses()}
|
|
@@ -154,6 +160,8 @@ ${cssProgressClasses()}
|
|
|
154
160
|
|
|
155
161
|
${cssNoticeClasses()}
|
|
156
162
|
|
|
163
|
+
${cssGlyphTileClasses()}
|
|
164
|
+
|
|
157
165
|
${cssTooltipClasses()}
|
|
158
166
|
|
|
159
167
|
${cssInfoTipClasses()}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
const label = typographyVariants.label;
|
|
3
3
|
const data = typographyVariants.data;
|
|
4
|
+
const caption = typographyVariants.caption;
|
|
4
5
|
export function cssFilterChipsClasses() {
|
|
5
6
|
return `.sw-filter-chips {
|
|
6
7
|
border: 0;
|
|
@@ -101,11 +102,35 @@ export function cssFilterChipsClasses() {
|
|
|
101
102
|
opacity: var(--sw-disabled-opacity);
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/*
|
|
106
|
+
* An error: the message under the chips in the danger colour (a polite live
|
|
107
|
+
* region, out of the layout while empty) and every chip's edge in danger,
|
|
108
|
+
* the chosen one too (it keeps its accent fill), in normal and forced
|
|
109
|
+
* colours alike.
|
|
110
|
+
*/
|
|
111
|
+
.sw-filter-chips-error {
|
|
112
|
+
color: var(--sw-color-danger);
|
|
113
|
+
display: block;
|
|
114
|
+
font-family: var(--sw-font-sans);
|
|
115
|
+
font-size: ${caption.fontSize}px;
|
|
116
|
+
line-height: ${caption.lineHeight}px;
|
|
117
|
+
padding-top: var(--sw-space-2);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.sw-filter-chips-error:empty { position: absolute; }
|
|
121
|
+
|
|
122
|
+
.sw-filter-chips[aria-invalid='true'] .sw-filter-chip-face {
|
|
123
|
+
border-color: var(--sw-color-danger);
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
@media (forced-colors: active) {
|
|
105
127
|
.sw-filter-chip-face { background: Canvas; border-color: CanvasText; color: CanvasText; }
|
|
128
|
+
.sw-filter-chips-error { color: Mark; }
|
|
106
129
|
.sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
|
|
107
130
|
.sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
|
|
108
131
|
.sw-filter-chip-count { color: inherit; }
|
|
132
|
+
/* After the checked rule, so every chip, the chosen one too, takes the edge. */
|
|
133
|
+
.sw-filter-chips[aria-invalid='true'] .sw-filter-chip-face { border-color: Mark; }
|
|
109
134
|
}`;
|
|
110
135
|
}
|
|
111
136
|
export function filterChipsClassCatalog() {
|
|
@@ -118,5 +143,6 @@ export function filterChipsClassCatalog() {
|
|
|
118
143
|
'sw-filter-chip-face',
|
|
119
144
|
'sw-filter-chip-count',
|
|
120
145
|
'sw-filter-chip-quiet',
|
|
146
|
+
'sw-filter-chips-error',
|
|
121
147
|
];
|
|
122
148
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const glyphTileSizes: readonly ["sm", "md", "lg"];
|
|
2
|
+
/** `sm` 24, `md` 28 (the xs control height), `lg` 32. */
|
|
3
|
+
export type GlyphTileSize = (typeof glyphTileSizes)[number];
|
|
4
|
+
export declare function cssGlyphTileClasses(): string;
|
|
5
|
+
export declare function glyphTileClassCatalog(): string[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { categoricalColorKeys } from '@scalewing/tokens';
|
|
2
|
+
export const glyphTileSizes = ['sm', 'md', 'lg'];
|
|
3
|
+
const tileSide = {
|
|
4
|
+
sm: 'var(--sw-space-5)',
|
|
5
|
+
md: 'var(--sw-control-xs-min-height)',
|
|
6
|
+
lg: 'var(--sw-space-6)',
|
|
7
|
+
};
|
|
8
|
+
function hueRules() {
|
|
9
|
+
return categoricalColorKeys
|
|
10
|
+
.map((key) => `.sw-glyph-tile-${key} { --sw-glyph-tile-fill: var(--sw-categorical-${key}); }`)
|
|
11
|
+
.join('\n');
|
|
12
|
+
}
|
|
13
|
+
function sizeRules() {
|
|
14
|
+
return Object.entries(tileSide)
|
|
15
|
+
.map(([size, side]) => `.sw-glyph-tile-${size} { height: ${side}; width: ${side}; }`)
|
|
16
|
+
.join('\n');
|
|
17
|
+
}
|
|
18
|
+
/*
|
|
19
|
+
* A rounded square in a categorical fill with the consumer's glyph centred
|
|
20
|
+
* in white, sized to spacing step 4 whatever the glyph's own size. It is
|
|
21
|
+
* decoration: the words beside it carry the meaning. Forced colors drop the
|
|
22
|
+
* fill, so a border keeps the tile's shape and the glyph takes the text
|
|
23
|
+
* color.
|
|
24
|
+
*/
|
|
25
|
+
export function cssGlyphTileClasses() {
|
|
26
|
+
return `.sw-glyph-tile {
|
|
27
|
+
align-items: center;
|
|
28
|
+
background: var(--sw-glyph-tile-fill);
|
|
29
|
+
border-radius: var(--sw-radius-sm);
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
color: var(--sw-categorical-on);
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
flex: none;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sw-glyph-tile > svg {
|
|
38
|
+
flex: none;
|
|
39
|
+
height: var(--sw-space-4);
|
|
40
|
+
width: var(--sw-space-4);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
${hueRules()}
|
|
44
|
+
|
|
45
|
+
${sizeRules()}
|
|
46
|
+
|
|
47
|
+
@media (forced-colors: active) {
|
|
48
|
+
.sw-glyph-tile { border: 1px solid CanvasText; color: CanvasText; }
|
|
49
|
+
}`;
|
|
50
|
+
}
|
|
51
|
+
export function glyphTileClassCatalog() {
|
|
52
|
+
return [
|
|
53
|
+
'sw-glyph-tile',
|
|
54
|
+
...categoricalColorKeys.map((key) => `sw-glyph-tile-${key}`),
|
|
55
|
+
...glyphTileSizes.map((size) => `sw-glyph-tile-${size}`),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { chevronStroke } from './chevron.js';
|
|
3
|
+
import { zIndex } from './stacking.js';
|
|
4
|
+
const label = typographyVariants.label;
|
|
5
|
+
const body = typographyVariants.body;
|
|
6
|
+
/*
|
|
7
|
+
* The trigger: a glass control at the md height with the muted label, the
|
|
8
|
+
* summary and Select's chevron. The panel: a glass popover beside it (as
|
|
9
|
+
* ActionMenu's list), All and Clear over a hairline, then one checkbox row
|
|
10
|
+
* per option at the md height, the whole row its label; a separated row
|
|
11
|
+
* starts after a hairline. Checkboxes reuse Checkbox's generated control.
|
|
12
|
+
*/
|
|
13
|
+
export function cssMultiSelectClasses() {
|
|
14
|
+
return `.sw-multi-select {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
max-width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sw-multi-select-trigger {
|
|
20
|
+
align-items: center;
|
|
21
|
+
appearance: none;
|
|
22
|
+
background-color: var(--sw-glass-fill);
|
|
23
|
+
border: 1px solid var(--sw-color-border);
|
|
24
|
+
border-radius: var(--sw-radius-sm);
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
color: var(--sw-color-text);
|
|
27
|
+
column-gap: var(--sw-space-2);
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
font-family: var(--sw-font-sans);
|
|
31
|
+
font-size: ${label.fontSize}px;
|
|
32
|
+
font-weight: ${label.fontWeight};
|
|
33
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
34
|
+
line-height: ${label.lineHeight}px;
|
|
35
|
+
margin: 0;
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
min-height: var(--sw-control-md-min-height);
|
|
38
|
+
padding-inline: var(--sw-control-md-padding-inline);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sw-multi-select-trigger::after {
|
|
42
|
+
${chevronStroke}
|
|
43
|
+
content: '';
|
|
44
|
+
flex: none;
|
|
45
|
+
margin-inline-start: var(--sw-space-2);
|
|
46
|
+
transform: translateY(-25%) rotate(45deg);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sw-multi-select-trigger:focus-visible {
|
|
50
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
51
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sw-multi-select-trigger:disabled {
|
|
55
|
+
cursor: not-allowed;
|
|
56
|
+
opacity: var(--sw-disabled-opacity);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sw-multi-select-label {
|
|
60
|
+
color: var(--sw-color-muted);
|
|
61
|
+
font-weight: 500;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.sw-multi-select-summary {
|
|
65
|
+
min-width: 0;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
text-overflow: ellipsis;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sw-multi-select-panel {
|
|
72
|
+
background: var(--sw-glass-fill);
|
|
73
|
+
border: 1px solid var(--sw-glass-border);
|
|
74
|
+
border-radius: var(--sw-radius-sm);
|
|
75
|
+
box-shadow: var(--sw-elevation-sm), inset 0 1px 0 var(--sw-glass-specular);
|
|
76
|
+
backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
77
|
+
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
color: var(--sw-color-text);
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
inset: auto;
|
|
83
|
+
margin: 0;
|
|
84
|
+
/* The viewport, not --sw-select-max: the list is short and should show
|
|
85
|
+
whole rather than scroll inside a 22rem cap. */
|
|
86
|
+
max-height: calc(100% - var(--sw-space-2) - var(--sw-space-2));
|
|
87
|
+
max-width: calc(100% - var(--sw-space-2) - var(--sw-space-2));
|
|
88
|
+
min-width: var(--sw-select-max);
|
|
89
|
+
overflow: auto;
|
|
90
|
+
padding: var(--sw-space-1);
|
|
91
|
+
position: fixed;
|
|
92
|
+
width: max-content;
|
|
93
|
+
${zIndex('popup')}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.sw-multi-select-commands {
|
|
97
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: space-between;
|
|
100
|
+
margin-bottom: var(--sw-space-1);
|
|
101
|
+
padding-bottom: var(--sw-space-1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.sw-multi-select-row-separated {
|
|
105
|
+
border-top: 1px solid var(--sw-color-border);
|
|
106
|
+
margin-top: var(--sw-space-1);
|
|
107
|
+
padding-top: var(--sw-space-1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.sw-checkbox-label.sw-multi-select-option {
|
|
111
|
+
border-radius: var(--sw-radius-sm);
|
|
112
|
+
box-sizing: border-box;
|
|
113
|
+
font-family: var(--sw-font-sans);
|
|
114
|
+
font-size: ${label.fontSize}px;
|
|
115
|
+
font-weight: ${body.fontWeight};
|
|
116
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
117
|
+
line-height: ${label.lineHeight}px;
|
|
118
|
+
padding-inline: var(--sw-space-2);
|
|
119
|
+
width: 100%;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.sw-multi-select-option:hover {
|
|
123
|
+
background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.sw-multi-select-icon {
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
flex: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.sw-multi-select-text {
|
|
132
|
+
min-width: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@media (forced-colors: active) {
|
|
136
|
+
.sw-multi-select-trigger::after { border-color: CanvasText; }
|
|
137
|
+
.sw-multi-select-panel { border-color: CanvasText; }
|
|
138
|
+
.sw-multi-select-commands,
|
|
139
|
+
.sw-multi-select-row-separated { border-color: CanvasText; }
|
|
140
|
+
}`;
|
|
141
|
+
}
|
|
142
|
+
export function multiSelectClassCatalog() {
|
|
143
|
+
return [
|
|
144
|
+
'sw-multi-select',
|
|
145
|
+
'sw-multi-select-trigger',
|
|
146
|
+
'sw-multi-select-label',
|
|
147
|
+
'sw-multi-select-summary',
|
|
148
|
+
'sw-multi-select-panel',
|
|
149
|
+
'sw-multi-select-commands',
|
|
150
|
+
'sw-multi-select-row',
|
|
151
|
+
'sw-multi-select-row-separated',
|
|
152
|
+
'sw-multi-select-option',
|
|
153
|
+
'sw-multi-select-icon',
|
|
154
|
+
'sw-multi-select-text',
|
|
155
|
+
];
|
|
156
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** How a section navigation marks its current item. */
|
|
2
|
+
export declare const sectionNavAppearances: readonly ["underline", "filled"];
|
|
3
|
+
export type SectionNavAppearance = (typeof sectionNavAppearances)[number];
|
|
4
|
+
export declare function assertSectionNavAppearance(appearance: unknown): void;
|
|
5
|
+
export declare function cssSectionNavFilledClasses(): string;
|
|
6
|
+
export declare function sectionNavFilledClassCatalog(): string[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
3
|
+
const caption = typographyVariants.caption;
|
|
4
|
+
const label = typographyVariants.label;
|
|
5
|
+
/** How a section navigation marks its current item. */
|
|
6
|
+
export const sectionNavAppearances = ['underline', 'filled'];
|
|
7
|
+
export function assertSectionNavAppearance(appearance) {
|
|
8
|
+
if (!sectionNavAppearances.includes(appearance))
|
|
9
|
+
throw new RangeError(`SectionNav appearance must be one of ${sectionNavAppearances.join(', ')}`);
|
|
10
|
+
}
|
|
11
|
+
/*
|
|
12
|
+
* `filled`: a quiet bar of page buttons, as an area's sub-navigation beside
|
|
13
|
+
* its name. No hairline under the row and no underline or start bar, in
|
|
14
|
+
* the row and the side list; every link is the sm control (32; the 44 px
|
|
15
|
+
* target on a coarse pointer) with the sm radius and the control's
|
|
16
|
+
* padding at weight 500, and the current one takes a soft muted fill in
|
|
17
|
+
* the text colour at the label weight (600), so it reads apart from a
|
|
18
|
+
* hovered link. A 2 px transparent border keeps room for forced colours,
|
|
19
|
+
* where the current link's border turns Highlight.
|
|
20
|
+
*
|
|
21
|
+
* A count after a label (a number waiting there) is a filled pill: the
|
|
22
|
+
* danger colour with its on-colour figures (4.5:1 in every palette, as a
|
|
23
|
+
* danger Button's label), caption size at weight 700, a space-5 less
|
|
24
|
+
* space-1 (20) high and at least as wide. Forced colours draw its edge.
|
|
25
|
+
*/
|
|
26
|
+
export function cssSectionNavFilledClasses() {
|
|
27
|
+
// Two classes, so every filled rule outranks the side list's (one class
|
|
28
|
+
// and :dir), not by source order alone.
|
|
29
|
+
const filled = '.sw-section-nav.sw-section-nav-filled';
|
|
30
|
+
return `${filled} .sw-section-nav-list {
|
|
31
|
+
border-bottom: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* (0,4,0): outranks the underline and the side list's start bar. */
|
|
35
|
+
[data-theme] ${filled} .sw-section-nav-link {
|
|
36
|
+
border: 2px solid transparent;
|
|
37
|
+
border-radius: var(--sw-radius-sm);
|
|
38
|
+
font-weight: 500;
|
|
39
|
+
margin-bottom: 0;
|
|
40
|
+
min-height: var(--sw-control-sm-min-height);
|
|
41
|
+
padding-inline: var(--sw-control-sm-padding-inline);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-theme] ${filled} .sw-section-nav-link[aria-current] {
|
|
45
|
+
background: color-mix(in srgb, var(--sw-color-muted) 18%, transparent);
|
|
46
|
+
border-color: transparent;
|
|
47
|
+
color: var(--sw-color-text);
|
|
48
|
+
font-weight: ${label.fontWeight};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[data-theme] .sw-section-nav-count {
|
|
52
|
+
align-items: center;
|
|
53
|
+
background: var(--sw-color-danger);
|
|
54
|
+
border-radius: var(--sw-radius-pill);
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
color: var(--sw-color-onDanger);
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
font-size: ${caption.fontSize}px;
|
|
59
|
+
font-variant-numeric: tabular-nums;
|
|
60
|
+
font-weight: 700;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
63
|
+
line-height: 1;
|
|
64
|
+
min-height: calc(var(--sw-space-5) - var(--sw-space-1));
|
|
65
|
+
min-width: calc(var(--sw-space-5) - var(--sw-space-1));
|
|
66
|
+
padding-inline: var(--sw-space-1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media ${coarsePointerQuery} {
|
|
70
|
+
[data-theme] ${filled} .sw-section-nav-link { min-height: ${touchTarget}; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@media (forced-colors: active) {
|
|
74
|
+
[data-theme] ${filled} .sw-section-nav-link[aria-current] { border-color: Highlight; }
|
|
75
|
+
[data-theme] .sw-section-nav-count { border: 1px solid CanvasText; }
|
|
76
|
+
}`;
|
|
77
|
+
}
|
|
78
|
+
export function sectionNavFilledClassCatalog() {
|
|
79
|
+
return ['sw-section-nav-filled', 'sw-section-nav-count'];
|
|
80
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { breakpointQuery, breakpoints, } from './breakpoints.js';
|
|
3
|
+
import { cssSectionNavFilledClasses, sectionNavFilledClassCatalog, } from './css-section-nav-filled.js';
|
|
3
4
|
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
4
5
|
const label = typographyVariants.label;
|
|
5
6
|
export function sectionNavVerticalClass(breakpoint) {
|
|
@@ -100,6 +101,8 @@ export function cssSectionNavClasses() {
|
|
|
100
101
|
|
|
101
102
|
${verticalRules()}
|
|
102
103
|
|
|
104
|
+
${cssSectionNavFilledClasses()}
|
|
105
|
+
|
|
103
106
|
@media ${coarsePointerQuery} {
|
|
104
107
|
[data-theme] .sw-section-nav-link { min-height: ${touchTarget}; }
|
|
105
108
|
}
|
|
@@ -116,5 +119,6 @@ export function sectionNavClassCatalog() {
|
|
|
116
119
|
'sw-section-nav-link',
|
|
117
120
|
'sw-section-nav-icon',
|
|
118
121
|
...breakpoints.map(sectionNavVerticalClass),
|
|
122
|
+
...sectionNavFilledClassCatalog(),
|
|
119
123
|
];
|
|
120
124
|
}
|
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
import { trackInset, typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
const body = typographyVariants.body;
|
|
3
3
|
const data = typographyVariants.data;
|
|
4
|
+
const label = typographyVariants.label;
|
|
5
|
+
/*
|
|
6
|
+
* Items with a mark (a glyph tile before the label) centre the mark and
|
|
7
|
+
* the label a space-2 gap apart; the mark keeps its size. On the compact
|
|
8
|
+
* track every item also takes the md control height and label type, so
|
|
9
|
+
* the row stays even; the track is the subtle fill (two classes, so
|
|
10
|
+
* reduced transparency keeps it) and the selected item is the surface
|
|
11
|
+
* raised by the small elevation, so the choice shows on a white canvas
|
|
12
|
+
* too. A filled track keeps its own look and only gains the marks. The
|
|
13
|
+
* error outline is restated after these rules so an icon track keeps it.
|
|
14
|
+
* In forced colours the selected item is outlined in Highlight; a focused
|
|
15
|
+
* selected item keeps that outline, offset by the focus ring's offset.
|
|
16
|
+
*/
|
|
17
|
+
function iconRules() {
|
|
18
|
+
const compact = '.sw-segmented-with-icons:not(.sw-segmented-filled)';
|
|
19
|
+
return `.sw-segmented${compact} {
|
|
20
|
+
background: var(--sw-color-subtle);
|
|
21
|
+
border-color: transparent;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
${compact} .sw-segmented-item-selected {
|
|
25
|
+
box-shadow: var(--sw-elevation-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sw-segmented-with-icons .sw-segmented-item {
|
|
29
|
+
align-items: center;
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
gap: var(--sw-space-2);
|
|
32
|
+
justify-content: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
${compact} .sw-segmented-item {
|
|
36
|
+
font-size: ${label.fontSize}px;
|
|
37
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
38
|
+
line-height: ${label.lineHeight}px;
|
|
39
|
+
min-height: var(--sw-control-md-min-height);
|
|
40
|
+
padding-inline: var(--sw-space-2) var(--sw-space-3);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sw-segmented-item-icon {
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
flex: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sw-segmented.sw-segmented-with-icons[aria-invalid='true'] {
|
|
49
|
+
border-color: var(--sw-color-danger);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@media (forced-colors: active) {
|
|
53
|
+
.sw-segmented${compact} { border-color: CanvasText; }
|
|
54
|
+
${compact} .sw-segmented-item-selected { outline: 2px solid Highlight; }
|
|
55
|
+
.sw-segmented.sw-segmented-with-icons[aria-invalid='true'] { border-color: Mark; }
|
|
56
|
+
}`;
|
|
57
|
+
}
|
|
4
58
|
/*
|
|
5
59
|
* The control and its error message. The wrapper takes the track's place in
|
|
6
60
|
* the layout: inline beside text and stretched in a Stack (compact), block
|
|
@@ -106,6 +160,8 @@ export function cssSegmentedClasses() {
|
|
|
106
160
|
color: var(--sw-color-onAccent);
|
|
107
161
|
}
|
|
108
162
|
|
|
163
|
+
${iconRules()}
|
|
164
|
+
|
|
109
165
|
.sw-segmented-disabled {
|
|
110
166
|
opacity: var(--sw-disabled-opacity);
|
|
111
167
|
}
|
|
@@ -122,6 +178,8 @@ export function segmentedClassCatalog() {
|
|
|
122
178
|
'sw-segmented-item',
|
|
123
179
|
'sw-segmented-item-selected',
|
|
124
180
|
'sw-segmented-filled',
|
|
181
|
+
'sw-segmented-with-icons',
|
|
182
|
+
'sw-segmented-item-icon',
|
|
125
183
|
'sw-segmented-disabled',
|
|
126
184
|
];
|
|
127
185
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { zIndex } from './stacking.js';
|
|
2
|
+
/*
|
|
3
|
+
* A table footer holds a total: its cells at weight 600 under a heavier
|
|
4
|
+
* rule, a 2 px line in the border colour across the top of its first row.
|
|
5
|
+
* The line is each cell's ::before (absolutely placed, a border, so forced
|
|
6
|
+
* colours keep it), not the cell's own collapsed border, which a sticky
|
|
7
|
+
* cell would leave behind as it moved.
|
|
8
|
+
*
|
|
9
|
+
* A sticky footer pins its cells to the bottom edge on the header's glass
|
|
10
|
+
* (solid surface under reduced transparency, the system canvas in forced
|
|
11
|
+
* colours, as the pinned header cells), on the `stickyFooter` layer: over
|
|
12
|
+
* the cells and the pinned header cells, under the ActionBar, which covers
|
|
13
|
+
* it, so a page does not pair the two. A table's region is a scroll box,
|
|
14
|
+
* which would pin it to the region's own bottom; while the table fits its
|
|
15
|
+
* width (`sw-scroll-fits`) a region that holds a sticky footer stops being
|
|
16
|
+
* one, so the footer pins to the page's bottom and the sticky header to
|
|
17
|
+
* its top, under any sticky top chrome (an AppHeader covers it). A table
|
|
18
|
+
* wider than its region scrolls sideways in it and pins to the region.
|
|
19
|
+
*/
|
|
20
|
+
export function cssTableFooterClasses() {
|
|
21
|
+
return `.sw-table tfoot :is(th, td) {
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
position: relative;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sw-table tfoot > tr:first-child > :is(th, td)::before {
|
|
27
|
+
border-top: 2px solid var(--sw-color-border);
|
|
28
|
+
content: '';
|
|
29
|
+
/* Over the last body row's 1 px border, so the rule reads 2 px. */
|
|
30
|
+
inset-block-start: -1px;
|
|
31
|
+
inset-inline: 0;
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
position: absolute;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sw-table .sw-table-footer-sticky :is(th, td) {
|
|
37
|
+
background: var(--sw-glass-fill);
|
|
38
|
+
backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
39
|
+
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
40
|
+
bottom: 0;
|
|
41
|
+
position: sticky;
|
|
42
|
+
${zIndex('stickyFooter')}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sw-table-wrap.sw-scroll-fits:has(> .sw-table > .sw-table-footer-sticky) {
|
|
46
|
+
overflow: visible;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media (forced-colors: active) {
|
|
50
|
+
.sw-table tfoot > tr:first-child > :is(th, td)::before { border-color: CanvasText; }
|
|
51
|
+
.sw-table .sw-table-footer-sticky :is(th, td),
|
|
52
|
+
.sw-table-sticky thead th { background: Canvas; backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
53
|
+
}`;
|
|
54
|
+
}
|
|
55
|
+
export function tableFooterClassCatalog() {
|
|
56
|
+
return ['sw-table-footer-sticky', 'sw-scroll-fits'];
|
|
57
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The colours a cell's text can take: a signed figure or a quiet value. */
|
|
2
|
+
export declare const tableCellTones: readonly ["success", "danger", "muted", "accent"];
|
|
3
|
+
export type TableCellTone = (typeof tableCellTones)[number];
|
|
4
|
+
/** A row's place in the table: a row of its own, or one under the row above. */
|
|
5
|
+
export declare const tableRowVariants: readonly ["default", "sub"];
|
|
6
|
+
export type TableRowVariant = (typeof tableRowVariants)[number];
|
|
7
|
+
export declare function assertTableCellTone(tone: unknown): void;
|
|
8
|
+
export declare function assertTableRowVariant(variant: unknown): void;
|
|
9
|
+
export declare function cssTableLedgerClasses(): string;
|
|
10
|
+
export declare function tableLedgerClassCatalog(): string[];
|