@scalewing/react 1.23.0 → 1.24.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 +8 -0
- package/README.md +2 -2
- package/dist/components/BarChart.d.ts +11 -0
- package/dist/components/BarChart.js +9 -1
- package/dist/components/Field.d.ts +4 -1
- package/dist/css/css-chart.d.ts +7 -0
- package/dist/css/css-chart.js +32 -1
- package/dist/css/css-chrome.js +2 -3
- package/dist/css/css-document.d.ts +14 -0
- package/dist/css/css-document.js +21 -7
- package/dist/css/css-field-changed.d.ts +13 -0
- package/dist/css/css-field-changed.js +64 -0
- package/dist/css/css-field.js +3 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/styles.css +52 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.24.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e6ed0d6: `Field` `changed` now also tints the control's fill (`docs/requests/teisoro-field-validation.md`, 2026-10-09 follow-up, Teisoro F-006-S11 task 1890), so a changed field no longer reads as the focused one: beside the accent border it keeps, the native control or the adorned frame takes the accent at 12 % as a background image over its own fill, which focus never draws. The value keeps 4.5:1 or more on the tint in every palette and scheme (6.74:1 at the lowest); the tint is on the text controls and select only (not a range, color or file input); while a placeholder shows the fill stays plain; on the tint an adorned field's prefix and suffix take the text color; a select keeps its chevron above it, placed for its size (`xs` included) by a private `--sw-select-chevron-inset` the select rule sets; and forced colors drop it and keep the `Highlight` border. No API change: same prop, same `sw-field-changed` class, no new tokens or dependencies. A consumer that layered its own background image on a changed control (none should: Scalewing owns the canvas) would now see it replaced.
|
|
8
|
+
|
|
9
|
+
`BarChartItem` takes an optional `tone` (`docs/requests/teisoro-bar-chart.md`, 2026-10-09 follow-up, Teisoro F-006-S11 task 1890): `'accent'`, `'success'`, `'warning'` or `'danger'` (new exported type `BarChartTone` and list `barChartTones`) colors a bar by what it means in place of its sign's default, such as a diverging chart's overage in warning and shortage in danger. New generated classes `sw-bar-chart-fill-accent`, `sw-bar-chart-fill-success`, `sw-bar-chart-fill-warning` and `sw-bar-chart-fill-danger`; a toned negative bar keeps `sw-bar-chart-fill-negative` and its diverging shape. Without a tone a chart is unchanged. An unknown tone throws a `RangeError`. Forced colors now draw every bar in `CanvasText`: a token fill was painted as the canvas there, so bars were invisible on their tracks. No new tokens and no new dependencies.
|
|
10
|
+
|
|
3
11
|
## 1.23.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Import the CSS once at the application entry. Do not copy it into your source tr
|
|
|
53
53
|
|
|
54
54
|
`Field` supports optional `description`, `error`, and `required` on one native input, select, or textarea child. It preserves an existing `aria-describedby` and appends a stable ID for the hint, or for the error, which replaces the hint on screen while it is supplied. `error` sets `aria-invalid` and token-owned invalid styling. The error sits in a polite live region (`aria-live="polite"`) that is always rendered, empty while there is no error, so an error that appears while the person types, or after a submit, is announced once, politely, when its text is swapped in; it is never `role="alert"`, so a submit that finds several errors does not fire several assertive alerts at once. Empty, the region takes no room in the layout. The consumer owns validation and localized messages, and after a refused submit it moves focus to the first invalid control (or to one form-level summary). `Checkbox`, `RadioGroup` and `DateField` expose their `error` the same way. `invalid` marks the control invalid the same way (`aria-invalid` and the danger border, `Mark` in forced colors) without a message of its own, for fields whose one error is shown elsewhere, such as a count that adds up to nothing under a group of count fields: point each control's `aria-describedby` at that message so it is read with the field. The field's own polite region stays empty and its `description` stays on screen; `error` implies `invalid`. Passing `invalid`, even `invalid={false}` but not `invalid={undefined}`, needs one native control child and throws a `TypeError` on the first render otherwise, so a form learns it while the fields are valid, not on its first failed submit. `error` keeps its rule: it needs the native control only while it holds a message. `invalid` is `Field`'s only: `DateField`, `Select` and the other controls with `error` do not have it yet.
|
|
55
55
|
|
|
56
|
-
`Field` `changed` marks a value the person changed from a saved one, such as a field in a correction: the control's border, or an adorned field's frame, takes the accent color a hairline thicker (an inset shadow, so nothing moves; `Highlight` in forced colors). It is a cue beside the words, so say what the value was in `description`; it sets no ARIA state, and `invalid` and `error` win over it. Like `invalid`, passing it, even `false` but not `undefined`, needs one native control child from the first render (a `TypeError` otherwise).
|
|
56
|
+
`Field` `changed` marks a value the person changed from a saved one, such as a field in a correction: the control's border, or an adorned field's frame, takes the accent color a hairline thicker (an inset shadow, so nothing moves; `Highlight` in forced colors), and its fill takes a 12 % accent tint over its own background, so a changed field reads apart from the focused one, whose ring never tints. The value keeps 4.5:1 or more on the tint in every palette; the tint is on text controls and selects (not a range, color or file input), a select's chevron stays where its size puts it, while a placeholder shows the fill stays plain, on the tint an adorned field's prefix and suffix take the text color, and forced colors drop the tint. It is a cue beside the words, so say what the value was in `description`; it sets no ARIA state, and `invalid` and `error` win over it. Like `invalid`, passing it, even `false` but not `undefined`, needs one native control child from the first render (a `TypeError` otherwise).
|
|
57
57
|
|
|
58
58
|
```tsx
|
|
59
59
|
<Field invalid={countIsZero} label="$1 bills">
|
|
@@ -285,7 +285,7 @@ An option's optional `description` (a `ReactNode`) is secondary text for that op
|
|
|
285
285
|
|
|
286
286
|
A header `TableCell` (`as="th"`) with `sort` (`'ascending'`, `'descending'` or `'none'`) and `onSort` is its column's sort control: its children become a button in the header's own style with a chevron (before the name in a `numeric` or end-aligned column), and the cell carries `aria-sort` only while it is the sorted column. `sort` on a data cell, or one of the two without the other, throws a `TypeError`. `Table` `layout="fixed"` (default `auto`) sizes the columns from the header row alone, so a filter that hides a row moves no column, and `verticalAlign="top"` (default `middle`) starts every cell of a tall row on its first line. `TableCell` `width` is `min` (an auto table's column takes the least room its content allows, on one line; set it on each cell of the column) or a size, `xs` to `xl` (4, 6, 8, 12 and 16 rem); in a fixed table a column without a width shares what is left, so leave one column, such as a description, unsized. The HTML `width` attribute is not accepted. Unknown values throw a `RangeError`.
|
|
287
287
|
|
|
288
|
-
`BarChart` `formatValue` writes the axis's values and each value without its own `valueLabel`, such as a currency. `diverging` puts zero in the middle of each track behind a hairline: a positive bar grows toward the inline end and a negative one toward the start, on one scale, and the axis reads minus the maximum, zero and the maximum. Without it every bar grows from the start.
|
|
288
|
+
`BarChart` `formatValue` writes the axis's values and each value without its own `valueLabel`, such as a currency. `diverging` puts zero in the middle of each track behind a hairline: a positive bar grows toward the inline end and a negative one toward the start, on one scale, and the axis reads minus the maximum, zero and the maximum. Without it every bar grows from the start. An item's `tone` (`BarChartTone`: `'accent'`, `'success'`, `'warning'` or `'danger'`, listed in the exported `barChartTones`) colors its bar by what it means in place of its sign's default (the accent, or danger for a negative value), such as a diverging chart's overage in `'warning'` and shortage in `'danger'`; a toned bar keeps its sign's place, every tone keeps 3:1 against the track, and an unknown tone throws a `RangeError`. Forced colors draw every bar in the system text color, so the value and its side of zero carry the meaning there.
|
|
289
289
|
|
|
290
290
|
`Spinner` shows indeterminate loading in small, medium, or large sizes. Supply localized `label` for the one announced status in a loading region. Use `decorative` on additional indicators beside that status so screen readers do not hear the same message repeatedly. Reduced motion leaves a static accented ring.
|
|
291
291
|
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { type HTMLAttributes } from 'react';
|
|
2
|
+
import { type BarChartTone, barChartTones } from '../css/css-chart.js';
|
|
3
|
+
export type { BarChartTone };
|
|
4
|
+
export { barChartTones };
|
|
2
5
|
export type BarChartItem = {
|
|
3
6
|
label: string;
|
|
4
7
|
value: number;
|
|
5
8
|
valueLabel?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Colors the bar by what it means, in place of its sign's default (the
|
|
11
|
+
* accent, or danger for a negative value): such as `'warning'` for an
|
|
12
|
+
* overage and `'danger'` for a shortage. The color is a cue beside the
|
|
13
|
+
* value and its label, never the only sign of the meaning. An unknown tone
|
|
14
|
+
* throws a `RangeError`.
|
|
15
|
+
*/
|
|
16
|
+
tone?: BarChartTone;
|
|
6
17
|
};
|
|
7
18
|
export type BarChartProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
8
19
|
'aria-label': string;
|
|
@@ -2,12 +2,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { barChartFillRatio, barChartScaleMax, formatBarChartValue, } from '@scalewing/tokens';
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
|
+
import { barChartTones } from '../css/css-chart.js';
|
|
6
|
+
export { barChartTones };
|
|
7
|
+
/** The fill's classes: the sign's (which also shapes a diverging bar) and the tone's. */
|
|
8
|
+
function barChartFillClassName(item) {
|
|
9
|
+
if (item.tone !== undefined && !barChartTones.includes(item.tone))
|
|
10
|
+
throw new RangeError(`BarChart item tone must be one of ${barChartTones.join(', ')}; received ${String(item.tone)}`);
|
|
11
|
+
return cx('sw-bar-chart-fill', item.value < 0 && 'sw-bar-chart-fill-negative', item.tone && `sw-bar-chart-fill-${item.tone}`);
|
|
12
|
+
}
|
|
5
13
|
export const BarChart = forwardRef(function BarChart({ 'aria-label': ariaLabel, className, diverging = false, formatValue = formatBarChartValue, items, max, style, ...rest }, ref) {
|
|
6
14
|
const scaleMax = barChartScaleMax(items.map((item) => item.value), max);
|
|
7
15
|
const axis = diverging ? [-scaleMax, 0, scaleMax] : [0, scaleMax];
|
|
8
16
|
return (_jsxs("div", { ref: ref, className: cx('sw-bar-chart', diverging && 'sw-bar-chart-diverging', className), style: style, ...rest, children: [_jsx("ul", { "aria-label": ariaLabel, className: "sw-bar-chart-plot", children: items.map((item, index) => {
|
|
9
17
|
const ratio = barChartFillRatio(item.value, scaleMax);
|
|
10
|
-
return (_jsxs("li", { className: "sw-bar-chart-row", children: [_jsx("span", { className: "sw-bar-chart-label", children: item.label }), _jsx("span", { className: "sw-bar-chart-track", children: _jsx("span", { className:
|
|
18
|
+
return (_jsxs("li", { className: "sw-bar-chart-row", children: [_jsx("span", { className: "sw-bar-chart-label", children: item.label }), _jsx("span", { className: "sw-bar-chart-track", children: _jsx("span", { className: barChartFillClassName(item), style: {
|
|
11
19
|
'--sw-bar-fill': ratio,
|
|
12
20
|
} }) }), _jsx("span", { className: "sw-bar-chart-value", children: item.valueLabel ?? formatValue(item.value) })] }, `${item.label}-${index}`));
|
|
13
21
|
}) }), _jsxs("div", { "aria-hidden": "true", className: "sw-bar-chart-axis", children: [_jsx("span", {}), _jsx("span", { className: "sw-bar-chart-axis-track", children: axis.map((value) => (_jsx("span", { children: formatValue(value) }, value))) }), _jsx("span", {})] })] }));
|
|
@@ -21,7 +21,10 @@ export type FieldProps = {
|
|
|
21
21
|
/**
|
|
22
22
|
* Marks a value the person changed from a saved one, such as a field in a
|
|
23
23
|
* correction: the control's border (or an adorned frame's) in the accent
|
|
24
|
-
* color, thicker than the plain hairline
|
|
24
|
+
* color, thicker than the plain hairline, and its fill tinted with 12 % of
|
|
25
|
+
* the accent, so a changed field reads apart from the focused one (focus
|
|
26
|
+
* draws a ring and never tints). The value keeps 4.5:1 on the tint; while
|
|
27
|
+
* a placeholder shows, the fill stays plain. It is a cue, not the record:
|
|
25
28
|
* say what it was in `description` ("Was 25"). `invalid` and `error`
|
|
26
29
|
* win. Like `invalid`, passing it, even `false` but not `undefined`,
|
|
27
30
|
* requires one native control child from the first render.
|
package/dist/css/css-chart.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The meanings a bar's fill can take in place of its sign's default (the
|
|
3
|
+
* accent, or danger for a negative value), such as a diverging chart whose
|
|
4
|
+
* overage is a warning and whose shortage is danger.
|
|
5
|
+
*/
|
|
6
|
+
export declare const barChartTones: readonly ["accent", "success", "warning", "danger"];
|
|
7
|
+
export type BarChartTone = (typeof barChartTones)[number];
|
|
1
8
|
export declare function cssChartClasses(): string;
|
|
2
9
|
export declare function chartClassCatalog(): string[];
|
package/dist/css/css-chart.js
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
const caption = typographyVariants.caption;
|
|
3
3
|
const data = typographyVariants.data;
|
|
4
|
+
/**
|
|
5
|
+
* The meanings a bar's fill can take in place of its sign's default (the
|
|
6
|
+
* accent, or danger for a negative value), such as a diverging chart whose
|
|
7
|
+
* overage is a warning and whose shortage is danger.
|
|
8
|
+
*/
|
|
9
|
+
export const barChartTones = [
|
|
10
|
+
'accent',
|
|
11
|
+
'success',
|
|
12
|
+
'warning',
|
|
13
|
+
'danger',
|
|
14
|
+
];
|
|
15
|
+
/*
|
|
16
|
+
* A tone's rule follows the negative fill's, at the same specificity, so a
|
|
17
|
+
* toned negative bar keeps its diverging geometry and takes the tone's
|
|
18
|
+
* color. Each tone keeps 3:1 against the track in every palette and scheme
|
|
19
|
+
* (`chart.test.tsx`). Forced colors would paint a token fill as the
|
|
20
|
+
* canvas, so every fill, toned or not, is drawn in the system text color
|
|
21
|
+
* after the tones: the value and its sign carry the meaning there.
|
|
22
|
+
*/
|
|
23
|
+
function toneRules() {
|
|
24
|
+
return barChartTones
|
|
25
|
+
.map((tone) => `.sw-bar-chart-fill-${tone} { background: var(--sw-color-${tone}); }`)
|
|
26
|
+
.join('\n');
|
|
27
|
+
}
|
|
4
28
|
/*
|
|
5
29
|
* A diverging chart puts zero in the middle of each track, marked by a
|
|
6
30
|
* hairline: a positive bar starts there and grows toward the inline end, a
|
|
@@ -98,6 +122,8 @@ export function cssChartClasses() {
|
|
|
98
122
|
background: var(--sw-color-danger);
|
|
99
123
|
}
|
|
100
124
|
|
|
125
|
+
${toneRules()}
|
|
126
|
+
|
|
101
127
|
.sw-bar-chart-value {
|
|
102
128
|
color: var(--sw-color-text);
|
|
103
129
|
font-family: var(--sw-font-sans);
|
|
@@ -123,7 +149,11 @@ export function cssChartClasses() {
|
|
|
123
149
|
justify-content: space-between;
|
|
124
150
|
}
|
|
125
151
|
|
|
126
|
-
${divergingRules()}
|
|
152
|
+
${divergingRules()}
|
|
153
|
+
|
|
154
|
+
@media (forced-colors: active) {
|
|
155
|
+
.sw-bar-chart-fill { background: CanvasText; }
|
|
156
|
+
}`;
|
|
127
157
|
}
|
|
128
158
|
export function chartClassCatalog() {
|
|
129
159
|
return [
|
|
@@ -135,6 +165,7 @@ export function chartClassCatalog() {
|
|
|
135
165
|
'sw-bar-chart-track',
|
|
136
166
|
'sw-bar-chart-fill',
|
|
137
167
|
'sw-bar-chart-fill-negative',
|
|
168
|
+
...barChartTones.map((tone) => `sw-bar-chart-fill-${tone}`),
|
|
138
169
|
'sw-bar-chart-value',
|
|
139
170
|
'sw-bar-chart-axis-track',
|
|
140
171
|
'sw-bar-chart-diverging',
|
package/dist/css/css-chrome.js
CHANGED
|
@@ -57,10 +57,9 @@ export function cssChromeClasses() {
|
|
|
57
57
|
padding-inline: var(--sw-control-xs-padding-inline);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/* The chevron's layers place themselves from this inset (selectChevronLayers). */
|
|
60
61
|
.sw-field-xs select {
|
|
61
|
-
|
|
62
|
-
calc(100% - var(--sw-space-3)) calc(50% - 1px),
|
|
63
|
-
calc(100% - calc(var(--sw-space-3) - var(--sw-space-1))) calc(50% - 1px);
|
|
62
|
+
--sw-select-chevron-inset: var(--sw-space-3);
|
|
64
63
|
height: calc(var(--sw-control-xs-min-height) - 1px - 1px);
|
|
65
64
|
min-height: 0;
|
|
66
65
|
padding-inline-end: calc(
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
/** The native controls that take typed text: the canvas's text controls. */
|
|
2
|
+
export declare const typedInputs: string;
|
|
1
3
|
/** The frame every native text control draws: fill, hairline border, radius. */
|
|
2
4
|
export declare const controlSurface = "background-color: var(--sw-glass-fill);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-radius-sm);\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n padding-inline: var(--sw-control-md-padding-inline);";
|
|
3
5
|
export declare const disabledControlSurface = "background-color: var(--sw-color-subtle);\n border-style: dashed;\n cursor: not-allowed;\n opacity: 1;\n -webkit-text-fill-color: currentColor;";
|
|
4
6
|
export declare const disabledControlForcedColors = "border-color: GrayText;\n color: GrayText;\n -webkit-text-fill-color: GrayText;";
|
|
7
|
+
/**
|
|
8
|
+
* A select's chevron: two muted triangles drawn as background layers, so a
|
|
9
|
+
* rule that adds a layer (a changed field's tint) lists them with it. Their
|
|
10
|
+
* distance from the end is `--sw-select-chevron-inset`, which the select
|
|
11
|
+
* rule sets per size (space 4, and space 3 in an `xs` field), so every rule
|
|
12
|
+
* that lists the layers places them for the size it is in.
|
|
13
|
+
*/
|
|
14
|
+
export declare const selectChevronLayers: {
|
|
15
|
+
readonly image: "linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),\n linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%)";
|
|
16
|
+
readonly position: "calc(100% - var(--sw-select-chevron-inset)) calc(50% - 1px),\n calc(100% - calc(var(--sw-select-chevron-inset) - var(--sw-space-1))) calc(50% - 1px)";
|
|
17
|
+
readonly size: "var(--sw-space-1) var(--sw-space-1),\n var(--sw-space-1) var(--sw-space-1)";
|
|
18
|
+
};
|
|
5
19
|
/** A script-only focus target in the canvas, at zero specificity. */
|
|
6
20
|
export declare const focusTargetSelector = ":where([data-theme] [tabindex='-1']:focus-visible)";
|
|
7
21
|
export declare function cssDocumentCanvas(): string;
|
package/dist/css/css-document.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
const body = typographyVariants.body;
|
|
3
|
-
|
|
3
|
+
/** The native controls that take typed text: the canvas's text controls. */
|
|
4
|
+
export const typedInputs = [
|
|
4
5
|
"input[type='text']",
|
|
5
6
|
"input[type='email']",
|
|
6
7
|
"input[type='number']",
|
|
@@ -34,6 +35,21 @@ export const disabledControlSurface = `background-color: var(--sw-color-subtle);
|
|
|
34
35
|
export const disabledControlForcedColors = `border-color: GrayText;
|
|
35
36
|
color: GrayText;
|
|
36
37
|
-webkit-text-fill-color: GrayText;`;
|
|
38
|
+
/**
|
|
39
|
+
* A select's chevron: two muted triangles drawn as background layers, so a
|
|
40
|
+
* rule that adds a layer (a changed field's tint) lists them with it. Their
|
|
41
|
+
* distance from the end is `--sw-select-chevron-inset`, which the select
|
|
42
|
+
* rule sets per size (space 4, and space 3 in an `xs` field), so every rule
|
|
43
|
+
* that lists the layers places them for the size it is in.
|
|
44
|
+
*/
|
|
45
|
+
export const selectChevronLayers = {
|
|
46
|
+
image: `linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),
|
|
47
|
+
linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%)`,
|
|
48
|
+
position: `calc(100% - var(--sw-select-chevron-inset)) calc(50% - 1px),
|
|
49
|
+
calc(100% - calc(var(--sw-select-chevron-inset) - var(--sw-space-1))) calc(50% - 1px)`,
|
|
50
|
+
size: `var(--sw-space-1) var(--sw-space-1),
|
|
51
|
+
var(--sw-space-1) var(--sw-space-1)`,
|
|
52
|
+
};
|
|
37
53
|
/** A script-only focus target in the canvas, at zero specificity. */
|
|
38
54
|
export const focusTargetSelector = ":where([data-theme] [tabindex='-1']:focus-visible)";
|
|
39
55
|
export function cssDocumentCanvas() {
|
|
@@ -103,16 +119,14 @@ ${focusTargetSelector} {
|
|
|
103
119
|
|
|
104
120
|
[data-theme] select {
|
|
105
121
|
${controlSurface}
|
|
122
|
+
--sw-select-chevron-inset: var(--sw-space-4);
|
|
106
123
|
appearance: none;
|
|
107
124
|
background-image:
|
|
108
|
-
|
|
109
|
-
linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%);
|
|
125
|
+
${selectChevronLayers.image};
|
|
110
126
|
background-position:
|
|
111
|
-
|
|
112
|
-
calc(100% - calc(var(--sw-space-4) - var(--sw-space-1))) calc(50% - 1px);
|
|
127
|
+
${selectChevronLayers.position};
|
|
113
128
|
background-repeat: no-repeat;
|
|
114
|
-
background-size:
|
|
115
|
-
var(--sw-space-1) var(--sw-space-1);
|
|
129
|
+
background-size: ${selectChevronLayers.size};
|
|
116
130
|
box-sizing: content-box;
|
|
117
131
|
height: calc(var(--sw-control-md-min-height) - 1px - 1px);
|
|
118
132
|
min-height: 0;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much accent a changed control's fill takes: the share of the accent
|
|
3
|
+
* layered over the control's own background (the glass fill, or the subtle
|
|
4
|
+
* fill of a disabled control). The value keeps 4.5:1 or more on it in every
|
|
5
|
+
* palette and scheme (`field-changed.test.tsx`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const changedTintStrength = 0.12;
|
|
8
|
+
/**
|
|
9
|
+
* The tint as one background image, so it lies over the control's background
|
|
10
|
+
* color instead of replacing it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const changedTintLayer = "linear-gradient(color-mix(in srgb, var(--sw-color-accent) 12%, transparent), color-mix(in srgb, var(--sw-color-accent) 12%, transparent))";
|
|
13
|
+
export declare function cssFieldChangedClasses(): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { selectChevronLayers, typedInputs } from './css-document.js';
|
|
2
|
+
/**
|
|
3
|
+
* How much accent a changed control's fill takes: the share of the accent
|
|
4
|
+
* layered over the control's own background (the glass fill, or the subtle
|
|
5
|
+
* fill of a disabled control). The value keeps 4.5:1 or more on it in every
|
|
6
|
+
* palette and scheme (`field-changed.test.tsx`).
|
|
7
|
+
*/
|
|
8
|
+
export const changedTintStrength = 0.12;
|
|
9
|
+
const tintColor = `color-mix(in srgb, var(--sw-color-accent) ${changedTintStrength * 100}%, transparent)`;
|
|
10
|
+
/**
|
|
11
|
+
* The tint as one background image, so it lies over the control's background
|
|
12
|
+
* color instead of replacing it.
|
|
13
|
+
*/
|
|
14
|
+
export const changedTintLayer = `linear-gradient(${tintColor}, ${tintColor})`;
|
|
15
|
+
/** An adorned frame that holds a value (no placeholder showing). */
|
|
16
|
+
const tintedFrame = '.sw-field-changed .sw-field-adorned:not(:has(> input:placeholder-shown))';
|
|
17
|
+
/*
|
|
18
|
+
* A changed value: the accent border, a hairline thicker through an inset
|
|
19
|
+
* shadow so nothing moves, and the accent tint over the fill, on the control
|
|
20
|
+
* or on an adorned frame (whose input is transparent and draws no border, so
|
|
21
|
+
* the tint shows once). Focus draws a ring and never tints, so "changed" and
|
|
22
|
+
* "where I am" read apart. While a placeholder shows there is no value to
|
|
23
|
+
* mark and the fill stays plain, so the muted placeholder keeps its contrast;
|
|
24
|
+
* on the tint, an adorned frame's prefix and suffix take the text color. The
|
|
25
|
+
* tint is for the text controls and select (not a range, color or file
|
|
26
|
+
* input). A select lists its chevron above the tint, placed for its size by
|
|
27
|
+
* `--sw-select-chevron-inset`. Forced colors drop the tint and the shadow
|
|
28
|
+
* and keep the Highlight border.
|
|
29
|
+
*/
|
|
30
|
+
export function cssFieldChangedClasses() {
|
|
31
|
+
return `[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
32
|
+
.sw-field-changed .sw-field-adorned {
|
|
33
|
+
border-color: var(--sw-color-accent);
|
|
34
|
+
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[data-theme] .sw-field-changed > :is(${typedInputs}):not(:placeholder-shown),
|
|
38
|
+
${tintedFrame} {
|
|
39
|
+
background-image: ${changedTintLayer};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[data-theme] .sw-field-changed > select {
|
|
43
|
+
background-image:
|
|
44
|
+
${selectChevronLayers.image},
|
|
45
|
+
${changedTintLayer};
|
|
46
|
+
background-position:
|
|
47
|
+
${selectChevronLayers.position},
|
|
48
|
+
0 0;
|
|
49
|
+
background-size: ${selectChevronLayers.size},
|
|
50
|
+
100% 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
${tintedFrame} > :is(.sw-field-prefix, .sw-field-suffix) {
|
|
54
|
+
color: var(--sw-color-text);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@media (forced-colors: active) {
|
|
58
|
+
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
59
|
+
.sw-field-changed .sw-field-adorned { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; }
|
|
60
|
+
[data-theme] .sw-field-changed > :is(${typedInputs}):not(:placeholder-shown),
|
|
61
|
+
${tintedFrame} { background-image: none; }
|
|
62
|
+
[data-theme] .sw-field-changed > select { background-image: ${selectChevronLayers.image}; }
|
|
63
|
+
}`;
|
|
64
|
+
}
|
package/dist/css/css-field.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { controlSurface, disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
|
|
3
|
+
import { cssFieldChangedClasses } from './css-field-changed.js';
|
|
3
4
|
const caption = typographyVariants.caption;
|
|
4
5
|
/*
|
|
5
6
|
* A prefix or suffix sits inside the control frame: the wrapper draws the
|
|
@@ -80,24 +81,13 @@ export function cssFieldClasses() {
|
|
|
80
81
|
border-color: var(--sw-color-danger);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
/*
|
|
84
|
-
* A changed value: the accent border, a hairline thicker through an inset
|
|
85
|
-
* shadow so nothing moves, on the control or on an adorned frame (whose
|
|
86
|
-
* input draws no border of its own).
|
|
87
|
-
*/
|
|
88
|
-
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
89
|
-
.sw-field-changed .sw-field-adorned {
|
|
90
|
-
border-color: var(--sw-color-accent);
|
|
91
|
-
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
84
|
${adornmentRules()}
|
|
95
85
|
|
|
86
|
+
${cssFieldChangedClasses()}
|
|
87
|
+
|
|
96
88
|
@media (forced-colors: active) {
|
|
97
89
|
[data-theme] .sw-field-invalid :is(input, select, textarea),
|
|
98
90
|
.sw-field-invalid .sw-field-adorned { border-color: Mark; }
|
|
99
|
-
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
100
|
-
.sw-field-changed .sw-field-adorned { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; }
|
|
101
91
|
.sw-field-prefix, .sw-field-suffix { color: CanvasText; }
|
|
102
92
|
.sw-field-adorned:has(> input:disabled) {
|
|
103
93
|
${disabledControlForcedColors}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { Tooltip, type TooltipProps, type TooltipRelationship, } from './compone
|
|
|
12
12
|
export { RadioGroup, type RadioGroupOption, type RadioGroupProps, } from './components/RadioGroup.js';
|
|
13
13
|
export { AppHeader, type AppHeaderProps } from './components/AppHeader.js';
|
|
14
14
|
export { Badge, type BadgeProps, type BadgeSize, type BadgeTone, badgeSizes, badgeTones, } from './components/Badge.js';
|
|
15
|
-
export { BarChart, type BarChartItem, type BarChartProps, } from './components/BarChart.js';
|
|
15
|
+
export { BarChart, type BarChartItem, type BarChartProps, type BarChartTone, barChartTones, } from './components/BarChart.js';
|
|
16
16
|
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, } from './components/Button.js';
|
|
17
17
|
export { Box, type BoxBorder, type BoxElement, type BoxProps, } from './components/Box.js';
|
|
18
18
|
export { ButtonGroup, type ButtonGroupJustify, type ButtonGroupProps, } from './components/ButtonGroup.js';
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { Tooltip, } from './components/Tooltip.js';
|
|
|
11
11
|
export { RadioGroup, } from './components/RadioGroup.js';
|
|
12
12
|
export { AppHeader } from './components/AppHeader.js';
|
|
13
13
|
export { Badge, badgeSizes, badgeTones, } from './components/Badge.js';
|
|
14
|
-
export { BarChart, } from './components/BarChart.js';
|
|
14
|
+
export { BarChart, barChartTones, } from './components/BarChart.js';
|
|
15
15
|
export { Button, } from './components/Button.js';
|
|
16
16
|
export { Box, } from './components/Box.js';
|
|
17
17
|
export { ButtonGroup, } from './components/ButtonGroup.js';
|
package/dist/styles.css
CHANGED
|
@@ -644,13 +644,14 @@ body {
|
|
|
644
644
|
font-family: inherit;
|
|
645
645
|
font-size: inherit;
|
|
646
646
|
padding-inline: var(--sw-control-md-padding-inline);
|
|
647
|
+
--sw-select-chevron-inset: var(--sw-space-4);
|
|
647
648
|
appearance: none;
|
|
648
649
|
background-image:
|
|
649
650
|
linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),
|
|
650
651
|
linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%);
|
|
651
652
|
background-position:
|
|
652
|
-
calc(100% - var(--sw-
|
|
653
|
-
calc(100% - calc(var(--sw-
|
|
653
|
+
calc(100% - var(--sw-select-chevron-inset)) calc(50% - 1px),
|
|
654
|
+
calc(100% - calc(var(--sw-select-chevron-inset) - var(--sw-space-1))) calc(50% - 1px);
|
|
654
655
|
background-repeat: no-repeat;
|
|
655
656
|
background-size: var(--sw-space-1) var(--sw-space-1),
|
|
656
657
|
var(--sw-space-1) var(--sw-space-1);
|
|
@@ -1951,6 +1952,11 @@ body {
|
|
|
1951
1952
|
background: var(--sw-color-danger);
|
|
1952
1953
|
}
|
|
1953
1954
|
|
|
1955
|
+
.sw-bar-chart-fill-accent { background: var(--sw-color-accent); }
|
|
1956
|
+
.sw-bar-chart-fill-success { background: var(--sw-color-success); }
|
|
1957
|
+
.sw-bar-chart-fill-warning { background: var(--sw-color-warning); }
|
|
1958
|
+
.sw-bar-chart-fill-danger { background: var(--sw-color-danger); }
|
|
1959
|
+
|
|
1954
1960
|
.sw-bar-chart-value {
|
|
1955
1961
|
color: var(--sw-color-text);
|
|
1956
1962
|
font-family: var(--sw-font-sans);
|
|
@@ -2008,6 +2014,10 @@ body {
|
|
|
2008
2014
|
.sw-bar-chart-diverging .sw-bar-chart-track::before { background: CanvasText; }
|
|
2009
2015
|
}
|
|
2010
2016
|
|
|
2017
|
+
@media (forced-colors: active) {
|
|
2018
|
+
.sw-bar-chart-fill { background: CanvasText; }
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2011
2021
|
.sw-app-header {
|
|
2012
2022
|
background: var(--sw-glass-fill);
|
|
2013
2023
|
border: 1px solid var(--sw-glass-border);
|
|
@@ -2061,10 +2071,9 @@ body {
|
|
|
2061
2071
|
padding-inline: var(--sw-control-xs-padding-inline);
|
|
2062
2072
|
}
|
|
2063
2073
|
|
|
2074
|
+
/* The chevron's layers place themselves from this inset (selectChevronLayers). */
|
|
2064
2075
|
.sw-field-xs select {
|
|
2065
|
-
|
|
2066
|
-
calc(100% - var(--sw-space-3)) calc(50% - 1px),
|
|
2067
|
-
calc(100% - calc(var(--sw-space-3) - var(--sw-space-1))) calc(50% - 1px);
|
|
2076
|
+
--sw-select-chevron-inset: var(--sw-space-3);
|
|
2068
2077
|
height: calc(var(--sw-control-xs-min-height) - 1px - 1px);
|
|
2069
2078
|
min-height: 0;
|
|
2070
2079
|
padding-inline-end: calc(
|
|
@@ -3611,17 +3620,6 @@ body {
|
|
|
3611
3620
|
border-color: var(--sw-color-danger);
|
|
3612
3621
|
}
|
|
3613
3622
|
|
|
3614
|
-
/*
|
|
3615
|
-
* A changed value: the accent border, a hairline thicker through an inset
|
|
3616
|
-
* shadow so nothing moves, on the control or on an adorned frame (whose
|
|
3617
|
-
* input draws no border of its own).
|
|
3618
|
-
*/
|
|
3619
|
-
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
3620
|
-
.sw-field-changed .sw-field-adorned {
|
|
3621
|
-
border-color: var(--sw-color-accent);
|
|
3622
|
-
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3625
3623
|
.sw-field-adorned {
|
|
3626
3624
|
background-color: var(--sw-glass-fill);
|
|
3627
3625
|
border: 1px solid var(--sw-color-border);
|
|
@@ -3685,11 +3683,47 @@ body {
|
|
|
3685
3683
|
|
|
3686
3684
|
[data-theme] .sw-field-adorned > input:disabled { background: transparent; }
|
|
3687
3685
|
|
|
3686
|
+
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
3687
|
+
.sw-field-changed .sw-field-adorned {
|
|
3688
|
+
border-color: var(--sw-color-accent);
|
|
3689
|
+
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
[data-theme] .sw-field-changed > :is(input[type='text'], input[type='email'], input[type='number'], input[type='search'], input[type='url'], input[type='password'], input:not([type]), textarea):not(:placeholder-shown),
|
|
3693
|
+
.sw-field-changed .sw-field-adorned:not(:has(> input:placeholder-shown)) {
|
|
3694
|
+
background-image: linear-gradient(color-mix(in srgb, var(--sw-color-accent) 12%, transparent), color-mix(in srgb, var(--sw-color-accent) 12%, transparent));
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
[data-theme] .sw-field-changed > select {
|
|
3698
|
+
background-image:
|
|
3699
|
+
linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),
|
|
3700
|
+
linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%),
|
|
3701
|
+
linear-gradient(color-mix(in srgb, var(--sw-color-accent) 12%, transparent), color-mix(in srgb, var(--sw-color-accent) 12%, transparent));
|
|
3702
|
+
background-position:
|
|
3703
|
+
calc(100% - var(--sw-select-chevron-inset)) calc(50% - 1px),
|
|
3704
|
+
calc(100% - calc(var(--sw-select-chevron-inset) - var(--sw-space-1))) calc(50% - 1px),
|
|
3705
|
+
0 0;
|
|
3706
|
+
background-size: var(--sw-space-1) var(--sw-space-1),
|
|
3707
|
+
var(--sw-space-1) var(--sw-space-1),
|
|
3708
|
+
100% 100%;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
.sw-field-changed .sw-field-adorned:not(:has(> input:placeholder-shown)) > :is(.sw-field-prefix, .sw-field-suffix) {
|
|
3712
|
+
color: var(--sw-color-text);
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3688
3715
|
@media (forced-colors: active) {
|
|
3689
|
-
[data-theme] .sw-field-invalid :is(input, select, textarea),
|
|
3690
|
-
.sw-field-invalid .sw-field-adorned { border-color: Mark; }
|
|
3691
3716
|
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
3692
3717
|
.sw-field-changed .sw-field-adorned { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; }
|
|
3718
|
+
[data-theme] .sw-field-changed > :is(input[type='text'], input[type='email'], input[type='number'], input[type='search'], input[type='url'], input[type='password'], input:not([type]), textarea):not(:placeholder-shown),
|
|
3719
|
+
.sw-field-changed .sw-field-adorned:not(:has(> input:placeholder-shown)) { background-image: none; }
|
|
3720
|
+
[data-theme] .sw-field-changed > select { background-image: linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),
|
|
3721
|
+
linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%); }
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
@media (forced-colors: active) {
|
|
3725
|
+
[data-theme] .sw-field-invalid :is(input, select, textarea),
|
|
3726
|
+
.sw-field-invalid .sw-field-adorned { border-color: Mark; }
|
|
3693
3727
|
.sw-field-prefix, .sw-field-suffix { color: CanvasText; }
|
|
3694
3728
|
.sw-field-adorned:has(> input:disabled) {
|
|
3695
3729
|
border-color: GrayText;
|