@scalewing/react 1.4.0 → 1.5.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,40 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7b51094: Add `columnSpan` to `Box` and every component built on it (`Stack`, `Inline`,
8
+ `Card`, `Grid`, …), so a direct child of a `Grid` can cover 1, 2, 3, 4 or 6
9
+ columns (see `docs/requests/teisoro-grid-column-span.md`). Three columns with a
10
+ two-column child give a two-to-one page:
11
+
12
+ ```tsx
13
+ <Grid columns={3} columnsBelow={{ md: 1 }} gap={4}>
14
+ <Stack columnSpan={2} gap={4}>
15
+ {form}
16
+ </Stack>
17
+ <Card variant="outlined">{panel}</Card>
18
+ </Grid>
19
+ ```
20
+
21
+ The generated `sw-grid-span-{1,2,3,4,6}` classes are capped at the
22
+ columns the grid has at the current width, so a span never adds an implicit
23
+ column and `columnsBelow` still stacks the page on a phone. A span outside the
24
+ catalog throws a `RangeError`. New exported types: `GridColumnSpan`,
25
+ `ColumnSpanProps`. Additive; no existing class or prop changes.
26
+
27
+ ## 1.4.1
28
+
29
+ ### Patch Changes
30
+
31
+ - efdd079: An open `Select`, `ActionMenu` or `Tooltip` inside a glass `Card` or an
32
+ `Accordion` now paints over the surface below it (see
33
+ `docs/requests/teisoro-popup-stacking.md`). Backdrop blur makes each glass
34
+ surface its own stacking context, so the open list painted under the next card
35
+ and a click on a covered option reached that card's field instead. No prop
36
+ changes.
37
+
3
38
  ## 1.4.0
4
39
 
5
40
  ### Minor Changes
package/README.md CHANGED
@@ -68,7 +68,20 @@ Import the CSS once at the application entry. Do not copy it into your source tr
68
68
 
69
69
  `Text` takes an optional `align` (`start`, `center`, `end`) mapped to generated `sw-text-align-*` classes, for a heading that must stay centered when it wraps.
70
70
 
71
- `Grid` places children in one to four equal-width columns with a token `gap` step. `columnsBelow={{ md: 2 }}` drops to fewer columns below the `md` breakpoint so tiles and stat cards stay readable on a phone. It accepts every `Box` prop.
71
+ `Grid` places children in one to four (or six) equal-width columns with a token `gap` step. `columnsBelow={{ md: 2 }}` drops to fewer columns below the `md` breakpoint so tiles and stat cards stay readable on a phone. It accepts every `Box` prop.
72
+
73
+ Every `Box`-based component (`Box`, `Stack`, `Inline`, `Card`, `Grid`, …) takes `columnSpan` (1, 2, 3, 4 or 6) to cover several columns of the `Grid` it sits in directly, through the generated `sw-grid-span-*` classes. A span is capped at the columns the grid has at the current width, so it never adds an implicit column, and `columnsBelow` still decides the phone layout. A form about twice the width of a side panel, stacking below `md`:
74
+
75
+ ```tsx
76
+ <Grid columns={3} columnsBelow={{ md: 1 }} gap={4}>
77
+ <Stack columnSpan={2} gap={4}>
78
+ {form}
79
+ </Stack>
80
+ <Card variant="outlined">{panel}</Card>
81
+ </Grid>
82
+ ```
83
+
84
+ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span and no arbitrary column template.
72
85
 
73
86
  `DenominationGrid` shows integer counts per unit across a fixed set of columns. The `strip` layout is a captioned table: each row has a toned label with an icon slot, muted zero cells, optional signed deltas toned by sign, and an optional consumer-formatted `total` that moves under the label on a phone. The `tiles` layout stacks the column label, the count, and an optional `subtotal` string per column. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
74
87
 
@@ -0,0 +1,9 @@
1
+ import { type GridColumnSpan } from './css/css-grid-span.js';
2
+ export type ColumnSpanProps = {
3
+ /**
4
+ * Columns this box spans when it is a direct child of a `Grid`, capped at
5
+ * the grid's column count at the current width.
6
+ */
7
+ columnSpan?: GridColumnSpan;
8
+ };
9
+ export declare function columnSpanClassNames({ columnSpan, }: ColumnSpanProps): string[];
@@ -0,0 +1,8 @@
1
+ import { gridSpanClass } from './css/css-grid-span.js';
2
+ import { assertGridCount } from './grid-counts.js';
3
+ export function columnSpanClassNames({ columnSpan, }) {
4
+ if (columnSpan === undefined)
5
+ return [];
6
+ assertGridCount('columnSpan', columnSpan);
7
+ return [gridSpanClass(columnSpan)];
8
+ }
@@ -1,15 +1,16 @@
1
1
  import { type RadiusStep, type SemanticColorKey } from '@scalewing/tokens';
2
2
  import { type AnchorHTMLAttributes, type HTMLAttributes } from 'react';
3
+ import { type ColumnSpanProps } from '../column-span-classes.js';
3
4
  import { type SpacingProps } from '../spacing-classes.js';
4
5
  import { type VisibilityProps } from '../visibility-classes.js';
5
6
  export type BoxElement = 'div' | 'section' | 'article' | 'header' | 'footer' | 'main' | 'nav' | 'aside' | 'span' | 'a' | 'label';
6
- export type BoxProps = SpacingProps & VisibilityProps & HTMLAttributes<HTMLElement> & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'download' | 'href' | 'rel' | 'target'> & {
7
+ export type BoxProps = SpacingProps & VisibilityProps & ColumnSpanProps & HTMLAttributes<HTMLElement> & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'download' | 'href' | 'rel' | 'target'> & {
7
8
  as?: BoxElement;
8
9
  background?: Extract<SemanticColorKey, 'background' | 'surface'>;
9
10
  radius?: RadiusStep;
10
11
  border?: boolean;
11
12
  };
12
- export declare const Box: import("react").ForwardRefExoticComponent<SpacingProps & VisibilityProps & HTMLAttributes<HTMLElement> & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, "download" | "href" | "rel" | "target"> & {
13
+ export declare const Box: import("react").ForwardRefExoticComponent<SpacingProps & VisibilityProps & ColumnSpanProps & HTMLAttributes<HTMLElement> & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, "download" | "href" | "rel" | "target"> & {
13
14
  as?: BoxElement;
14
15
  background?: Extract<SemanticColorKey, "background" | "surface">;
15
16
  radius?: RadiusStep;
@@ -1,9 +1,10 @@
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
+ import { columnSpanClassNames, } from '../column-span-classes.js';
4
5
  import { spacingClassNames } from '../spacing-classes.js';
5
6
  import { visibilityClassNames, } from '../visibility-classes.js';
6
- export const Box = forwardRef(function Box({ as = 'div', background, border, className, hideBelow, hideFrom, padding, paddingBottom, paddingLeft, paddingRight, paddingTop, paddingX, paddingY, radius, style, ...rest }, ref) {
7
+ export const Box = forwardRef(function Box({ as = 'div', background, border, className, columnSpan, hideBelow, hideFrom, padding, paddingBottom, paddingLeft, paddingRight, paddingTop, paddingX, paddingY, radius, style, ...rest }, ref) {
7
8
  const Component = as;
8
9
  return (_jsx(Component, { ref: ref, className: cx(...spacingClassNames({
9
10
  padding,
@@ -13,7 +14,7 @@ export const Box = forwardRef(function Box({ as = 'div', background, border, cla
13
14
  paddingTop,
14
15
  paddingX,
15
16
  paddingY,
16
- }), ...visibilityClassNames({ hideBelow, hideFrom }), className), style: {
17
+ }), ...visibilityClassNames({ hideBelow, hideFrom }), ...columnSpanClassNames({ columnSpan }), className), style: {
17
18
  backgroundColor: background
18
19
  ? `var(--sw-color-${background})`
19
20
  : undefined,
@@ -1,20 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../class-names.js';
3
3
  import { breakpoints } from '../css/breakpoints.js';
4
- import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, } from '../css/css-grid.js';
4
+ import { gridColumnsBelowClass, gridColumnsClass, } from '../css/css-grid.js';
5
5
  import { spacingClass } from '../css/spacing-classes.js';
6
+ import { assertGridCount } from '../grid-counts.js';
6
7
  import { Box } from './Box.js';
7
- function assertColumns(columns) {
8
- if (!gridColumnCounts.includes(columns))
9
- throw new RangeError(`columns must be one of ${gridColumnCounts.join(', ')}`);
10
- }
11
8
  export function Grid({ className, columns = 1, columnsBelow, gap = 0, ...rest }) {
12
- assertColumns(columns);
9
+ assertGridCount('columns', columns);
13
10
  const belowClasses = breakpoints.flatMap((breakpoint) => {
14
11
  const count = columnsBelow?.[breakpoint];
15
12
  if (count === undefined)
16
13
  return [];
17
- assertColumns(count);
14
+ assertGridCount('columnsBelow', count);
18
15
  return [gridColumnsBelowClass(breakpoint, count)];
19
16
  });
20
17
  return (_jsx(Box, { className: cx('sw-grid', gridColumnsClass(columns), ...belowClasses, spacingClass('gap', 'all', gap), className), ...rest }));
@@ -22,6 +22,7 @@ import { cssTabsClasses } from './css-tabs.js';
22
22
  import { cssResponsiveClasses } from './css-responsive.js';
23
23
  import { cssFilterChipsClasses } from './css-filter-chips.js';
24
24
  import { cssGridClasses } from './css-grid.js';
25
+ import { cssGridSpanClasses } from './css-grid-span.js';
25
26
  import { cssDenominationGridClasses } from './css-denomination-grid.js';
26
27
  import { cssFieldClasses } from './css-field.js';
27
28
  import { cssToastClasses } from './css-toast.js';
@@ -55,6 +56,13 @@ export function cssComponentClasses() {
55
56
  -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
56
57
  }
57
58
 
59
+ /* Backdrop blur makes a glass surface its own stacking context, so an open popup inside it would paint under the next surface. */
60
+ .sw-card-glass:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])),
61
+ .sw-accordion:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])) {
62
+ position: relative;
63
+ z-index: 1;
64
+ }
65
+
58
66
  .sw-card-outlined {
59
67
  background: var(--sw-color-surface);
60
68
  border: 1px solid var(--sw-color-border);
@@ -142,6 +150,7 @@ ${cssTabsClasses()}
142
150
  ${cssFilterChipsClasses()}
143
151
 
144
152
  ${cssGridClasses()}
153
+ ${cssGridSpanClasses()}
145
154
  ${cssDenominationGridClasses()}
146
155
 
147
156
  ${cssFieldClasses()}
@@ -0,0 +1,7 @@
1
+ import { type GridColumns } from './css-grid.js';
2
+ /** A child spans one of the same bounded counts a `Grid` can have. */
3
+ export type GridColumnSpan = GridColumns;
4
+ export declare const gridColumnSpans: readonly [1, 2, 3, 4, 6];
5
+ export declare function gridSpanClass(span: GridColumnSpan): string;
6
+ export declare function cssGridSpanClasses(): string;
7
+ export declare function gridSpanClassCatalog(): string[];
@@ -0,0 +1,35 @@
1
+ import { breakpointQuery, breakpoints } from './breakpoints.js';
2
+ import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, } from './css-grid.js';
3
+ export const gridColumnSpans = gridColumnCounts;
4
+ export function gridSpanClass(span) {
5
+ return `sw-grid-span-${span}`;
6
+ }
7
+ function spanRule(selector, span) {
8
+ return `${selector} { grid-column: span ${span}; }`;
9
+ }
10
+ /**
11
+ * Caps a span at the columns its grid has at the current width, so a wide
12
+ * child never adds an implicit column. `:where()` keeps the cap at one class
13
+ * of specificity; source order lets it override the plain span rule.
14
+ */
15
+ function capRule(gridClass, columns, span) {
16
+ return spanRule(`:where(.${gridClass}) > .${gridSpanClass(span)}`, Math.min(span, columns));
17
+ }
18
+ function widerSpans(columns) {
19
+ return gridColumnSpans.filter((span) => span > columns);
20
+ }
21
+ export function cssGridSpanClasses() {
22
+ const spans = gridColumnSpans.map((span) => spanRule(`.${gridSpanClass(span)}`, span));
23
+ const caps = gridColumnCounts.flatMap((columns) => widerSpans(columns).map((span) => capRule(gridColumnsClass(columns), columns, span)));
24
+ // Below a breakpoint every span wider than one is restated for the phone
25
+ // column count, which also undoes a desktop cap when that count is larger.
26
+ const below = breakpoints.map((breakpoint) => `@media ${breakpointQuery('below', breakpoint)} {
27
+ ${gridColumnCounts
28
+ .flatMap((columns) => widerSpans(1).map((span) => ` ${capRule(gridColumnsBelowClass(breakpoint, columns), columns, span)}`))
29
+ .join('\n')}
30
+ }`);
31
+ return [...spans, ...caps, ...below].join('\n');
32
+ }
33
+ export function gridSpanClassCatalog() {
34
+ return gridColumnSpans.map((span) => gridSpanClass(span));
35
+ }
@@ -14,6 +14,7 @@ import { tabsClassCatalog } from './css-tabs.js';
14
14
  import { responsiveClassCatalog } from './css-responsive.js';
15
15
  import { filterChipsClassCatalog } from './css-filter-chips.js';
16
16
  import { gridClassCatalog } from './css-grid.js';
17
+ import { gridSpanClassCatalog } from './css-grid-span.js';
17
18
  import { denominationGridClassCatalog } from './css-denomination-grid.js';
18
19
  import { fieldClassCatalog } from './css-field.js';
19
20
  import { cssComponentClasses } from './css-components.js';
@@ -63,6 +64,7 @@ export function utilityClassCatalog() {
63
64
  ...tabsClassCatalog(),
64
65
  ...filterChipsClassCatalog(),
65
66
  ...gridClassCatalog(),
67
+ ...gridSpanClassCatalog(),
66
68
  ...denominationGridClassCatalog(),
67
69
  ...fieldClassCatalog(),
68
70
  ...splitClassCatalog(),
@@ -0,0 +1,3 @@
1
+ import { type GridColumns } from './css/css-grid.js';
2
+ /** Fails closed on a column count or span outside the bounded catalog. */
3
+ export declare function assertGridCount(prop: string, value: number): asserts value is GridColumns;
@@ -0,0 +1,6 @@
1
+ import { gridColumnCounts } from './css/css-grid.js';
2
+ /** Fails closed on a column count or span outside the bounded catalog. */
3
+ export function assertGridCount(prop, value) {
4
+ if (!gridColumnCounts.includes(value))
5
+ throw new RangeError(`${prop} must be one of ${gridColumnCounts.join(', ')}`);
6
+ }
package/dist/index.d.ts CHANGED
@@ -38,6 +38,8 @@ export { Separator } from './components/Separator.js';
38
38
  export type { SeparatorOrientation, SeparatorProps, } from './components/Separator.js';
39
39
  export { Grid } from './components/Grid.js';
40
40
  export type { GridColumns, GridProps } from './components/Grid.js';
41
+ export type { GridColumnSpan } from './css/css-grid-span.js';
42
+ export type { ColumnSpanProps } from './column-span-classes.js';
41
43
  export { DenominationGrid } from './components/DenominationGrid.js';
42
44
  export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridTone, } from './components/DenominationGrid.js';
43
45
  export { FilterChips } from './components/FilterChips.js';
package/dist/styles.css CHANGED
@@ -682,6 +682,13 @@ body {
682
682
  -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
683
683
  }
684
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
+
685
692
  .sw-card-outlined {
686
693
  background: var(--sw-color-surface);
687
694
  border: 1px solid var(--sw-color-border);
@@ -2159,6 +2166,43 @@ body {
2159
2166
  .sw-grid-cols-below-md-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
2160
2167
  .sw-grid-cols-below-md-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
2161
2168
  }
2169
+ .sw-grid-span-1 { grid-column: span 1; }
2170
+ .sw-grid-span-2 { grid-column: span 2; }
2171
+ .sw-grid-span-3 { grid-column: span 3; }
2172
+ .sw-grid-span-4 { grid-column: span 4; }
2173
+ .sw-grid-span-6 { grid-column: span 6; }
2174
+ :where(.sw-grid-cols-1) > .sw-grid-span-2 { grid-column: span 1; }
2175
+ :where(.sw-grid-cols-1) > .sw-grid-span-3 { grid-column: span 1; }
2176
+ :where(.sw-grid-cols-1) > .sw-grid-span-4 { grid-column: span 1; }
2177
+ :where(.sw-grid-cols-1) > .sw-grid-span-6 { grid-column: span 1; }
2178
+ :where(.sw-grid-cols-2) > .sw-grid-span-3 { grid-column: span 2; }
2179
+ :where(.sw-grid-cols-2) > .sw-grid-span-4 { grid-column: span 2; }
2180
+ :where(.sw-grid-cols-2) > .sw-grid-span-6 { grid-column: span 2; }
2181
+ :where(.sw-grid-cols-3) > .sw-grid-span-4 { grid-column: span 3; }
2182
+ :where(.sw-grid-cols-3) > .sw-grid-span-6 { grid-column: span 3; }
2183
+ :where(.sw-grid-cols-4) > .sw-grid-span-6 { grid-column: span 4; }
2184
+ @media not all and (min-width: 48rem) {
2185
+ :where(.sw-grid-cols-below-md-1) > .sw-grid-span-2 { grid-column: span 1; }
2186
+ :where(.sw-grid-cols-below-md-1) > .sw-grid-span-3 { grid-column: span 1; }
2187
+ :where(.sw-grid-cols-below-md-1) > .sw-grid-span-4 { grid-column: span 1; }
2188
+ :where(.sw-grid-cols-below-md-1) > .sw-grid-span-6 { grid-column: span 1; }
2189
+ :where(.sw-grid-cols-below-md-2) > .sw-grid-span-2 { grid-column: span 2; }
2190
+ :where(.sw-grid-cols-below-md-2) > .sw-grid-span-3 { grid-column: span 2; }
2191
+ :where(.sw-grid-cols-below-md-2) > .sw-grid-span-4 { grid-column: span 2; }
2192
+ :where(.sw-grid-cols-below-md-2) > .sw-grid-span-6 { grid-column: span 2; }
2193
+ :where(.sw-grid-cols-below-md-3) > .sw-grid-span-2 { grid-column: span 2; }
2194
+ :where(.sw-grid-cols-below-md-3) > .sw-grid-span-3 { grid-column: span 3; }
2195
+ :where(.sw-grid-cols-below-md-3) > .sw-grid-span-4 { grid-column: span 3; }
2196
+ :where(.sw-grid-cols-below-md-3) > .sw-grid-span-6 { grid-column: span 3; }
2197
+ :where(.sw-grid-cols-below-md-4) > .sw-grid-span-2 { grid-column: span 2; }
2198
+ :where(.sw-grid-cols-below-md-4) > .sw-grid-span-3 { grid-column: span 3; }
2199
+ :where(.sw-grid-cols-below-md-4) > .sw-grid-span-4 { grid-column: span 4; }
2200
+ :where(.sw-grid-cols-below-md-4) > .sw-grid-span-6 { grid-column: span 4; }
2201
+ :where(.sw-grid-cols-below-md-6) > .sw-grid-span-2 { grid-column: span 2; }
2202
+ :where(.sw-grid-cols-below-md-6) > .sw-grid-span-3 { grid-column: span 3; }
2203
+ :where(.sw-grid-cols-below-md-6) > .sw-grid-span-4 { grid-column: span 4; }
2204
+ :where(.sw-grid-cols-below-md-6) > .sw-grid-span-6 { grid-column: span 6; }
2205
+ }
2162
2206
  .sw-denomination-grid {
2163
2207
  color: var(--sw-color-text);
2164
2208
  font-family: var(--sw-font-sans);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalewing/react",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Scalewing DOM components and package-owned CSS utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",