@scalewing/react 1.22.0 → 1.23.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 +7 -0
- package/README.md +3 -1
- package/dist/css/breakpoints.d.ts +24 -3
- package/dist/css/breakpoints.js +27 -1
- package/dist/css/css-chrome.js +13 -0
- package/dist/css/css-responsive.js +4 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/styles.css +20 -0
- package/dist/visibility-classes.d.ts +3 -3
- package/dist/visibility-classes.js +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f51d196: A `Button` inside `Nav` is at least 44 × 44 px on a coarse pointer (`docs/requests/teisoro-nav.md`, Teisoro F-006-S11, UX-16), whatever its `size`, as `SectionNav` links, `CalendarButton` and the `ActionMenu` trigger already are; a fine pointer is unchanged. Only the minimum height and width grow, so a label beside its glyph keeps its line. No new classes, tokens or dependencies.
|
|
8
|
+
- f51d196: `hideBelow` and `hideFrom` take `lg` (64rem) (`docs/requests/teisoro-responsive-visibility.md`, 2026-10-09 follow-up, Teisoro F-006-S11, SET-3 and UX-16), for content that fits only from a laptop up, such as a row of labelled destinations a tablet shows as glyphs. New generated classes `sw-hide-below-lg` and `sw-hide-from-lg`; new exported types `VisibilityBreakpoint` (`'md' | 'lg'`) and `BreakpointDirection` (`'from' | 'below'`). The layout props keep the one layout breakpoint, `md`. `breakpointQuery(direction, breakpoint)` is now exported: the media query a hide class uses, so an app's `matchMedia` follows the same width; an unknown direction or breakpoint throws a `RangeError`. An unknown `hideBelow` or `hideFrom` now throws a `RangeError` (it produced a class with no rule). No new tokens and no new dependencies.
|
|
9
|
+
|
|
3
10
|
## 1.22.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ Import the CSS once at the application entry. Do not copy it into your source tr
|
|
|
49
49
|
|
|
50
50
|
`Box` `border` draws a hairline in the `border` color token: `border` (or `border={true}`) is solid, and `border="dashed"` is dashed (the generated `sw-border` and `sw-border-dashed` classes; a `style` you pass still wins), for a space to fill in by hand such as a blank on a printed form. It does not make the box look or act like an input. Every `Box`-based component takes it.
|
|
51
51
|
|
|
52
|
-
`Box hideBelow="md"` hides a region under 48rem and `hideFrom="md"` hides it at 48rem and wider, so a product can swap a wide layout for a narrow one without its own stylesheet. Hidden regions leave the accessibility tree; do not use this to protect data. `Box as="a"` is a layout link. Use `Button` for press actions. `Field` associates a native `<input>` or `<select>` with a label and token gap; native text controls inherit the generated document canvas. `Select` is a labeled listbox menu when the open list must match the canvas.
|
|
52
|
+
`Box hideBelow="md"` hides a region under 48rem and `hideFrom="md"` hides it at 48rem and wider, so a product can swap a wide layout for a narrow one without its own stylesheet. They also take `lg` (64rem), for content that fits only from a laptop up, such as a row of labelled destinations that a tablet shows as glyphs (name each glyph-only control, for example with `Tooltip relationship="label"`); the layout props keep `md`. `breakpointQuery('below', 'lg')` returns the query a hide class uses (`not all and (min-width: 64rem)`), for an app whose script must follow the same width. An unknown breakpoint throws a `RangeError`. Hidden regions leave the accessibility tree; do not use this to protect data. `Box as="a"` is a layout link. Use `Button` for press actions. `Field` associates a native `<input>` or `<select>` with a label and token gap; native text controls inherit the generated document canvas. `Select` is a labeled listbox menu when the open list must match the canvas.
|
|
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
|
|
|
@@ -244,6 +244,8 @@ An option's optional `description` (a `ReactNode`) is secondary text for that op
|
|
|
244
244
|
|
|
245
245
|
`Tabs` is the strip for the sections of one page (`role="tablist"`, one `role="tab"` button per item; arrow keys, Home and End move and select), and `TabPanel` is each section's content, hidden while another tab is current. `sticky` (default `false`) keeps the strip at the top of the viewport, under `env(safe-area-inset-top)`, while a long panel scrolls under it, on the same layer as a sticky `AppHeader` (so a page uses one or the other at the top edge). The stuck strip is a full-bleed band of the page canvas, not a glass card: the canvas color at nine parts in ten over the glass blur, with the strip's hairline under it, solid canvas under Reduce Transparency and in forced colors. A sticky element only sticks within its parent: make the strip a direct child of the long page container that also holds the panels, not of a padded `Box` round the strip alone, and keep `overflow` visible on its ancestors. Put the page's side gutter on the title and the panels instead: each tab's inline padding is the md control's 16 px (spacing step 4), so the first label lines up with a `space-4` gutter when the strip runs edge to edge. A strip whose labels do not fit scrolls sideways, and while labels are scrolled out past an inline edge that edge draws the same inset shade as a wide `Table` (none in forced colors), so a phone shows there are more tabs.
|
|
246
246
|
|
|
247
|
+
`Nav` is a labelled `nav` of a workspace's destinations, links or buttons one token gap apart. A `Button` inside it is at least 44 × 44 px on a coarse pointer, whatever its `size`, so a compact row of destinations is still a touch target on a phone or a tablet.
|
|
248
|
+
|
|
247
249
|
`SectionNav` is the navigation between the sections of one area, such as a portal's pages: a labelled `nav` (`aria-label` or `aria-labelledby`) of links from `items` (`{ id, label, href, current?, icon? }`), quieter than a workspace's `Nav`. `current` is `'page'`, `'location'` (a page inside that section, such as a detail page under its list) or a boolean, written as `aria-current`; the current item is in the text color over an accent underline, or, from `verticalFrom` (a breakpoint) up, where the items stack as a side list, a bar at its start. `icon` is a decorative glyph before the label. `onNavigate` takes a plain press after preventing the browser's navigation, for a client router; a press with a modifier key or the middle button keeps the browser's behavior, and without `onNavigate` the links carry no handler, so a server component can render the nav. A coarse pointer gets 44 px targets. Empty or duplicate ids, empty labels and an unknown `verticalFrom` throw a `RangeError`. Use `Tabs` for panels on one page.
|
|
248
250
|
|
|
249
251
|
```tsx
|
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
/** The layout breakpoint: a phone below it, a wider screen from it. */
|
|
1
2
|
export declare const breakpointScale: {
|
|
2
3
|
readonly md: 48;
|
|
3
4
|
};
|
|
4
5
|
export type Breakpoint = keyof typeof breakpointScale;
|
|
5
6
|
export declare const breakpoints: Breakpoint[];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The breakpoints `hideBelow` and `hideFrom` take: `md`, and `lg` (64rem)
|
|
9
|
+
* for content that fits only from a laptop up, such as a row of labelled
|
|
10
|
+
* destinations a tablet shows as glyphs. The layout props (`columnsBelow`,
|
|
11
|
+
* `stickyBelow`, `verticalFrom`, …) keep the one layout breakpoint.
|
|
12
|
+
*/
|
|
13
|
+
export declare const visibilityBreakpointScale: {
|
|
14
|
+
readonly lg: 64;
|
|
15
|
+
readonly md: 48;
|
|
16
|
+
};
|
|
17
|
+
export type VisibilityBreakpoint = keyof typeof visibilityBreakpointScale;
|
|
18
|
+
export declare const visibilityBreakpoints: VisibilityBreakpoint[];
|
|
19
|
+
/** Which side of a breakpoint: `from` it and wider, or `below` it. */
|
|
20
|
+
export type BreakpointDirection = 'from' | 'below';
|
|
21
|
+
export declare const breakpointDirections: readonly BreakpointDirection[];
|
|
22
|
+
export declare function hideClass(direction: BreakpointDirection, breakpoint: VisibilityBreakpoint): string;
|
|
23
|
+
/**
|
|
24
|
+
* The media query `hideBelow` (`'below'`) or `hideFrom` (`'from'`) uses at a
|
|
25
|
+
* breakpoint, such as `not all and (min-width: 64rem)` below `lg`, for an app
|
|
26
|
+
* that must follow the same width in script (a tooltip that names a glyph
|
|
27
|
+
* only while its label is hidden).
|
|
28
|
+
*/
|
|
29
|
+
export declare function breakpointQuery(direction: BreakpointDirection, breakpoint: VisibilityBreakpoint): string;
|
package/dist/css/breakpoints.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
|
+
/** The layout breakpoint: a phone below it, a wider screen from it. */
|
|
1
2
|
export const breakpointScale = {
|
|
2
3
|
md: 48,
|
|
3
4
|
};
|
|
4
5
|
export const breakpoints = Object.keys(breakpointScale);
|
|
6
|
+
/**
|
|
7
|
+
* The breakpoints `hideBelow` and `hideFrom` take: `md`, and `lg` (64rem)
|
|
8
|
+
* for content that fits only from a laptop up, such as a row of labelled
|
|
9
|
+
* destinations a tablet shows as glyphs. The layout props (`columnsBelow`,
|
|
10
|
+
* `stickyBelow`, `verticalFrom`, …) keep the one layout breakpoint.
|
|
11
|
+
*/
|
|
12
|
+
export const visibilityBreakpointScale = {
|
|
13
|
+
...breakpointScale,
|
|
14
|
+
lg: 64,
|
|
15
|
+
};
|
|
16
|
+
export const visibilityBreakpoints = Object.keys(visibilityBreakpointScale);
|
|
17
|
+
export const breakpointDirections = [
|
|
18
|
+
'from',
|
|
19
|
+
'below',
|
|
20
|
+
];
|
|
5
21
|
export function hideClass(direction, breakpoint) {
|
|
6
22
|
return `sw-hide-${direction}-${breakpoint}`;
|
|
7
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The media query `hideBelow` (`'below'`) or `hideFrom` (`'from'`) uses at a
|
|
26
|
+
* breakpoint, such as `not all and (min-width: 64rem)` below `lg`, for an app
|
|
27
|
+
* that must follow the same width in script (a tooltip that names a glyph
|
|
28
|
+
* only while its label is hidden).
|
|
29
|
+
*/
|
|
8
30
|
export function breakpointQuery(direction, breakpoint) {
|
|
9
|
-
|
|
31
|
+
if (!breakpointDirections.includes(direction))
|
|
32
|
+
throw new RangeError(`direction must be one of ${breakpointDirections.join(', ')}`);
|
|
33
|
+
if (!visibilityBreakpoints.includes(breakpoint))
|
|
34
|
+
throw new RangeError(`breakpoint must be one of ${visibilityBreakpoints.join(', ')}`);
|
|
35
|
+
const minWidth = `(min-width: ${visibilityBreakpointScale[breakpoint]}rem)`;
|
|
10
36
|
return direction === 'from' ? minWidth : `not all and ${minWidth}`;
|
|
11
37
|
}
|
package/dist/css/css-chrome.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { zIndex } from './stacking.js';
|
|
3
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
3
4
|
const caption = typographyVariants.caption;
|
|
4
5
|
const label = typographyVariants.label;
|
|
5
6
|
export function cssChromeClasses() {
|
|
@@ -36,6 +37,18 @@ export function cssChromeClasses() {
|
|
|
36
37
|
color: var(--sw-color-text);
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/*
|
|
41
|
+
* A button in a navigation is a destination: on a coarse pointer it grows to
|
|
42
|
+
* the 44 px touch target both ways, whatever its size, as a section link
|
|
43
|
+
* and a calendar button do. A label beside its glyph keeps its line.
|
|
44
|
+
*/
|
|
45
|
+
@media ${coarsePointerQuery} {
|
|
46
|
+
.sw-nav .sw-button {
|
|
47
|
+
min-height: ${touchTarget};
|
|
48
|
+
min-width: ${touchTarget};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
.sw-field-xs :is(select, input, textarea) {
|
|
40
53
|
font-size: ${caption.fontSize}px;
|
|
41
54
|
letter-spacing: ${caption.letterSpacing}px;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const directions = ['from', 'below'];
|
|
1
|
+
import { breakpointDirections, breakpointQuery, hideClass, visibilityBreakpoints, } from './breakpoints.js';
|
|
3
2
|
export function cssResponsiveClasses() {
|
|
4
|
-
return
|
|
5
|
-
.flatMap((breakpoint) =>
|
|
3
|
+
return visibilityBreakpoints
|
|
4
|
+
.flatMap((breakpoint) => breakpointDirections.map((direction) => `@media ${breakpointQuery(direction, breakpoint)} {
|
|
6
5
|
.${hideClass(direction, breakpoint)} { display: none; }
|
|
7
6
|
}`))
|
|
8
7
|
.join('\n\n');
|
|
9
8
|
}
|
|
10
9
|
export function responsiveClassCatalog() {
|
|
11
|
-
return
|
|
10
|
+
return visibilityBreakpoints.flatMap((breakpoint) => breakpointDirections.map((direction) => hideClass(direction, breakpoint)));
|
|
12
11
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,8 @@ export { Text, type TextAlign, type TextElement, type TextProps, } from './compo
|
|
|
37
37
|
export { ThemeProvider, useTheme, type ThemeProviderProps, } from './theme/ThemeProvider.js';
|
|
38
38
|
export { utilityClassCatalog } from './css/stylesheet.js';
|
|
39
39
|
export type { VisibilityProps } from './visibility-classes.js';
|
|
40
|
-
export type { Breakpoint } from './css/breakpoints.js';
|
|
40
|
+
export type { Breakpoint, BreakpointDirection, VisibilityBreakpoint, } from './css/breakpoints.js';
|
|
41
|
+
export { breakpointQuery } from './css/breakpoints.js';
|
|
41
42
|
export { Separator } from './components/Separator.js';
|
|
42
43
|
export type { SeparatorOrientation, SeparatorProps, } from './components/Separator.js';
|
|
43
44
|
export { Grid } from './components/Grid.js';
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export { Table, TableBody, TableCell, TableHeader, TableRow, } from './component
|
|
|
32
32
|
export { Text, } from './components/Text.js';
|
|
33
33
|
export { ThemeProvider, useTheme, } from './theme/ThemeProvider.js';
|
|
34
34
|
export { utilityClassCatalog } from './css/stylesheet.js';
|
|
35
|
+
export { breakpointQuery } from './css/breakpoints.js';
|
|
35
36
|
export { Separator } from './components/Separator.js';
|
|
36
37
|
export { Grid } from './components/Grid.js';
|
|
37
38
|
export { DenominationGrid } from './components/DenominationGrid.js';
|
package/dist/styles.css
CHANGED
|
@@ -2041,6 +2041,18 @@ body {
|
|
|
2041
2041
|
color: var(--sw-color-text);
|
|
2042
2042
|
}
|
|
2043
2043
|
|
|
2044
|
+
/*
|
|
2045
|
+
* A button in a navigation is a destination: on a coarse pointer it grows to
|
|
2046
|
+
* the 44 px touch target both ways, whatever its size, as a section link
|
|
2047
|
+
* and a calendar button do. A label beside its glyph keeps its line.
|
|
2048
|
+
*/
|
|
2049
|
+
@media (pointer: coarse) {
|
|
2050
|
+
.sw-nav .sw-button {
|
|
2051
|
+
min-height: var(--sw-control-md-min-height);
|
|
2052
|
+
min-width: var(--sw-control-md-min-height);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2044
2056
|
.sw-field-xs :is(select, input, textarea) {
|
|
2045
2057
|
font-size: 13px;
|
|
2046
2058
|
letter-spacing: -0.08px;
|
|
@@ -3873,6 +3885,14 @@ body {
|
|
|
3873
3885
|
.sw-hide-below-md { display: none; }
|
|
3874
3886
|
}
|
|
3875
3887
|
|
|
3888
|
+
@media (min-width: 64rem) {
|
|
3889
|
+
.sw-hide-from-lg { display: none; }
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
@media not all and (min-width: 64rem) {
|
|
3893
|
+
.sw-hide-below-lg { display: none; }
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3876
3896
|
.sw-border { border: 1px solid var(--sw-color-border); }
|
|
3877
3897
|
.sw-border-dashed { border: 1px dashed var(--sw-color-border); }
|
|
3878
3898
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type VisibilityBreakpoint } from './css/breakpoints.js';
|
|
2
2
|
export type VisibilityProps = {
|
|
3
|
-
hideBelow?:
|
|
4
|
-
hideFrom?:
|
|
3
|
+
hideBelow?: VisibilityBreakpoint;
|
|
4
|
+
hideFrom?: VisibilityBreakpoint;
|
|
5
5
|
};
|
|
6
6
|
export declare function visibilityClassNames({ hideBelow, hideFrom, }: VisibilityProps): string[];
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { hideClass } from './css/breakpoints.js';
|
|
1
|
+
import { hideClass, visibilityBreakpoints, } from './css/breakpoints.js';
|
|
2
|
+
function assertBreakpoint(prop, breakpoint) {
|
|
3
|
+
if (breakpoint && !visibilityBreakpoints.includes(breakpoint))
|
|
4
|
+
throw new RangeError(`${prop} must be one of ${visibilityBreakpoints.join(', ')}`);
|
|
5
|
+
}
|
|
2
6
|
export function visibilityClassNames({ hideBelow, hideFrom, }) {
|
|
7
|
+
assertBreakpoint('hideBelow', hideBelow);
|
|
8
|
+
assertBreakpoint('hideFrom', hideFrom);
|
|
3
9
|
return [
|
|
4
10
|
...(hideBelow ? [hideClass('below', hideBelow)] : []),
|
|
5
11
|
...(hideFrom ? [hideClass('from', hideFrom)] : []),
|