@scalewing/react 1.3.0 → 1.4.1
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 +25 -0
- package/dist/components/StatTile.d.ts +25 -0
- package/dist/components/StatTile.js +14 -0
- package/dist/components/Table.d.ts +5 -0
- package/dist/components/Table.js +6 -1
- package/dist/components/Tabs.d.ts +39 -0
- package/dist/components/Tabs.js +60 -0
- package/dist/css/css-components.js +13 -0
- package/dist/css/css-data.d.ts +1 -1
- package/dist/css/css-data.js +18 -1
- package/dist/css/css-stat-tile.d.ts +5 -0
- package/dist/css/css-stat-tile.js +73 -0
- package/dist/css/css-tabs.d.ts +3 -0
- package/dist/css/css-tabs.js +64 -0
- package/dist/css/stylesheet.js +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/styles.css +131 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- efdd079: An open `Select`, `ActionMenu` or `Tooltip` inside a glass `Card` or an
|
|
8
|
+
`Accordion` now paints over the surface below it (see
|
|
9
|
+
`docs/requests/teisoro-popup-stacking.md`). Backdrop blur makes each glass
|
|
10
|
+
surface its own stacking context, so the open list painted under the next card
|
|
11
|
+
and a click on a covered option reached that card's field instead. No prop
|
|
12
|
+
changes.
|
|
13
|
+
|
|
14
|
+
## 1.4.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- e3b4ddc: Add `StatTile`, one prominent figure with its label: a glyph slot, a tabular value with a `tone` (`accent`, `success`, `danger`, `warning`), an optional caption, and `emphasis="primary"` that fills the tile in the accent for the figure a page leads with (generated `sw-stat-tile`, `sw-stat-tile-primary`, `sw-stat-tile-glyph`, `sw-stat-tile-body`, `sw-stat-tile-label`, `sw-stat-tile-value`, `sw-stat-tile-caption`; the tone colours the value through `Text color`).
|
|
19
|
+
- 97e172a: Add `Tabs` and `TabPanel`: a `tablist` strip with an accent underline under the current tab, roving focus, ArrowLeft / ArrowRight / Home / End that move and select, sideways scrolling when the labels overflow (generated `sw-tabs`, `sw-tab`, `sw-tab-selected`), and a labelled `tabpanel` that hides itself while another tab is current.
|
|
20
|
+
- 98ffed7: Add `Badge tone="warning"`, outlined in the new `warning` colour like `success` and `danger` (generated `sw-badge-warning`), for the middle status a product shows between good and blocked.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- aefecaf: `Table` scroll wrapper is keyboard reachable: the `sw-table-wrap` element is a `group` named after the table (`aria-label` / `aria-labelledby` mirrored) with `tabIndex={0}` and an accent focus ring, so a wide table with no focusable cell can be scrolled sideways from the keyboard and passes the axe `scrollable-region-focusable` rule.
|
|
25
|
+
- Updated dependencies [98ffed7]
|
|
26
|
+
- @scalewing/tokens@1.2.0
|
|
27
|
+
|
|
3
28
|
## 1.3.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { type StatTileTone, statTileTones } from '../css/css-stat-tile.js';
|
|
3
|
+
export type { StatTileTone };
|
|
4
|
+
export { statTileTones };
|
|
5
|
+
export type StatTileEmphasis = 'default' | 'primary';
|
|
6
|
+
export type StatTileProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
|
|
7
|
+
/** A short name for the figure, set in the label variant. */
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
/** The figure, already formatted by the consumer; tabular numerals. */
|
|
10
|
+
value: ReactNode;
|
|
11
|
+
/** The consumer's glyph (Lucide), shown in a tinted circle before the text. */
|
|
12
|
+
glyph?: ReactNode;
|
|
13
|
+
/** Colours the value through `Text color`: the sign of a variance, a count that needs attention. */
|
|
14
|
+
tone?: StatTileTone;
|
|
15
|
+
/** A second line under the value: a breakdown, a unit, a comparison. */
|
|
16
|
+
caption?: ReactNode;
|
|
17
|
+
/** `primary` fills the tile in the accent for the one figure a page leads with. */
|
|
18
|
+
emphasis?: StatTileEmphasis;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* One prominent figure with its label: the tiles a report or a list opens
|
|
22
|
+
* with. Presentation only; the consumer formats the number and picks the
|
|
23
|
+
* glyph.
|
|
24
|
+
*/
|
|
25
|
+
export declare function StatTile({ caption, className, emphasis, glyph, label, tone, value, ...rest }: StatTileProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../class-names.js';
|
|
3
|
+
import { statTileTones } from '../css/css-stat-tile.js';
|
|
4
|
+
import { Text } from './Text.js';
|
|
5
|
+
export { statTileTones };
|
|
6
|
+
/**
|
|
7
|
+
* One prominent figure with its label: the tiles a report or a list opens
|
|
8
|
+
* with. Presentation only; the consumer formats the number and picks the
|
|
9
|
+
* glyph.
|
|
10
|
+
*/
|
|
11
|
+
export function StatTile({ caption, className, emphasis = 'default', glyph, label, tone = 'default', value, ...rest }) {
|
|
12
|
+
const primary = emphasis === 'primary';
|
|
13
|
+
return (_jsxs("section", { className: cx('sw-stat-tile', primary && 'sw-stat-tile-primary', className), ...rest, children: [glyph ? (_jsx("span", { "aria-hidden": "true", className: "sw-stat-tile-glyph", children: glyph })) : null, _jsxs("span", { className: "sw-stat-tile-body", children: [_jsx(Text, { as: "span", className: "sw-stat-tile-label", color: primary ? 'onAccent' : 'muted', variant: "label", children: label }), _jsx(Text, { as: "strong", className: "sw-stat-tile-value", color: primary ? 'onAccent' : tone === 'default' ? 'text' : tone, variant: "heading", children: value }), caption ? (_jsx(Text, { as: "span", className: "sw-stat-tile-caption", color: primary ? 'onAccent' : 'muted', variant: "caption", children: caption })) : null] })] }));
|
|
14
|
+
}
|
|
@@ -4,6 +4,11 @@ export type TableProps = HTMLAttributes<HTMLTableElement> & {
|
|
|
4
4
|
density?: TableDensity;
|
|
5
5
|
stickyHeader?: boolean;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* The scroll wrapper is a keyboard stop named after the table, so a wide
|
|
9
|
+
* table with no focusable cell can still be scrolled sideways from the
|
|
10
|
+
* keyboard and is announced as one group.
|
|
11
|
+
*/
|
|
7
12
|
export declare const Table: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLTableElement> & {
|
|
8
13
|
density?: TableDensity;
|
|
9
14
|
stickyHeader?: boolean;
|
package/dist/components/Table.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, } from 'react';
|
|
3
3
|
import { cx } from '../class-names.js';
|
|
4
|
+
/**
|
|
5
|
+
* The scroll wrapper is a keyboard stop named after the table, so a wide
|
|
6
|
+
* table with no focusable cell can still be scrolled sideways from the
|
|
7
|
+
* keyboard and is announced as one group.
|
|
8
|
+
*/
|
|
4
9
|
export const Table = forwardRef(function Table({ children, className, density = 'comfortable', stickyHeader = true, ...rest }, ref) {
|
|
5
|
-
return (_jsx("div", { className: "sw-table-wrap", children: _jsx("table", { ref: ref, className: cx('sw-table', density === 'compact' && 'sw-table-compact', stickyHeader && 'sw-table-sticky', className), ...rest, children: children }) }));
|
|
10
|
+
return (_jsx("div", { "aria-label": rest['aria-label'], "aria-labelledby": rest['aria-labelledby'], className: "sw-table-wrap", role: "group", tabIndex: 0, children: _jsx("table", { ref: ref, className: cx('sw-table', density === 'compact' && 'sw-table-compact', stickyHeader && 'sw-table-sticky', className), ...rest, children: children }) }));
|
|
6
11
|
});
|
|
7
12
|
export const TableHeader = forwardRef(function TableHeader({ className, ...rest }, ref) {
|
|
8
13
|
return _jsx("thead", { ref: ref, className: className, ...rest });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export type TabItem = {
|
|
3
|
+
id: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
type TabsLabel = {
|
|
7
|
+
'aria-label': string;
|
|
8
|
+
'aria-labelledby'?: never;
|
|
9
|
+
} | {
|
|
10
|
+
'aria-label'?: never;
|
|
11
|
+
'aria-labelledby': string;
|
|
12
|
+
};
|
|
13
|
+
export type TabsProps = TabsLabel & {
|
|
14
|
+
/** Prefix for the tab and panel ids, so a `TabPanel` can point back at its tab. */
|
|
15
|
+
id: string;
|
|
16
|
+
items: readonly TabItem[];
|
|
17
|
+
value: string;
|
|
18
|
+
onChange: (id: string) => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function tabId(tabsId: string, id: string): string;
|
|
21
|
+
export declare function tabPanelId(tabsId: string, id: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* A tab strip with `tablist` semantics for the sections of one page. Arrow
|
|
24
|
+
* keys, Home and End move the focus and select at once; the strip scrolls
|
|
25
|
+
* sideways when it overflows. Pair each item with a `TabPanel`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function Tabs({ id, items, onChange, value, ...labelProps }: TabsProps): import("react").JSX.Element;
|
|
28
|
+
export type TabPanelProps = Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
|
29
|
+
/** The `id` of the `Tabs` this panel belongs to. */
|
|
30
|
+
tabsId: string;
|
|
31
|
+
/** The item id this panel shows. */
|
|
32
|
+
id: string;
|
|
33
|
+
/** The current `Tabs` value; the panel hides itself when another tab is current. */
|
|
34
|
+
value: string;
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
};
|
|
37
|
+
/** The content of one tab, labelled by its tab and hidden while another tab is current. */
|
|
38
|
+
export declare function TabPanel({ children, className, id, tabsId, value, ...rest }: TabPanelProps): import("react").JSX.Element;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, } from 'react';
|
|
3
|
+
import { cx } from '../class-names.js';
|
|
4
|
+
export function tabId(tabsId, id) {
|
|
5
|
+
return `${tabsId}-tab-${id}`;
|
|
6
|
+
}
|
|
7
|
+
export function tabPanelId(tabsId, id) {
|
|
8
|
+
return `${tabsId}-panel-${id}`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A tab strip with `tablist` semantics for the sections of one page. Arrow
|
|
12
|
+
* keys, Home and End move the focus and select at once; the strip scrolls
|
|
13
|
+
* sideways when it overflows. Pair each item with a `TabPanel`.
|
|
14
|
+
*/
|
|
15
|
+
export function Tabs({ id, items, onChange, value, ...labelProps }) {
|
|
16
|
+
const tabs = useRef(new Map());
|
|
17
|
+
function select(next) {
|
|
18
|
+
if (!next || next.id === value) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
onChange(next.id);
|
|
22
|
+
tabs.current.get(next.id)?.focus();
|
|
23
|
+
}
|
|
24
|
+
function onKeyDown(event) {
|
|
25
|
+
const index = items.findIndex((item) => item.id === value);
|
|
26
|
+
if (index < 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const last = items.length - 1;
|
|
30
|
+
const target = event.key === 'ArrowRight'
|
|
31
|
+
? items[index === last ? 0 : index + 1]
|
|
32
|
+
: event.key === 'ArrowLeft'
|
|
33
|
+
? items[index === 0 ? last : index - 1]
|
|
34
|
+
: event.key === 'Home'
|
|
35
|
+
? items[0]
|
|
36
|
+
: event.key === 'End'
|
|
37
|
+
? items[last]
|
|
38
|
+
: undefined;
|
|
39
|
+
if (!target) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
select(target);
|
|
44
|
+
}
|
|
45
|
+
return (_jsx("div", { className: "sw-tabs", onKeyDown: onKeyDown, role: "tablist", ...labelProps, children: items.map((item) => {
|
|
46
|
+
const selected = item.id === value;
|
|
47
|
+
return (_jsx("button", { "aria-controls": tabPanelId(id, item.id), "aria-selected": selected, className: cx('sw-tab', selected && 'sw-tab-selected'), id: tabId(id, item.id), onClick: () => select(item), ref: (node) => {
|
|
48
|
+
if (node) {
|
|
49
|
+
tabs.current.set(item.id, node);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
tabs.current.delete(item.id);
|
|
53
|
+
}
|
|
54
|
+
}, role: "tab", tabIndex: selected ? 0 : -1, type: "button", children: item.label }, item.id));
|
|
55
|
+
}) }));
|
|
56
|
+
}
|
|
57
|
+
/** The content of one tab, labelled by its tab and hidden while another tab is current. */
|
|
58
|
+
export function TabPanel({ children, className, id, tabsId, value, ...rest }) {
|
|
59
|
+
return (_jsx("div", { "aria-labelledby": tabId(tabsId, id), className: cx('sw-tab-panel', className), hidden: value !== id, id: tabPanelId(tabsId, id), role: "tabpanel", tabIndex: 0, ...rest, children: children }));
|
|
60
|
+
}
|
|
@@ -17,6 +17,8 @@ import { cssSpinnerClasses } from './css-spinner.js';
|
|
|
17
17
|
import { cssProgressClasses } from './css-progress.js';
|
|
18
18
|
import { cssTooltipClasses } from './css-tooltip.js';
|
|
19
19
|
import { cssSeparatorClasses } from './css-separator.js';
|
|
20
|
+
import { cssStatTileClasses } from './css-stat-tile.js';
|
|
21
|
+
import { cssTabsClasses } from './css-tabs.js';
|
|
20
22
|
import { cssResponsiveClasses } from './css-responsive.js';
|
|
21
23
|
import { cssFilterChipsClasses } from './css-filter-chips.js';
|
|
22
24
|
import { cssGridClasses } from './css-grid.js';
|
|
@@ -53,6 +55,13 @@ export function cssComponentClasses() {
|
|
|
53
55
|
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
/* Backdrop blur makes a glass surface its own stacking context, so an open popup inside it would paint under the next surface. */
|
|
59
|
+
.sw-card-glass:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])),
|
|
60
|
+
.sw-accordion:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])) {
|
|
61
|
+
position: relative;
|
|
62
|
+
z-index: 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
.sw-card-outlined {
|
|
57
66
|
background: var(--sw-color-surface);
|
|
58
67
|
border: 1px solid var(--sw-color-border);
|
|
@@ -133,6 +142,10 @@ ${cssTooltipClasses()}
|
|
|
133
142
|
|
|
134
143
|
${cssSeparatorClasses()}
|
|
135
144
|
|
|
145
|
+
${cssStatTileClasses()}
|
|
146
|
+
|
|
147
|
+
${cssTabsClasses()}
|
|
148
|
+
|
|
136
149
|
${cssFilterChipsClasses()}
|
|
137
150
|
|
|
138
151
|
${cssGridClasses()}
|
package/dist/css/css-data.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const badgeTones: readonly ["neutral", "accent", "success", "danger"];
|
|
1
|
+
export declare const badgeTones: readonly ["neutral", "accent", "success", "danger", "warning"];
|
|
2
2
|
export type BadgeTone = (typeof badgeTones)[number];
|
|
3
3
|
export declare const badgeSizes: readonly ["sm", "md"];
|
|
4
4
|
export type BadgeSize = (typeof badgeSizes)[number];
|
package/dist/css/css-data.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { trackInset, typographyVariants } from '@scalewing/tokens';
|
|
2
|
-
export const badgeTones = [
|
|
2
|
+
export const badgeTones = [
|
|
3
|
+
'neutral',
|
|
4
|
+
'accent',
|
|
5
|
+
'success',
|
|
6
|
+
'danger',
|
|
7
|
+
'warning',
|
|
8
|
+
];
|
|
3
9
|
export const badgeSizes = ['sm', 'md'];
|
|
4
10
|
const caption = typographyVariants.caption;
|
|
5
11
|
const data = typographyVariants.data;
|
|
@@ -54,6 +60,12 @@ export function cssDataClasses() {
|
|
|
54
60
|
color: var(--sw-color-danger);
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
.sw-badge-warning {
|
|
64
|
+
background: transparent;
|
|
65
|
+
border-color: var(--sw-color-warning);
|
|
66
|
+
color: var(--sw-color-warning);
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
.sw-segmented {
|
|
58
70
|
background: var(--sw-glass-fill);
|
|
59
71
|
border: 1px solid var(--sw-glass-border);
|
|
@@ -127,6 +139,11 @@ export function cssDataClasses() {
|
|
|
127
139
|
width: 100%;
|
|
128
140
|
}
|
|
129
141
|
|
|
142
|
+
.sw-table-wrap:focus-visible {
|
|
143
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
144
|
+
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
145
|
+
}
|
|
146
|
+
|
|
130
147
|
.sw-table {
|
|
131
148
|
border-collapse: collapse;
|
|
132
149
|
width: 100%;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const statTileTones: readonly ["default", "accent", "success", "danger", "warning"];
|
|
2
|
+
export type StatTileTone = (typeof statTileTones)[number];
|
|
3
|
+
/** The figure tile: a glyph circle before a label, a tabular value and a caption. Text colours come from `Text color`. */
|
|
4
|
+
export declare function cssStatTileClasses(): string;
|
|
5
|
+
export declare function statTileClassCatalog(): string[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const statTileTones = [
|
|
2
|
+
'default',
|
|
3
|
+
'accent',
|
|
4
|
+
'success',
|
|
5
|
+
'danger',
|
|
6
|
+
'warning',
|
|
7
|
+
];
|
|
8
|
+
/** The figure tile: a glyph circle before a label, a tabular value and a caption. Text colours come from `Text color`. */
|
|
9
|
+
export function cssStatTileClasses() {
|
|
10
|
+
return `.sw-stat-tile {
|
|
11
|
+
align-items: center;
|
|
12
|
+
background: var(--sw-color-surface);
|
|
13
|
+
border: 1px solid var(--sw-color-border);
|
|
14
|
+
border-radius: var(--sw-radius-lg);
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
color: var(--sw-color-text);
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: var(--sw-space-3);
|
|
19
|
+
min-width: 0;
|
|
20
|
+
padding: var(--sw-space-4);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sw-stat-tile-glyph {
|
|
24
|
+
align-items: center;
|
|
25
|
+
background: var(--sw-glass-fill);
|
|
26
|
+
border: 1px solid var(--sw-color-border);
|
|
27
|
+
border-radius: var(--sw-radius-pill);
|
|
28
|
+
color: var(--sw-color-accent);
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
flex: none;
|
|
31
|
+
height: var(--sw-control-md-min-height);
|
|
32
|
+
justify-content: center;
|
|
33
|
+
width: var(--sw-control-md-min-height);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sw-stat-tile-body {
|
|
37
|
+
display: grid;
|
|
38
|
+
gap: var(--sw-space-1);
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sw-stat-tile-value {
|
|
43
|
+
font-variant-numeric: tabular-nums;
|
|
44
|
+
overflow-wrap: anywhere;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sw-stat-tile-primary {
|
|
48
|
+
background: var(--sw-color-accent);
|
|
49
|
+
border-color: transparent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sw-stat-tile-primary .sw-stat-tile-glyph {
|
|
53
|
+
background: transparent;
|
|
54
|
+
border-color: var(--sw-color-onAccent);
|
|
55
|
+
color: var(--sw-color-onAccent);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media (forced-colors: active) {
|
|
59
|
+
.sw-stat-tile { border-color: CanvasText; }
|
|
60
|
+
.sw-stat-tile-primary { background: Highlight; }
|
|
61
|
+
}`;
|
|
62
|
+
}
|
|
63
|
+
export function statTileClassCatalog() {
|
|
64
|
+
return [
|
|
65
|
+
'sw-stat-tile',
|
|
66
|
+
'sw-stat-tile-primary',
|
|
67
|
+
'sw-stat-tile-glyph',
|
|
68
|
+
'sw-stat-tile-body',
|
|
69
|
+
'sw-stat-tile-label',
|
|
70
|
+
'sw-stat-tile-value',
|
|
71
|
+
'sw-stat-tile-caption',
|
|
72
|
+
];
|
|
73
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
const label = typographyVariants.label;
|
|
3
|
+
/** The tab strip: a scrollable row of tabs with an accent underline under the current one. */
|
|
4
|
+
export function cssTabsClasses() {
|
|
5
|
+
return `.sw-tabs {
|
|
6
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
7
|
+
display: flex;
|
|
8
|
+
gap: var(--sw-space-1);
|
|
9
|
+
margin: 0;
|
|
10
|
+
overflow-x: auto;
|
|
11
|
+
padding: 0;
|
|
12
|
+
scrollbar-width: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sw-tabs::-webkit-scrollbar {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sw-tab {
|
|
20
|
+
appearance: none;
|
|
21
|
+
background: transparent;
|
|
22
|
+
border: 0;
|
|
23
|
+
border-bottom: 2px solid transparent;
|
|
24
|
+
border-radius: var(--sw-radius-sm) var(--sw-radius-sm) 0 0;
|
|
25
|
+
color: var(--sw-color-muted);
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
flex: none;
|
|
28
|
+
font-family: var(--sw-font-sans);
|
|
29
|
+
font-size: ${label.fontSize}px;
|
|
30
|
+
font-weight: ${label.fontWeight};
|
|
31
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
32
|
+
line-height: ${label.lineHeight}px;
|
|
33
|
+
margin-bottom: -1px;
|
|
34
|
+
min-height: var(--sw-control-md-min-height);
|
|
35
|
+
padding-inline: var(--sw-control-md-padding-inline);
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.sw-tab:hover {
|
|
40
|
+
color: var(--sw-color-text);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sw-tab:focus-visible {
|
|
44
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
45
|
+
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sw-tab-selected {
|
|
49
|
+
border-bottom-color: var(--sw-color-accent);
|
|
50
|
+
color: var(--sw-color-accent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sw-tab-panel:focus-visible {
|
|
54
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
55
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media (forced-colors: active) {
|
|
59
|
+
.sw-tab-selected { border-bottom-color: Highlight; color: Highlight; }
|
|
60
|
+
}`;
|
|
61
|
+
}
|
|
62
|
+
export function tabsClassCatalog() {
|
|
63
|
+
return ['sw-tabs', 'sw-tab', 'sw-tab-selected', 'sw-tab-panel'];
|
|
64
|
+
}
|
package/dist/css/stylesheet.js
CHANGED
|
@@ -9,6 +9,8 @@ import { spinnerClassCatalog } from './css-spinner.js';
|
|
|
9
9
|
import { progressClassCatalog } from './css-progress.js';
|
|
10
10
|
import { tooltipClassCatalog } from './css-tooltip.js';
|
|
11
11
|
import { separatorClassCatalog } from './css-separator.js';
|
|
12
|
+
import { statTileClassCatalog } from './css-stat-tile.js';
|
|
13
|
+
import { tabsClassCatalog } from './css-tabs.js';
|
|
12
14
|
import { responsiveClassCatalog } from './css-responsive.js';
|
|
13
15
|
import { filterChipsClassCatalog } from './css-filter-chips.js';
|
|
14
16
|
import { gridClassCatalog } from './css-grid.js';
|
|
@@ -57,6 +59,8 @@ export function utilityClassCatalog() {
|
|
|
57
59
|
...progressClassCatalog(),
|
|
58
60
|
...tooltipClassCatalog(),
|
|
59
61
|
...separatorClassCatalog(),
|
|
62
|
+
...statTileClassCatalog(),
|
|
63
|
+
...tabsClassCatalog(),
|
|
60
64
|
...filterChipsClassCatalog(),
|
|
61
65
|
...gridClassCatalog(),
|
|
62
66
|
...denominationGridClassCatalog(),
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export { Box, type BoxElement, type BoxProps } from './components/Box.js';
|
|
|
16
16
|
export { ButtonGroup, type ButtonGroupJustify, type ButtonGroupProps, } from './components/ButtonGroup.js';
|
|
17
17
|
export { Card, type CardProps, type CardVariant } from './components/Card.js';
|
|
18
18
|
export { Dialog, type DialogProps, type DialogSize, } from './components/Dialog.js';
|
|
19
|
+
export { StatTile, type StatTileEmphasis, type StatTileProps, type StatTileTone, statTileTones, } from './components/StatTile.js';
|
|
20
|
+
export { TabPanel, type TabItem, type TabPanelProps, Tabs, type TabsProps, tabId, tabPanelId, } from './components/Tabs.js';
|
|
19
21
|
export { Toast } from './components/Toast.js';
|
|
20
22
|
export type { ToastProps } from './components/Toast.js';
|
|
21
23
|
export { Field, type FieldProps, type FieldSize } from './components/Field.js';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ export { Box } from './components/Box.js';
|
|
|
15
15
|
export { ButtonGroup, } from './components/ButtonGroup.js';
|
|
16
16
|
export { Card } from './components/Card.js';
|
|
17
17
|
export { Dialog, } from './components/Dialog.js';
|
|
18
|
+
export { StatTile, statTileTones, } from './components/StatTile.js';
|
|
19
|
+
export { TabPanel, Tabs, tabId, tabPanelId, } from './components/Tabs.js';
|
|
18
20
|
export { Toast } from './components/Toast.js';
|
|
19
21
|
export { Field } from './components/Field.js';
|
|
20
22
|
export { Inline } from './components/Inline.js';
|
package/dist/styles.css
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
--sw-color-danger: #B42318;
|
|
11
11
|
--sw-color-onDanger: #FFFFFF;
|
|
12
12
|
--sw-color-success: #087443;
|
|
13
|
+
--sw-color-warning: #B54708;
|
|
13
14
|
--sw-color-border: #D2D2D7;
|
|
14
15
|
--sw-space-0: 0px;
|
|
15
16
|
--sw-space-1: 4px;
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
--sw-color-danger: #F97066;
|
|
67
68
|
--sw-color-onDanger: #101214;
|
|
68
69
|
--sw-color-success: #5BE0A0;
|
|
70
|
+
--sw-color-warning: #FDB022;
|
|
69
71
|
--sw-color-border: #3A3A3C;
|
|
70
72
|
--sw-space-0: 0px;
|
|
71
73
|
--sw-space-1: 4px;
|
|
@@ -680,6 +682,13 @@ body {
|
|
|
680
682
|
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
681
683
|
}
|
|
682
684
|
|
|
685
|
+
/* Backdrop blur makes a glass surface its own stacking context, so an open popup inside it would paint under the next surface. */
|
|
686
|
+
.sw-card-glass:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])),
|
|
687
|
+
.sw-accordion:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])) {
|
|
688
|
+
position: relative;
|
|
689
|
+
z-index: 1;
|
|
690
|
+
}
|
|
691
|
+
|
|
683
692
|
.sw-card-outlined {
|
|
684
693
|
background: var(--sw-color-surface);
|
|
685
694
|
border: 1px solid var(--sw-color-border);
|
|
@@ -1072,6 +1081,12 @@ body {
|
|
|
1072
1081
|
color: var(--sw-color-danger);
|
|
1073
1082
|
}
|
|
1074
1083
|
|
|
1084
|
+
.sw-badge-warning {
|
|
1085
|
+
background: transparent;
|
|
1086
|
+
border-color: var(--sw-color-warning);
|
|
1087
|
+
color: var(--sw-color-warning);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1075
1090
|
.sw-segmented {
|
|
1076
1091
|
background: var(--sw-glass-fill);
|
|
1077
1092
|
border: 1px solid var(--sw-glass-border);
|
|
@@ -1145,6 +1160,11 @@ body {
|
|
|
1145
1160
|
width: 100%;
|
|
1146
1161
|
}
|
|
1147
1162
|
|
|
1163
|
+
.sw-table-wrap:focus-visible {
|
|
1164
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
1165
|
+
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1148
1168
|
.sw-table {
|
|
1149
1169
|
border-collapse: collapse;
|
|
1150
1170
|
width: 100%;
|
|
@@ -1920,6 +1940,116 @@ body {
|
|
|
1920
1940
|
.sw-separator { background: CanvasText; }
|
|
1921
1941
|
}
|
|
1922
1942
|
|
|
1943
|
+
.sw-stat-tile {
|
|
1944
|
+
align-items: center;
|
|
1945
|
+
background: var(--sw-color-surface);
|
|
1946
|
+
border: 1px solid var(--sw-color-border);
|
|
1947
|
+
border-radius: var(--sw-radius-lg);
|
|
1948
|
+
box-sizing: border-box;
|
|
1949
|
+
color: var(--sw-color-text);
|
|
1950
|
+
display: flex;
|
|
1951
|
+
gap: var(--sw-space-3);
|
|
1952
|
+
min-width: 0;
|
|
1953
|
+
padding: var(--sw-space-4);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
.sw-stat-tile-glyph {
|
|
1957
|
+
align-items: center;
|
|
1958
|
+
background: var(--sw-glass-fill);
|
|
1959
|
+
border: 1px solid var(--sw-color-border);
|
|
1960
|
+
border-radius: var(--sw-radius-pill);
|
|
1961
|
+
color: var(--sw-color-accent);
|
|
1962
|
+
display: inline-flex;
|
|
1963
|
+
flex: none;
|
|
1964
|
+
height: var(--sw-control-md-min-height);
|
|
1965
|
+
justify-content: center;
|
|
1966
|
+
width: var(--sw-control-md-min-height);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
.sw-stat-tile-body {
|
|
1970
|
+
display: grid;
|
|
1971
|
+
gap: var(--sw-space-1);
|
|
1972
|
+
min-width: 0;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
.sw-stat-tile-value {
|
|
1976
|
+
font-variant-numeric: tabular-nums;
|
|
1977
|
+
overflow-wrap: anywhere;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
.sw-stat-tile-primary {
|
|
1981
|
+
background: var(--sw-color-accent);
|
|
1982
|
+
border-color: transparent;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
.sw-stat-tile-primary .sw-stat-tile-glyph {
|
|
1986
|
+
background: transparent;
|
|
1987
|
+
border-color: var(--sw-color-onAccent);
|
|
1988
|
+
color: var(--sw-color-onAccent);
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
@media (forced-colors: active) {
|
|
1992
|
+
.sw-stat-tile { border-color: CanvasText; }
|
|
1993
|
+
.sw-stat-tile-primary { background: Highlight; }
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
.sw-tabs {
|
|
1997
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
1998
|
+
display: flex;
|
|
1999
|
+
gap: var(--sw-space-1);
|
|
2000
|
+
margin: 0;
|
|
2001
|
+
overflow-x: auto;
|
|
2002
|
+
padding: 0;
|
|
2003
|
+
scrollbar-width: none;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
.sw-tabs::-webkit-scrollbar {
|
|
2007
|
+
display: none;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
.sw-tab {
|
|
2011
|
+
appearance: none;
|
|
2012
|
+
background: transparent;
|
|
2013
|
+
border: 0;
|
|
2014
|
+
border-bottom: 2px solid transparent;
|
|
2015
|
+
border-radius: var(--sw-radius-sm) var(--sw-radius-sm) 0 0;
|
|
2016
|
+
color: var(--sw-color-muted);
|
|
2017
|
+
cursor: pointer;
|
|
2018
|
+
flex: none;
|
|
2019
|
+
font-family: var(--sw-font-sans);
|
|
2020
|
+
font-size: 15px;
|
|
2021
|
+
font-weight: 600;
|
|
2022
|
+
letter-spacing: -0.2px;
|
|
2023
|
+
line-height: 20px;
|
|
2024
|
+
margin-bottom: -1px;
|
|
2025
|
+
min-height: var(--sw-control-md-min-height);
|
|
2026
|
+
padding-inline: var(--sw-control-md-padding-inline);
|
|
2027
|
+
white-space: nowrap;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
.sw-tab:hover {
|
|
2031
|
+
color: var(--sw-color-text);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
.sw-tab:focus-visible {
|
|
2035
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
2036
|
+
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
.sw-tab-selected {
|
|
2040
|
+
border-bottom-color: var(--sw-color-accent);
|
|
2041
|
+
color: var(--sw-color-accent);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.sw-tab-panel:focus-visible {
|
|
2045
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
2046
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
@media (forced-colors: active) {
|
|
2050
|
+
.sw-tab-selected { border-bottom-color: Highlight; color: Highlight; }
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1923
2053
|
.sw-filter-chips {
|
|
1924
2054
|
border: 0;
|
|
1925
2055
|
margin: 0;
|
|
@@ -2047,6 +2177,7 @@ body {
|
|
|
2047
2177
|
.sw-denomination-row-accent { --sw-denomination-tone: var(--sw-color-accent); }
|
|
2048
2178
|
.sw-denomination-row-success { --sw-denomination-tone: var(--sw-color-success); }
|
|
2049
2179
|
.sw-denomination-row-danger { --sw-denomination-tone: var(--sw-color-danger); }
|
|
2180
|
+
.sw-denomination-row-warning { --sw-denomination-tone: var(--sw-color-warning); }
|
|
2050
2181
|
|
|
2051
2182
|
.sw-denomination-strip {
|
|
2052
2183
|
border-collapse: collapse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalewing/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Scalewing DOM components and package-owned CSS utilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"README.md"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@scalewing/tokens": "^1.
|
|
41
|
+
"@scalewing/tokens": "^1.2.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": "^19.0.0",
|