@scalewing/react 1.29.0 → 1.31.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 +54 -0
- package/README.md +13 -3
- 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/Combobox.d.ts +14 -0
- package/dist/components/Combobox.js +125 -0
- package/dist/components/Field.d.ts +17 -0
- package/dist/components/Field.js +54 -5
- package/dist/components/FilterChips.d.ts +17 -1
- package/dist/components/FilterChips.js +11 -6
- 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/Table.d.ts +49 -5
- package/dist/components/Table.js +11 -4
- package/dist/components/action-menu/ActionMenuList.js +7 -7
- package/dist/components/combobox/ComboboxPopup.d.ts +25 -0
- package/dist/components/combobox/ComboboxPopup.js +68 -0
- package/dist/components/combobox/entries.d.ts +18 -0
- package/dist/components/combobox/entries.js +34 -0
- package/dist/components/combobox/types.d.ts +63 -0
- package/dist/components/combobox/types.js +1 -0
- package/dist/components/combobox/validate.d.ts +3 -0
- package/dist/components/combobox/validate.js +44 -0
- package/dist/components/field/SearchFrame.d.ts +16 -0
- package/dist/components/field/SearchFrame.js +32 -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-combobox.d.ts +2 -0
- package/dist/css/css-combobox.js +135 -0
- package/dist/css/css-components.js +4 -0
- package/dist/css/css-field-search.d.ts +2 -0
- package/dist/css/css-field-search.js +118 -0
- package/dist/css/css-field.js +4 -0
- package/dist/css/css-filter-chips.js +26 -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-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/stacking.d.ts +9 -3
- package/dist/css/stacking.js +28 -8
- package/dist/css/stylesheet.js +2 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/styles.css +483 -7
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
import { type TableRowVariant } from '../../css/css-table-ledger.js';
|
|
3
|
+
export type { TableRowVariant };
|
|
4
|
+
export type TableRowProps = HTMLAttributes<HTMLTableRowElement> & {
|
|
5
|
+
selected?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* `sub` sets the row under the one above it, as an item of a batch: its
|
|
8
|
+
* row header (`TableCell as="th" scope="row"`) is indented with a guide
|
|
9
|
+
* bar at the indent; a sub row without a row header is not indented.
|
|
10
|
+
* `default` is a row of its own.
|
|
11
|
+
*/
|
|
12
|
+
variant?: TableRowVariant;
|
|
13
|
+
/**
|
|
14
|
+
* A voided row kept on the record: the subtle fill and the text muted
|
|
15
|
+
* and struck through, toned cells included. A line-through is not read
|
|
16
|
+
* aloud, so say why in the row, such as a `Badge` "Voided" in the
|
|
17
|
+
* title's line (a Badge in a line of text is not struck; one that is a
|
|
18
|
+
* flex item, a direct child of `Inline`, is). Works with either variant.
|
|
19
|
+
*/
|
|
20
|
+
struck?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const TableRow: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLTableRowElement> & {
|
|
23
|
+
selected?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* `sub` sets the row under the one above it, as an item of a batch: its
|
|
26
|
+
* row header (`TableCell as="th" scope="row"`) is indented with a guide
|
|
27
|
+
* bar at the indent; a sub row without a row header is not indented.
|
|
28
|
+
* `default` is a row of its own.
|
|
29
|
+
*/
|
|
30
|
+
variant?: TableRowVariant;
|
|
31
|
+
/**
|
|
32
|
+
* A voided row kept on the record: the subtle fill and the text muted
|
|
33
|
+
* and struck through, toned cells included. A line-through is not read
|
|
34
|
+
* aloud, so say why in the row, such as a `Badge` "Voided" in the
|
|
35
|
+
* title's line (a Badge in a line of text is not struck; one that is a
|
|
36
|
+
* flex item, a direct child of `Inline`, is). Works with either variant.
|
|
37
|
+
*/
|
|
38
|
+
struck?: boolean;
|
|
39
|
+
} & import("react").RefAttributes<HTMLTableRowElement>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { cx } from '../../class-names.js';
|
|
4
|
+
import { assertTableRowVariant, } from '../../css/css-table-ledger.js';
|
|
5
|
+
export const TableRow = forwardRef(function TableRow({ className, selected = false, struck = false, variant = 'default', ...rest }, ref) {
|
|
6
|
+
assertTableRowVariant(variant);
|
|
7
|
+
return (_jsx("tr", { ref: ref, "aria-selected": selected || undefined, className: cx(selected && 'sw-table-row-selected', variant === 'sub' && 'sw-table-row-sub', struck && 'sw-table-row-struck', className), ...rest }));
|
|
8
|
+
});
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { type RefObject } from 'react';
|
|
2
2
|
import { type ScrollOverflow } from '../scroll-overflow.js';
|
|
3
|
+
/** A box's overflow, and whether it has been measured yet. */
|
|
4
|
+
export type MeasuredScrollOverflow = ScrollOverflow & {
|
|
5
|
+
measured: boolean;
|
|
6
|
+
};
|
|
3
7
|
/**
|
|
4
8
|
* Follows which inline edges of a scroll box have more content past them:
|
|
5
9
|
* on mount, on every scroll, and whenever the box or its content changes
|
|
6
|
-
* size. Before mount (and on the server) it reports no overflow
|
|
7
|
-
* box renders as it always has until it is
|
|
10
|
+
* size. Before mount (and on the server) it reports no overflow and
|
|
11
|
+
* `measured: false`, so the box renders as it always has until it is
|
|
12
|
+
* measured; a measured box with neither edge set fits its content.
|
|
8
13
|
*/
|
|
9
|
-
export declare function useScrollOverflow(ref: RefObject<HTMLElement | null>):
|
|
14
|
+
export declare function useScrollOverflow(ref: RefObject<HTMLElement | null>): MeasuredScrollOverflow;
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { noScrollOverflow, sameScrollOverflow, scrollOverflow, } from '../scroll-overflow.js';
|
|
4
|
+
const unmeasured = {
|
|
5
|
+
...noScrollOverflow,
|
|
6
|
+
measured: false,
|
|
7
|
+
};
|
|
4
8
|
/**
|
|
5
9
|
* Follows which inline edges of a scroll box have more content past them:
|
|
6
10
|
* on mount, on every scroll, and whenever the box or its content changes
|
|
7
|
-
* size. Before mount (and on the server) it reports no overflow
|
|
8
|
-
* box renders as it always has until it is
|
|
11
|
+
* size. Before mount (and on the server) it reports no overflow and
|
|
12
|
+
* `measured: false`, so the box renders as it always has until it is
|
|
13
|
+
* measured; a measured box with neither edge set fits its content.
|
|
9
14
|
*/
|
|
10
15
|
export function useScrollOverflow(ref) {
|
|
11
|
-
const [overflow, setOverflow] = useState(
|
|
16
|
+
const [overflow, setOverflow] = useState(unmeasured);
|
|
12
17
|
useEffect(() => {
|
|
13
18
|
const box = ref.current;
|
|
14
19
|
if (!box)
|
|
15
20
|
return;
|
|
21
|
+
// The metrics hold on a box that does not scroll (a fitting table
|
|
22
|
+
// region set to overflow: visible): scrollWidth still reports content
|
|
23
|
+
// past its edge, so the box turns back into a scroll box as it widens.
|
|
24
|
+
// Keep it that way; a scroll-container-only check would pin it open.
|
|
16
25
|
function measure() {
|
|
17
26
|
if (!box)
|
|
18
27
|
return;
|
|
19
28
|
const next = scrollOverflow(box.scrollLeft, box.scrollWidth, box.clientWidth);
|
|
20
|
-
setOverflow((current) => sameScrollOverflow(current, next)
|
|
29
|
+
setOverflow((current) => current.measured && sameScrollOverflow(current, next)
|
|
30
|
+
? current
|
|
31
|
+
: { ...next, measured: true });
|
|
21
32
|
}
|
|
22
33
|
measure();
|
|
23
34
|
box.addEventListener('scroll', measure, { passive: true });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { chevronStroke } from './chevron.js';
|
|
2
3
|
import { zIndex } from './stacking.js';
|
|
3
4
|
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
4
5
|
const caption = typographyVariants.caption;
|
|
@@ -114,6 +115,43 @@ export function cssActionMenuClasses() {
|
|
|
114
115
|
color: var(--sw-color-danger);
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/*
|
|
119
|
+
* A command's detail: its current value at the inline end in the muted
|
|
120
|
+
* colour, then a chevron pointing to the inline end (mirrored right to
|
|
121
|
+
* left). A long value wraps between words, never inside one (the command
|
|
122
|
+
* itself may break anywhere), and the chevron stays last.
|
|
123
|
+
*/
|
|
124
|
+
.sw-action-menu-detail {
|
|
125
|
+
align-items: center;
|
|
126
|
+
color: var(--sw-color-muted);
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
gap: var(--sw-space-2);
|
|
129
|
+
margin-inline-start: auto;
|
|
130
|
+
overflow-wrap: normal;
|
|
131
|
+
padding-inline-start: var(--sw-space-3);
|
|
132
|
+
text-align: end;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.sw-action-menu-chevron {
|
|
136
|
+
${chevronStroke}
|
|
137
|
+
flex: none;
|
|
138
|
+
transform: rotate(-45deg);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.sw-action-menu-chevron:dir(rtl) {
|
|
142
|
+
transform: rotate(135deg);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* A hairline that sets a command apart from the ones above. */
|
|
146
|
+
.sw-action-menu-separator {
|
|
147
|
+
border-top: 1px solid var(--sw-color-border);
|
|
148
|
+
margin-block: var(--sw-space-1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (forced-colors: active) {
|
|
152
|
+
.sw-action-menu-chevron { border-color: CanvasText; }
|
|
153
|
+
}
|
|
154
|
+
|
|
117
155
|
/* A coarse pointer gets the full touch target: the trigger at least square, and every command as tall. */
|
|
118
156
|
@media ${coarsePointerQuery} {
|
|
119
157
|
.sw-action-menu-trigger {
|
|
@@ -135,5 +173,8 @@ export function actionMenuClassCatalog() {
|
|
|
135
173
|
'sw-action-menu-items',
|
|
136
174
|
'sw-action-menu-item',
|
|
137
175
|
'sw-action-menu-item-danger',
|
|
176
|
+
'sw-action-menu-detail',
|
|
177
|
+
'sw-action-menu-chevron',
|
|
178
|
+
'sw-action-menu-separator',
|
|
138
179
|
];
|
|
139
180
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
2
|
+
import { joinedZIndex } from './stacking.js';
|
|
3
|
+
/*
|
|
4
|
+
* An attached ButtonGroup is one joined control, such as Previous, Today
|
|
5
|
+
* and Next: its direct Button children side by side with no gap and one
|
|
6
|
+
* height, their borders overlapping into single dividers, the inner
|
|
7
|
+
* corners square and the outer ones the sm radius. It keeps its row on a
|
|
8
|
+
* phone (it never stacks) and sizes to its buttons, even in a Stack that
|
|
9
|
+
* would stretch it, so `justify` does not apply.
|
|
10
|
+
*
|
|
11
|
+
* A borderless variant (primary, tertiary, ghost, danger) draws its
|
|
12
|
+
* divider in its own text colour at low strength, so a joined row of them
|
|
13
|
+
* still reads as separate buttons; forced colours draw every border.
|
|
14
|
+
*
|
|
15
|
+
* The group is its own stacking context: a pressed button's ring paints
|
|
16
|
+
* over its neighbours and the focused button's standard outer ring over
|
|
17
|
+
* both, so neither is covered by the next button.
|
|
18
|
+
*/
|
|
19
|
+
export function cssButtonGroupAttachedClasses() {
|
|
20
|
+
const group = '.sw-button-group.sw-button-group-attached';
|
|
21
|
+
return `${group} {
|
|
22
|
+
align-items: stretch;
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
flex-wrap: nowrap;
|
|
25
|
+
gap: 0;
|
|
26
|
+
isolation: isolate;
|
|
27
|
+
max-width: 100%;
|
|
28
|
+
width: fit-content;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
${group} > .sw-button {
|
|
32
|
+
border-radius: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
${group} > .sw-button + .sw-button {
|
|
36
|
+
margin-inline-start: -1px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
${group} > .sw-button:first-child {
|
|
40
|
+
border-end-start-radius: var(--sw-radius-sm);
|
|
41
|
+
border-start-start-radius: var(--sw-radius-sm);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
${group} > .sw-button:last-child {
|
|
45
|
+
border-end-end-radius: var(--sw-radius-sm);
|
|
46
|
+
border-start-end-radius: var(--sw-radius-sm);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
${group} > .sw-button + .sw-button:not(.sw-button-secondary) {
|
|
50
|
+
border-inline-start-color: color-mix(in srgb, currentColor 30%, transparent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
${group} > .sw-button[aria-pressed='true'] {
|
|
54
|
+
position: relative;
|
|
55
|
+
${joinedZIndex('pressed')}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
${group} > .sw-button:focus-visible {
|
|
59
|
+
position: relative;
|
|
60
|
+
${joinedZIndex('focused')}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
64
|
+
${group} { align-items: stretch; flex-direction: row; }
|
|
65
|
+
${group} > * { width: auto; }
|
|
66
|
+
}`;
|
|
67
|
+
}
|
|
68
|
+
export function buttonGroupAttachedClassCatalog() {
|
|
69
|
+
return ['sw-button-group-attached'];
|
|
70
|
+
}
|
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
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { zIndex } from './stacking.js';
|
|
3
|
+
const caption = typographyVariants.caption;
|
|
4
|
+
const label = typographyVariants.label;
|
|
5
|
+
/*
|
|
6
|
+
* The combobox input is a Field's input. Its list is a glass popover on
|
|
7
|
+
* the popup layer (as Select's list), at least as wide as the input: a
|
|
8
|
+
* caption heading per group, then options at the md control height with
|
|
9
|
+
* the label, a badge and a muted description on one wrapping line. The
|
|
10
|
+
* active option (the one aria-activedescendant names) takes the soft
|
|
11
|
+
* muted fill; a hairline parts each option from the one before it, as
|
|
12
|
+
* the boards draw it; a disabled option is muted and not pressable; the action
|
|
13
|
+
* ends the list in the accent colour over a hairline. The empty text and
|
|
14
|
+
* the note are muted captions under the list.
|
|
15
|
+
*/
|
|
16
|
+
export function cssComboboxClasses() {
|
|
17
|
+
return `.sw-combobox {
|
|
18
|
+
max-width: 100%;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sw-combobox-popup {
|
|
23
|
+
background: var(--sw-glass-fill);
|
|
24
|
+
border: 1px solid var(--sw-glass-border);
|
|
25
|
+
border-radius: var(--sw-radius-sm);
|
|
26
|
+
box-shadow: var(--sw-elevation-sm), inset 0 1px 0 var(--sw-glass-specular);
|
|
27
|
+
backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
28
|
+
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
color: var(--sw-color-text);
|
|
31
|
+
inset: auto;
|
|
32
|
+
margin: 0;
|
|
33
|
+
max-height: min(var(--sw-select-max), calc(100% - var(--sw-space-2) - var(--sw-space-2)));
|
|
34
|
+
max-width: calc(100% - var(--sw-space-2) - var(--sw-space-2));
|
|
35
|
+
overflow: auto;
|
|
36
|
+
padding: var(--sw-space-1);
|
|
37
|
+
position: fixed;
|
|
38
|
+
${zIndex('popup')}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sw-combobox-heading {
|
|
42
|
+
color: var(--sw-color-muted);
|
|
43
|
+
font-family: var(--sw-font-sans);
|
|
44
|
+
font-size: ${caption.fontSize}px;
|
|
45
|
+
font-weight: ${label.fontWeight};
|
|
46
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
47
|
+
line-height: ${caption.lineHeight}px;
|
|
48
|
+
padding: var(--sw-space-2) var(--sw-space-3) var(--sw-space-1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.sw-combobox-option {
|
|
52
|
+
align-items: center;
|
|
53
|
+
border-radius: var(--sw-radius-sm);
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
column-gap: var(--sw-space-2);
|
|
56
|
+
cursor: pointer;
|
|
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-height: var(--sw-control-md-min-height);
|
|
64
|
+
overflow-wrap: anywhere;
|
|
65
|
+
padding: var(--sw-space-1) var(--sw-space-3);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sw-combobox-option + .sw-combobox-option {
|
|
69
|
+
border-top: 1px solid var(--sw-color-border);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.sw-combobox-label {
|
|
73
|
+
font-weight: ${label.fontWeight};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.sw-combobox-description {
|
|
77
|
+
color: var(--sw-color-muted);
|
|
78
|
+
font-size: ${caption.fontSize}px;
|
|
79
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
80
|
+
line-height: ${caption.lineHeight}px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sw-combobox-option[aria-selected='true'] {
|
|
84
|
+
background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sw-combobox-option[aria-disabled='true'] {
|
|
88
|
+
cursor: not-allowed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.sw-combobox-option[aria-disabled='true'] .sw-combobox-label {
|
|
92
|
+
color: var(--sw-color-muted);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.sw-combobox-action {
|
|
96
|
+
border-top: 1px solid var(--sw-color-border);
|
|
97
|
+
color: var(--sw-color-accent);
|
|
98
|
+
margin-top: var(--sw-space-1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.sw-combobox-empty,
|
|
102
|
+
.sw-combobox-note {
|
|
103
|
+
color: var(--sw-color-muted);
|
|
104
|
+
font-family: var(--sw-font-sans);
|
|
105
|
+
font-size: ${caption.fontSize}px;
|
|
106
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
107
|
+
line-height: ${caption.lineHeight}px;
|
|
108
|
+
padding: var(--sw-space-2) var(--sw-space-3);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (forced-colors: active) {
|
|
112
|
+
.sw-combobox-popup { border-color: CanvasText; }
|
|
113
|
+
.sw-combobox-option[aria-selected='true'] { background: Highlight; color: HighlightText; forced-color-adjust: none; }
|
|
114
|
+
.sw-combobox-option[aria-selected='true'] .sw-combobox-description { color: HighlightText; }
|
|
115
|
+
.sw-combobox-option[aria-selected='true'] .sw-badge { forced-color-adjust: auto; }
|
|
116
|
+
.sw-combobox-option[aria-disabled='true'] .sw-combobox-label { color: GrayText; }
|
|
117
|
+
.sw-combobox-option[aria-selected='true'][aria-disabled='true'] .sw-combobox-label { color: HighlightText; }
|
|
118
|
+
.sw-combobox-option + .sw-combobox-option { border-color: CanvasText; }
|
|
119
|
+
.sw-combobox-action { border-color: CanvasText; }
|
|
120
|
+
}`;
|
|
121
|
+
}
|
|
122
|
+
export function comboboxClassCatalog() {
|
|
123
|
+
return [
|
|
124
|
+
'sw-combobox',
|
|
125
|
+
'sw-combobox-popup',
|
|
126
|
+
'sw-combobox-list',
|
|
127
|
+
'sw-combobox-heading',
|
|
128
|
+
'sw-combobox-option',
|
|
129
|
+
'sw-combobox-label',
|
|
130
|
+
'sw-combobox-description',
|
|
131
|
+
'sw-combobox-action',
|
|
132
|
+
'sw-combobox-empty',
|
|
133
|
+
'sw-combobox-note',
|
|
134
|
+
];
|
|
135
|
+
}
|
|
@@ -14,6 +14,7 @@ import { cssTableClasses } from './css-table.js';
|
|
|
14
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
|
+
import { cssComboboxClasses } from './css-combobox.js';
|
|
17
18
|
import { cssSelectClasses } from './css-select.js';
|
|
18
19
|
import { cssMultiSelectClasses } from './css-multi-select.js';
|
|
19
20
|
import { cssSplitClasses } from './css-split.js';
|
|
@@ -55,6 +56,7 @@ function reducedTransparencyRules() {
|
|
|
55
56
|
.sw-badge-neutral,
|
|
56
57
|
.sw-segmented,
|
|
57
58
|
.sw-table-sticky thead th,
|
|
59
|
+
.sw-table .sw-table-footer-sticky :is(th, td),
|
|
58
60
|
.sw-denomination-strip .sw-denomination-label,
|
|
59
61
|
.sw-denomination-strip thead .sw-denomination-corner:first-child,
|
|
60
62
|
.sw-bar-chart-track,
|
|
@@ -63,6 +65,7 @@ function reducedTransparencyRules() {
|
|
|
63
65
|
.sw-select-list,
|
|
64
66
|
.sw-action-menu-list,
|
|
65
67
|
.sw-multi-select-panel,
|
|
68
|
+
.sw-combobox-popup,
|
|
66
69
|
.sw-date-field-calendar,
|
|
67
70
|
.sw-toast {
|
|
68
71
|
background: var(--sw-color-surface);
|
|
@@ -147,6 +150,7 @@ ${cssSelectClasses()}
|
|
|
147
150
|
|
|
148
151
|
${cssActionMenuClasses()}
|
|
149
152
|
|
|
153
|
+
${cssComboboxClasses()}
|
|
150
154
|
${cssMultiSelectClasses()}
|
|
151
155
|
|
|
152
156
|
${cssActionBarClasses()}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
2
|
+
/*
|
|
3
|
+
* A search field is the adorned frame (the control surface, its focus
|
|
4
|
+
* ring, its danger and disabled looks) with the consumer's glyph in the
|
|
5
|
+
* muted colour at its start, a space-2 gap, and a clear button at its end:
|
|
6
|
+
* a muted disc with a cross in the surface colour, centred in an xs control
|
|
7
|
+
* square (the 44 px target on a coarse pointer) that reaches the frame's
|
|
8
|
+
* edge. The browser's own clear button and search glyph are hidden, so the
|
|
9
|
+
* field shows one of each.
|
|
10
|
+
*/
|
|
11
|
+
export function cssFieldSearchClasses() {
|
|
12
|
+
return `.sw-field-adorned.sw-field-search {
|
|
13
|
+
gap: var(--sw-space-2);
|
|
14
|
+
padding-inline-end: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sw-field-search-leading {
|
|
18
|
+
color: var(--sw-color-muted);
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
flex: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
[data-theme] .sw-field-search > input[type='search'] {
|
|
24
|
+
appearance: none;
|
|
25
|
+
-webkit-appearance: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sw-field-search > input[type='search']::-webkit-search-cancel-button,
|
|
29
|
+
.sw-field-search > input[type='search']::-webkit-search-decoration {
|
|
30
|
+
-webkit-appearance: none;
|
|
31
|
+
appearance: none;
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sw-field-search > input:last-child {
|
|
36
|
+
margin-inline-end: var(--sw-control-md-padding-inline);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.sw-field-search-clear {
|
|
40
|
+
align-items: center;
|
|
41
|
+
align-self: stretch;
|
|
42
|
+
appearance: none;
|
|
43
|
+
background: transparent;
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: var(--sw-radius-sm);
|
|
46
|
+
color: inherit;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
flex: none;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
margin: 0;
|
|
52
|
+
min-width: var(--sw-control-xs-min-height);
|
|
53
|
+
padding: 0 var(--sw-space-2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.sw-field-search-clear:focus-visible {
|
|
57
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
58
|
+
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* One ring at a time: the clear button's own while it has focus. */
|
|
62
|
+
.sw-field-search:has(.sw-field-search-clear:focus-visible) {
|
|
63
|
+
outline: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sw-field-search-clear:hover .sw-field-search-cross {
|
|
67
|
+
background: var(--sw-color-text);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.sw-field-xs .sw-field-search > input:last-child {
|
|
71
|
+
margin-inline-end: var(--sw-control-xs-padding-inline);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sw-field-search-cross {
|
|
75
|
+
background: var(--sw-color-muted);
|
|
76
|
+
border-radius: var(--sw-radius-pill);
|
|
77
|
+
display: inline-block;
|
|
78
|
+
height: var(--sw-space-4);
|
|
79
|
+
position: relative;
|
|
80
|
+
width: var(--sw-space-4);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sw-field-search-cross::before,
|
|
84
|
+
.sw-field-search-cross::after {
|
|
85
|
+
background: var(--sw-color-surface);
|
|
86
|
+
border-radius: 1px;
|
|
87
|
+
content: '';
|
|
88
|
+
height: 2px;
|
|
89
|
+
inset: 0;
|
|
90
|
+
margin: auto;
|
|
91
|
+
position: absolute;
|
|
92
|
+
width: calc(var(--sw-space-2) + 2px);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.sw-field-search-cross::before { transform: rotate(45deg); }
|
|
96
|
+
.sw-field-search-cross::after { transform: rotate(-45deg); }
|
|
97
|
+
|
|
98
|
+
@media ${coarsePointerQuery} {
|
|
99
|
+
.sw-field-search-clear { min-width: ${touchTarget}; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media (forced-colors: active) {
|
|
103
|
+
.sw-field-search-leading { color: FieldText; }
|
|
104
|
+
/* Backgrounds are forced in this mode; the disc keeps its own system colours. */
|
|
105
|
+
.sw-field-search-cross { background: ButtonText; forced-color-adjust: none; }
|
|
106
|
+
.sw-field-search-cross::before,
|
|
107
|
+
.sw-field-search-cross::after { background: ButtonFace; }
|
|
108
|
+
}`;
|
|
109
|
+
}
|
|
110
|
+
export function fieldSearchClassCatalog() {
|
|
111
|
+
return [
|
|
112
|
+
'sw-field-search-field',
|
|
113
|
+
'sw-field-search',
|
|
114
|
+
'sw-field-search-leading',
|
|
115
|
+
'sw-field-search-clear',
|
|
116
|
+
'sw-field-search-cross',
|
|
117
|
+
];
|
|
118
|
+
}
|
package/dist/css/css-field.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { controlSurface, disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
|
|
3
3
|
import { cssFieldChangedClasses } from './css-field-changed.js';
|
|
4
|
+
import { cssFieldSearchClasses, fieldSearchClassCatalog, } from './css-field-search.js';
|
|
4
5
|
const caption = typographyVariants.caption;
|
|
5
6
|
/*
|
|
6
7
|
* A prefix or suffix sits inside the control frame: the wrapper draws the
|
|
@@ -83,6 +84,8 @@ export function cssFieldClasses() {
|
|
|
83
84
|
|
|
84
85
|
${adornmentRules()}
|
|
85
86
|
|
|
87
|
+
${cssFieldSearchClasses()}
|
|
88
|
+
|
|
86
89
|
${cssFieldChangedClasses()}
|
|
87
90
|
|
|
88
91
|
@media (forced-colors: active) {
|
|
@@ -106,5 +109,6 @@ export function fieldClassCatalog() {
|
|
|
106
109
|
'sw-field-adorned',
|
|
107
110
|
'sw-field-prefix',
|
|
108
111
|
'sw-field-suffix',
|
|
112
|
+
...fieldSearchClassCatalog(),
|
|
109
113
|
];
|
|
110
114
|
}
|
|
@@ -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,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[];
|