@scalewing/react 1.59.4 → 1.60.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 1.60.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 69e52f9: `PreviewImage`: a real screenshot inside a `ScreenPreview`, at the screen's width with its height following. Decorative, as the preview's `label` names the image (Teisoro F-017 task 2730).
8
+
9
+ ## 1.59.5
10
+
11
+ ### Patch Changes
12
+
13
+ - e06c1e2: Forced colours show every choice. The chosen `FilterChips` chip sets `forced-color-adjust: none` on its Highlight face, so Chromium's Canvas backplate no longer hides its HighlightText words, and every `SegmentedControl` variant (plain and filled, not only the icon and raised tracks) outlines its selected item in Highlight. No API change.
14
+
3
15
  ## 1.59.4
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -39,6 +39,7 @@ import {
39
39
  Hero,
40
40
  ScreenPreview,
41
41
  PreviewGroup,
42
+ PreviewImage,
42
43
  PreviewRow,
43
44
  PreviewTiles,
44
45
  SiteFooter,
@@ -1176,6 +1177,8 @@ const { headings, omissions } = inspectMarkdown(page.body, {
1176
1177
 
1177
1178
  `ScreenPreview` is a drawn stand-in for an app's screen on a public page, until a real screenshot replaces it: a `figure` whose screen is one image (`role="img"`) named by `label`, its alternative text, so a screen reader hears that sentence instead of the rows; an optional `caption` shows under it as the `figcaption`. An optional `eyebrow` and `title` head the screen. Its content is `PreviewGroup`s of `PreviewRow`s (`label` at the start, `value` at the end in tabular numerals; `kind` `plain`, `total` in bold past a hairline, `note` a small label over its words, `filled` on the quiet fill, or `alert` on the danger tint) and `PreviewTiles` (`items` of `{ id, label, value }`, three to a row on a phone, six from `md`). `frame="card"` (default) is a surface card whose groups are parted by hairlines; `frame="device"` draws a 20rem phone from `md` (a bezel in the text colour round a quiet screen with a surface header and each group a card) and the card below `md`. A blank `label`, an unknown `frame` or `kind`, no tiles, or a blank or repeated tile id throws a `RangeError`.
1178
1179
 
1180
+ When the real screenshot exists, a `PreviewImage` (`src`, `width`, `height`) holds it in the same frame, filling the screen's body with its height following. It is decorative: `label` stays the alternative text.
1181
+
1179
1182
  ```tsx
1180
1183
  <ScreenPreview
1181
1184
  frame="device"
@@ -12,14 +12,15 @@ export type ScreenPreviewProps = Omit<HTMLAttributes<HTMLElement>, 'children' |
12
12
  title?: ReactNode;
13
13
  /** A visible caption under the screen, such as what will replace it. */
14
14
  caption?: ReactNode;
15
- /** `PreviewGroup`s, `PreviewRow`s and `PreviewTiles`. */
15
+ /** `PreviewGroup`s, `PreviewRow`s, `PreviewTiles` or a `PreviewImage`. */
16
16
  children: ReactNode;
17
17
  };
18
18
  /**
19
19
  * A drawn stand-in for an app's screen on a public page: a `figure` whose
20
20
  * screen is one image (`role="img"`) named by `label`, so its rows are
21
21
  * read as that one sentence, with an optional visible `caption`. Its
22
- * content is `PreviewGroup`s of `PreviewRow`s and `PreviewTiles`. A blank
22
+ * content is `PreviewGroup`s of `PreviewRow`s, `PreviewTiles` or a
23
+ * `PreviewImage` holding a real screenshot. A blank
23
24
  * label or an unknown frame throws a `RangeError`.
24
25
  */
25
26
  export declare function ScreenPreview({ caption, children, className, eyebrow, frame, label, title, ...rest }: ScreenPreviewProps): import("react").JSX.Element;
@@ -7,7 +7,8 @@ export { screenPreviewFrames };
7
7
  * A drawn stand-in for an app's screen on a public page: a `figure` whose
8
8
  * screen is one image (`role="img"`) named by `label`, so its rows are
9
9
  * read as that one sentence, with an optional visible `caption`. Its
10
- * content is `PreviewGroup`s of `PreviewRow`s and `PreviewTiles`. A blank
10
+ * content is `PreviewGroup`s of `PreviewRow`s, `PreviewTiles` or a
11
+ * `PreviewImage` holding a real screenshot. A blank
11
12
  * label or an unknown frame throws a `RangeError`.
12
13
  */
13
14
  export function ScreenPreview({ caption, children, className, eyebrow, frame = 'card', label, title, ...rest }) {
@@ -0,0 +1,13 @@
1
+ /** The file and its natural size in pixels, so the page keeps its place while it loads. */
2
+ export type PreviewImageProps = {
3
+ src: string;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ /**
8
+ * A real screenshot inside a `ScreenPreview`, drawn at the width of the
9
+ * screen with its height following. It is decorative (`alt=""`): the
10
+ * preview's `label` already says what it shows. A blank `src` or a size
11
+ * that is not a positive whole number throws a `RangeError`.
12
+ */
13
+ export declare function PreviewImage({ height, src, width }: PreviewImageProps): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * A real screenshot inside a `ScreenPreview`, drawn at the width of the
4
+ * screen with its height following. It is decorative (`alt=""`): the
5
+ * preview's `label` already says what it shows. A blank `src` or a size
6
+ * that is not a positive whole number throws a `RangeError`.
7
+ */
8
+ export function PreviewImage({ height, src, width }) {
9
+ if (!src.trim())
10
+ throw new RangeError('A PreviewImage needs a src');
11
+ for (const [name, side] of [
12
+ ['width', width],
13
+ ['height', height],
14
+ ])
15
+ if (!Number.isInteger(side) || side <= 0)
16
+ throw new RangeError(`A PreviewImage's ${name} must be a positive whole number, not ${side}`);
17
+ return (_jsx("img", { alt: "", className: "sw-preview-image", height: height, loading: "lazy", src: src, width: width }));
18
+ }
@@ -135,7 +135,8 @@ ${cssFilterChipsRowClasses()}
135
135
  @media (forced-colors: active) {
136
136
  .sw-filter-chip-face { background: Canvas; border-color: CanvasText; color: CanvasText; }
137
137
  .sw-filter-chips-error { color: Mark; }
138
- .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
138
+ /* Chromium paints a Canvas backplate behind text in forced colours; HighlightText on it vanishes. */
139
+ .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; forced-color-adjust: none; }
139
140
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
140
141
  .sw-filter-chip-count { color: inherit; }
141
142
  /* After the checked rule, so every chip, the chosen one too, takes the edge. */
@@ -2,9 +2,11 @@ import { mutedReadsAsText, quietFill } from './quiet-fill.js';
2
2
  /*
3
3
  * The action sits after the row in its own column, centred on the row's
4
4
  * height, outside the row's press area, keeping the row's end padding
5
- * after it. Where :has() is supported the hover fill spans the whole item,
6
- * so the row, its select and its action read as one line; a detail under
7
- * the row is on that fill too, so its muted words take the text colour.
5
+ * after it. It ends at the column's end, so a short action on one row and
6
+ * a long one on the next finish on the same line. Where :has() is supported
7
+ * the hover fill spans the whole item, so the row, its select and its action
8
+ * read as one line; a detail under the row is on that fill too, so its muted
9
+ * words take the text colour.
8
10
  */
9
11
  function actionRules() {
10
12
  return `.sw-fixture-action {
@@ -13,6 +15,7 @@ function actionRules() {
13
15
  display: inline-flex;
14
16
  grid-column: -2 / -1;
15
17
  grid-row: 1;
18
+ justify-content: flex-end;
16
19
  padding-inline-end: var(--sw-space-2);
17
20
  }
18
21
 
@@ -87,6 +87,16 @@ function cardRules() {
87
87
  line-height: ${caption.lineHeight}px;
88
88
  }`;
89
89
  }
90
+ /* A screenshot fills the screen's width; its height follows. */
91
+ function imageRules() {
92
+ return `.sw-preview-image {
93
+ border-radius: var(--sw-radius-sm);
94
+ display: block;
95
+ height: auto;
96
+ max-width: 100%;
97
+ width: 100%;
98
+ }`;
99
+ }
90
100
  function rowRules() {
91
101
  return `.sw-preview-row {
92
102
  display: flex;
@@ -206,7 +216,7 @@ function wideRules() {
206
216
  }`;
207
217
  }
208
218
  export function cssScreenPreviewClasses() {
209
- return [cardRules(), rowRules(), wideRules()].join('\n\n');
219
+ return [cardRules(), imageRules(), rowRules(), wideRules()].join('\n\n');
210
220
  }
211
221
  export function screenPreviewClassCatalog() {
212
222
  return [
@@ -224,6 +234,7 @@ export function screenPreviewClassCatalog() {
224
234
  ...previewRowKinds.map(previewRowKindClass),
225
235
  'sw-preview-row-label',
226
236
  'sw-preview-row-value',
237
+ 'sw-preview-image',
227
238
  'sw-preview-tiles',
228
239
  'sw-preview-tile',
229
240
  'sw-preview-tile-label',
@@ -99,6 +99,8 @@ function fieldRules() {
99
99
 
100
100
  @media (forced-colors: active) {
101
101
  .sw-segmented[aria-invalid='true'] { border-color: Mark; }
102
+ /* Every variant's chosen item: its surface or accent fill is dropped. */
103
+ .sw-segmented-item-selected { outline: 2px solid Highlight; }
102
104
  }`;
103
105
  }
104
106
  export function cssSegmentedClasses() {
package/dist/index.d.ts CHANGED
@@ -90,6 +90,8 @@ export type { SiteHeaderItem, SiteHeaderLabels, SiteHeaderProps, SiteHeaderSkipL
90
90
  export { ScreenPreview, screenPreviewFrames, } from './components/ScreenPreview.js';
91
91
  export type { ScreenPreviewFrame, ScreenPreviewProps, } from './components/ScreenPreview.js';
92
92
  export { PreviewGroup } from './components/screen-preview/PreviewGroup.js';
93
+ export { PreviewImage } from './components/screen-preview/PreviewImage.js';
94
+ export type { PreviewImageProps } from './components/screen-preview/PreviewImage.js';
93
95
  export { PreviewRow, previewRowKinds, } from './components/screen-preview/PreviewRow.js';
94
96
  export type { PreviewRowKind, PreviewRowProps, } from './components/screen-preview/PreviewRow.js';
95
97
  export { PreviewTiles } from './components/screen-preview/PreviewTiles.js';
package/dist/index.js CHANGED
@@ -81,6 +81,7 @@ export { Band, bandElements, bandTones, bandWidths, } from './components/Band.js
81
81
  export { SiteHeader } from './components/SiteHeader.js';
82
82
  export { ScreenPreview, screenPreviewFrames, } from './components/ScreenPreview.js';
83
83
  export { PreviewGroup } from './components/screen-preview/PreviewGroup.js';
84
+ export { PreviewImage } from './components/screen-preview/PreviewImage.js';
84
85
  export { PreviewRow, previewRowKinds, } from './components/screen-preview/PreviewRow.js';
85
86
  export { PreviewTiles } from './components/screen-preview/PreviewTiles.js';
86
87
  export { Hero } from './components/Hero.js';
package/dist/styles.css CHANGED
@@ -1546,6 +1546,8 @@ body {
1546
1546
 
1547
1547
  @media (forced-colors: active) {
1548
1548
  .sw-segmented[aria-invalid='true'] { border-color: Mark; }
1549
+ /* Every variant's chosen item: its surface or accent fill is dropped. */
1550
+ .sw-segmented-item-selected { outline: 2px solid Highlight; }
1549
1551
  }
1550
1552
 
1551
1553
  .sw-segmented {
@@ -3616,6 +3618,7 @@ button.sw-date-toggle-day[aria-disabled='true'] {
3616
3618
  display: inline-flex;
3617
3619
  grid-column: -2 / -1;
3618
3620
  grid-row: 1;
3621
+ justify-content: flex-end;
3619
3622
  padding-inline-end: var(--sw-space-2);
3620
3623
  }
3621
3624
 
@@ -5297,6 +5300,14 @@ button.sw-date-toggle-day[aria-disabled='true'] {
5297
5300
  line-height: 18px;
5298
5301
  }
5299
5302
 
5303
+ .sw-preview-image {
5304
+ border-radius: var(--sw-radius-sm);
5305
+ display: block;
5306
+ height: auto;
5307
+ max-width: 100%;
5308
+ width: 100%;
5309
+ }
5310
+
5300
5311
  .sw-preview-row {
5301
5312
  display: flex;
5302
5313
  gap: var(--sw-space-3);
@@ -8953,7 +8964,8 @@ html:has(.sw-site-header-sticky) {
8953
8964
  @media (forced-colors: active) {
8954
8965
  .sw-filter-chip-face { background: Canvas; border-color: CanvasText; color: CanvasText; }
8955
8966
  .sw-filter-chips-error { color: Mark; }
8956
- .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
8967
+ /* Chromium paints a Canvas backplate behind text in forced colours; HighlightText on it vanishes. */
8968
+ .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; forced-color-adjust: none; }
8957
8969
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
8958
8970
  .sw-filter-chip-count { color: inherit; }
8959
8971
  /* After the checked rule, so every chip, the chosen one too, takes the edge. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalewing/react",
3
- "version": "1.59.4",
3
+ "version": "1.60.0",
4
4
  "description": "Scalewing DOM components and package-owned CSS utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",