@scalewing/react 1.0.0 → 1.1.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,20 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f45ef5d: Generate compact `display` and `heading` sizes below the `md` breakpoint from the new tokens, so page titles and panel figures stop dominating a phone screen. No product change needed.
8
+ - 01e7d0e: Add `align` (`start`, `center`, `end`) to `Text`, backed by generated `sw-text-align-*` classes, and keep `DenominationGrid` tiles three per row below the `md` breakpoint.
9
+ - f00b3cc: Add `DenominationGrid`, a read-only count-per-unit display across a fixed column set. The `strip` layout is a captioned table with a toned row label (icon slot), muted zero cells, signed delta cells toned by sign, and an optional consumer-formatted total that moves under the row label below the `md` breakpoint; the `tiles` layout stacks the column label, count, and an optional consumer-formatted subtotal per column. Generated `sw-denomination-*` classes; invalid columns, rows, or cells throw.
10
+ - cc9aa28: Add an optional `count` to `FilterChipOption`. It renders as a tabular chicklet after the chip label (generated `sw-filter-chip-count`), joins the option's accessible name, and a zero count quiets the chip (`sw-filter-chip-quiet`) until it is selected. Negative or fractional counts throw.
11
+ - f7f3bf9: Add `Grid`, a Box-based layout that places children in one to four equal-width columns with a token gap step and an optional smaller column count below the `md` breakpoint (`columnsBelow={{ md: 2 }}`), backed by generated `sw-grid-*` classes.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [f45ef5d]
16
+ - @scalewing/tokens@1.1.0
17
+
3
18
  ## 1.0.0
4
19
 
5
20
  ### Major Changes
package/README.md CHANGED
@@ -18,7 +18,9 @@ import {
18
18
  Progress,
19
19
  Tooltip,
20
20
  Separator,
21
+ DenominationGrid,
21
22
  FilterChips,
23
+ Grid,
22
24
  DateField,
23
25
  Field,
24
26
  Select,
@@ -62,7 +64,15 @@ Import the CSS once at the application entry. Do not copy it into your source tr
62
64
 
63
65
  `Separator` divides sections with a token-colored line. It is horizontal by default; use `orientation="vertical"` in a flex row. Set `decorative` when the line only supports layout so assistive technology ignores it.
64
66
 
65
- `FilterChips` presents a long, wrapping single-choice filter set. Provide a localized group `label`, controlled `value`, `onChange`, and options with stable values and localized labels. Counts belong in those labels. Native radios provide arrow and Space navigation; disabled options are skipped.
67
+ `Text` steps `display` and `heading` down to the compact token sizes below the `md` breakpoint; consumers do not size type per viewport.
68
+
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
+
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.
72
+
73
+ `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
+
75
+ `FilterChips` presents a long, wrapping single-choice filter set. Provide a localized group `label`, controlled `value`, `onChange`, and options with stable values and localized labels. An optional non-negative integer `count` renders as a tabular chicklet after the label and is part of the option's accessible name; a zero count quiets the chip until it is selected. Native radios provide arrow and Space navigation; disabled options are skipped.
66
76
 
67
77
  `DateField` is a controlled native date input. Supply `value`, `min`, and `max` as valid `YYYY-MM-DD` dates; use an empty `value` for a blank field. Its callback returns a date-only string or an empty string, without timezone conversion. Invalid serialized dates and inverted bounds throw; a value outside the bounds remains visible with invalid styling. For a blank required field, supply `error` when form validation runs to show the message and invalid styling. The browser chooses the calendar and localized display.
68
78
 
@@ -0,0 +1,3 @@
1
+ import { type DenominationGridProps } from './denomination-grid/types.js';
2
+ export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridTone, } from './denomination-grid/types.js';
3
+ export declare function DenominationGrid({ label, columns, rows, layout, subtotal, zeroLabel, }: DenominationGridProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DenominationStrip } from './denomination-grid/DenominationStrip.js';
3
+ import { DenominationTiles } from './denomination-grid/DenominationTiles.js';
4
+ import { assertDenominationGrid } from './denomination-grid/validate.js';
5
+ export function DenominationGrid({ label, columns, rows, layout = 'strip', subtotal, zeroLabel = '—', }) {
6
+ assertDenominationGrid(label, columns, rows);
7
+ if (!zeroLabel)
8
+ throw new RangeError('zeroLabel must not be empty');
9
+ if (layout === 'tiles')
10
+ return (_jsx(DenominationTiles, { columns: columns, label: label, rows: rows, subtotal: subtotal, zeroLabel: zeroLabel }));
11
+ return (_jsx(DenominationStrip, { columns: columns, label: label, rows: rows, zeroLabel: zeroLabel }));
12
+ }
@@ -1,6 +1,7 @@
1
1
  export type FilterChipOption = {
2
2
  value: string;
3
3
  label: string;
4
+ count?: number;
4
5
  disabled?: boolean;
5
6
  };
6
7
  export type FilterChipsProps = {
@@ -1,6 +1,10 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useId } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ function isCount(count) {
6
+ return count === undefined || (Number.isInteger(count) && count >= 0);
7
+ }
4
8
  export function FilterChips({ label, value, onChange, options, }) {
5
9
  const name = useId();
6
10
  const values = options.map((option) => option.value);
@@ -13,8 +17,10 @@ export function FilterChips({ label, value, onChange, options, }) {
13
17
  throw new RangeError('options must have unique values');
14
18
  if (!values.includes(value))
15
19
  throw new RangeError('value must match an option');
16
- return (_jsxs("fieldset", { className: "sw-filter-chips", children: [_jsx("legend", { className: "sw-filter-chips-legend", children: label }), _jsx("span", { className: "sw-filter-chips-options", children: options.map((option) => (_jsxs("label", { className: "sw-filter-chip", children: [_jsx("input", { checked: option.value === value, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
20
+ if (options.some((option) => !isCount(option.count)))
21
+ throw new RangeError('count must be a non-negative integer');
22
+ return (_jsxs("fieldset", { className: "sw-filter-chips", children: [_jsx("legend", { className: "sw-filter-chips-legend", children: label }), _jsx("span", { className: "sw-filter-chips-options", children: options.map((option) => (_jsxs("label", { className: cx('sw-filter-chip', option.count === 0 && 'sw-filter-chip-quiet'), children: [_jsx("input", { checked: option.value === value, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
17
23
  if (event.currentTarget.checked && !option.disabled)
18
24
  onChange(option.value);
19
- }, type: "radio", value: option.value }), _jsx("span", { className: "sw-filter-chip-face", children: option.label })] }, option.value))) })] }));
25
+ }, type: "radio", value: option.value }), _jsxs("span", { className: "sw-filter-chip-face", children: [option.label, option.count === undefined ? null : (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "sw-filter-chip-count", children: option.count })] }))] })] }, option.value))) })] }));
20
26
  }
@@ -0,0 +1,11 @@
1
+ import { type SpacingStep } from '@scalewing/tokens';
2
+ import { type Breakpoint } from '../css/breakpoints.js';
3
+ import { type GridColumns } from '../css/css-grid.js';
4
+ import { type BoxProps } from './Box.js';
5
+ export type { GridColumns };
6
+ export type GridProps = BoxProps & {
7
+ columns?: GridColumns;
8
+ columnsBelow?: Partial<Record<Breakpoint, GridColumns>>;
9
+ gap?: SpacingStep;
10
+ };
11
+ export declare function Grid({ className, columns, columnsBelow, gap, ...rest }: GridProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../class-names.js';
3
+ import { breakpoints } from '../css/breakpoints.js';
4
+ import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, } from '../css/css-grid.js';
5
+ import { spacingClass } from '../css/spacing-classes.js';
6
+ 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
+ export function Grid({ className, columns = 1, columnsBelow, gap = 0, ...rest }) {
12
+ assertColumns(columns);
13
+ const belowClasses = breakpoints.flatMap((breakpoint) => {
14
+ const count = columnsBelow?.[breakpoint];
15
+ if (count === undefined)
16
+ return [];
17
+ assertColumns(count);
18
+ return [gridColumnsBelowClass(breakpoint, count)];
19
+ });
20
+ return (_jsx(Box, { className: cx('sw-grid', gridColumnsClass(columns), ...belowClasses, spacingClass('gap', 'all', gap), className), ...rest }));
21
+ }
@@ -1,13 +1,16 @@
1
1
  import { type SemanticColorKey, type TypographyVariant } from '@scalewing/tokens';
2
2
  import { type HTMLAttributes, type LabelHTMLAttributes } from 'react';
3
3
  export type TextElement = 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'label' | 'strong';
4
+ export type TextAlign = 'start' | 'center' | 'end';
4
5
  export type TextProps = HTMLAttributes<HTMLElement> & Pick<LabelHTMLAttributes<HTMLLabelElement>, 'htmlFor'> & {
6
+ align?: TextAlign;
5
7
  as?: TextElement;
6
8
  color?: SemanticColorKey;
7
9
  truncate?: boolean;
8
10
  variant?: TypographyVariant;
9
11
  };
10
12
  export declare const Text: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLElement> & Pick<LabelHTMLAttributes<HTMLLabelElement>, "htmlFor"> & {
13
+ align?: TextAlign;
11
14
  as?: TextElement;
12
15
  color?: SemanticColorKey;
13
16
  truncate?: boolean;
@@ -10,7 +10,7 @@ const defaultElement = {
10
10
  label: 'span',
11
11
  title: 'h3',
12
12
  };
13
- export const Text = forwardRef(function Text({ as, className, color = 'text', style, truncate = false, variant = 'body', ...rest }, ref) {
13
+ export const Text = forwardRef(function Text({ align, as, className, color = 'text', style, truncate = false, variant = 'body', ...rest }, ref) {
14
14
  const Component = as ?? defaultElement[variant];
15
- return (_jsx(Component, { ref: ref, className: cx(`sw-text-${variant}`, truncate && 'sw-truncate', className), style: { color: `var(--sw-color-${color})`, ...style }, ...rest }));
15
+ return (_jsx(Component, { ref: ref, className: cx(`sw-text-${variant}`, align && `sw-text-align-${align}`, truncate && 'sw-truncate', className), style: { color: `var(--sw-color-${color})`, ...style }, ...rest }));
16
16
  });
@@ -0,0 +1,4 @@
1
+ import { type DenominationGridProps } from './types.js';
2
+ type StripProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel'>>;
3
+ export declare function DenominationStrip({ label, columns, rows, zeroLabel, }: StripProps): import("react").JSX.Element;
4
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../../class-names.js';
3
+ import { denominationCellView } from '../../denomination-cells.js';
4
+ export function DenominationStrip({ label, columns, rows, zeroLabel, }) {
5
+ const hasTotals = rows.some((row) => row.total !== undefined);
6
+ return (_jsxs("table", { className: "sw-denomination-grid sw-denomination-strip", children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: column.label }, column.key))), hasTotals ? _jsx("td", { className: "sw-denomination-corner" }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`, row.signed && 'sw-denomination-row-signed'), children: [_jsxs("th", { className: "sw-denomination-label", scope: "row", children: [row.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-icon", children: row.icon })) : null, _jsx("span", { className: "sw-denomination-label-text", children: row.label }), row.total !== undefined ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-total-inline", children: row.total })) : null] }), columns.map((column, index) => {
7
+ const view = denominationCellView(row.cells[index] ?? null, row.signed ?? false, zeroLabel);
8
+ return (_jsx("td", { className: `sw-denomination-cell sw-denomination-cell-${view.state}`, children: view.text }, column.key));
9
+ }), hasTotals ? (_jsx("td", { className: "sw-denomination-total", children: row.total ?? '' })) : null] }, row.id))) })] }));
10
+ }
@@ -0,0 +1,4 @@
1
+ import { type DenominationGridProps } from './types.js';
2
+ type TilesProps = Required<Pick<DenominationGridProps, 'label' | 'columns' | 'rows' | 'zeroLabel'>> & Pick<DenominationGridProps, 'subtotal'>;
3
+ export declare function DenominationTiles({ label, columns, rows, subtotal, zeroLabel, }: TilesProps): import("react").JSX.Element;
4
+ export {};
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../../class-names.js';
3
+ import { denominationCellView } from '../../denomination-cells.js';
4
+ export function DenominationTiles({ label, columns, rows, subtotal, zeroLabel, }) {
5
+ return (_jsx("div", { "aria-label": label, className: "sw-denomination-grid sw-denomination-tiles", role: "group", children: rows.map((row) => (_jsxs("section", { "aria-label": row.label, className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`), children: [rows.length > 1 || row.icon ? (_jsxs("span", { className: "sw-denomination-label", children: [row.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-icon", children: row.icon })) : null, _jsx("span", { className: "sw-denomination-label-text", children: row.label }), row.total !== undefined ? (_jsx("span", { className: "sw-denomination-total", children: row.total })) : null] })) : null, _jsx("ul", { className: "sw-denomination-tile-list", children: columns.map((column, index) => {
6
+ const cell = row.cells[index] ?? null;
7
+ const view = denominationCellView(cell, row.signed ?? false, zeroLabel);
8
+ const amount = subtotal && cell !== null && cell !== 0
9
+ ? subtotal(cell, column)
10
+ : null;
11
+ return (_jsxs("li", { className: "sw-denomination-tile", children: [_jsx("span", { className: "sw-denomination-head", children: column.label }), _jsx("span", { className: `sw-denomination-cell sw-denomination-cell-${view.state}`, children: view.text }), subtotal ? (_jsx("span", { className: "sw-denomination-subtotal", children: amount ?? zeroLabel })) : null] }, column.key));
12
+ }) })] }, row.id))) }));
13
+ }
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type BadgeTone } from '../../css/css-data.js';
3
+ export type DenominationGridTone = BadgeTone;
4
+ export type DenominationGridLayout = 'strip' | 'tiles';
5
+ export type DenominationGridColumn = {
6
+ key: string;
7
+ label: string;
8
+ };
9
+ export type DenominationGridRow = {
10
+ id: string;
11
+ label: string;
12
+ cells: readonly (number | null)[];
13
+ icon?: ReactNode;
14
+ tone?: DenominationGridTone;
15
+ signed?: boolean;
16
+ total?: string;
17
+ };
18
+ export type DenominationGridProps = {
19
+ label: string;
20
+ columns: readonly DenominationGridColumn[];
21
+ rows: readonly DenominationGridRow[];
22
+ layout?: DenominationGridLayout;
23
+ subtotal?: (count: number, column: DenominationGridColumn) => string;
24
+ zeroLabel?: string;
25
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { type DenominationGridColumn, type DenominationGridRow } from './types.js';
2
+ export declare function assertDenominationGrid(label: string, columns: readonly DenominationGridColumn[], rows: readonly DenominationGridRow[]): void;
@@ -0,0 +1,18 @@
1
+ import { isDenominationCount } from '../../denomination-cells.js';
2
+ export function assertDenominationGrid(label, columns, rows) {
3
+ if (!label.trim())
4
+ throw new RangeError('label must not be empty');
5
+ if (columns.length === 0 ||
6
+ columns.some((column) => !column.key.trim() || !column.label.trim()))
7
+ throw new RangeError('columns must have nonempty keys and labels');
8
+ if (new Set(columns.map((column) => column.key)).size !== columns.length)
9
+ throw new RangeError('columns must have unique keys');
10
+ if (rows.some((row) => !row.id.trim() || !row.label.trim()))
11
+ throw new RangeError('rows must have nonempty ids and labels');
12
+ if (new Set(rows.map((row) => row.id)).size !== rows.length)
13
+ throw new RangeError('rows must have unique ids');
14
+ if (rows.some((row) => row.cells.length !== columns.length))
15
+ throw new RangeError('each row needs one cell per column');
16
+ if (rows.some((row) => row.cells.some((cell) => !isDenominationCount(cell))))
17
+ throw new RangeError('cells must be integers or null');
18
+ }
@@ -1,4 +1,5 @@
1
- import { typographyVariants } from '@scalewing/tokens';
1
+ import { compactTypographyVariants, typographyVariants, } from '@scalewing/tokens';
2
+ import { breakpointQuery } from './breakpoints.js';
2
3
  import { cssActionMenuClasses } from './css-action-menu.js';
3
4
  import { cssAccordionClasses } from './css-accordion.js';
4
5
  import { cssButtonClasses } from './css-button.js';
@@ -18,6 +19,8 @@ import { cssTooltipClasses } from './css-tooltip.js';
18
19
  import { cssSeparatorClasses } from './css-separator.js';
19
20
  import { cssResponsiveClasses } from './css-responsive.js';
20
21
  import { cssFilterChipsClasses } from './css-filter-chips.js';
22
+ import { cssGridClasses } from './css-grid.js';
23
+ import { cssDenominationGridClasses } from './css-denomination-grid.js';
21
24
  import { cssFieldClasses } from './css-field.js';
22
25
  import { cssToastClasses } from './css-toast.js';
23
26
  function textVariantRules() {
@@ -30,6 +33,12 @@ function textVariantRules() {
30
33
  })
31
34
  .join('\n');
32
35
  }
36
+ function compactTextVariantRules() {
37
+ const rules = Object.entries(compactTypographyVariants)
38
+ .map(([name, variant]) => ` .sw-text-${name} { font-size: ${variant.fontSize}px; line-height: ${variant.lineHeight}px; letter-spacing: ${variant.letterSpacing}px; }`)
39
+ .join('\n');
40
+ return `@media ${breakpointQuery('below', 'md')} {\n${rules}\n}`;
41
+ }
33
42
  export function cssComponentClasses() {
34
43
  return `.sw-card {
35
44
  border-radius: var(--sw-radius-lg);
@@ -74,6 +83,10 @@ export function cssComponentClasses() {
74
83
  }
75
84
  }
76
85
 
86
+ .sw-text-align-start { text-align: start; }
87
+ .sw-text-align-center { text-align: center; }
88
+ .sw-text-align-end { text-align: end; }
89
+
77
90
  .sw-truncate {
78
91
  overflow: hidden;
79
92
  text-overflow: ellipsis;
@@ -86,6 +99,8 @@ export function cssComponentClasses() {
86
99
 
87
100
  ${textVariantRules()}
88
101
 
102
+ ${compactTextVariantRules()}
103
+
89
104
  ${cssButtonClasses()}
90
105
 
91
106
  ${cssCheckboxClasses()}
@@ -120,6 +135,9 @@ ${cssSeparatorClasses()}
120
135
 
121
136
  ${cssFilterChipsClasses()}
122
137
 
138
+ ${cssGridClasses()}
139
+ ${cssDenominationGridClasses()}
140
+
123
141
  ${cssFieldClasses()}
124
142
 
125
143
  ${cssSplitClasses()}
@@ -0,0 +1,2 @@
1
+ export declare function cssDenominationGridClasses(): string;
2
+ export declare function denominationGridClassCatalog(): string[];
@@ -0,0 +1,213 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { breakpointQuery } from './breakpoints.js';
3
+ import { badgeTones } from './css-data.js';
4
+ const caption = typographyVariants.caption;
5
+ const data = typographyVariants.data;
6
+ const label = typographyVariants.label;
7
+ const title = typographyVariants.title;
8
+ function toneRules() {
9
+ return badgeTones
10
+ .map((tone) => `.sw-denomination-row-${tone} { --sw-denomination-tone: var(--sw-color-${tone === 'neutral' ? 'muted' : tone}); }`)
11
+ .join('\n');
12
+ }
13
+ export function cssDenominationGridClasses() {
14
+ return `.sw-denomination-grid {
15
+ color: var(--sw-color-text);
16
+ font-family: var(--sw-font-sans);
17
+ max-width: 100%;
18
+ min-width: 0;
19
+ }
20
+
21
+ ${toneRules()}
22
+
23
+ .sw-denomination-strip {
24
+ border-collapse: collapse;
25
+ width: 100%;
26
+ }
27
+
28
+ .sw-denomination-strip th,
29
+ .sw-denomination-strip td {
30
+ padding: var(--sw-space-1) var(--sw-space-2);
31
+ vertical-align: middle;
32
+ }
33
+
34
+ .sw-denomination-strip tbody tr {
35
+ border-top: 1px solid var(--sw-color-border);
36
+ }
37
+
38
+ .sw-denomination-head {
39
+ color: var(--sw-color-muted);
40
+ font-size: ${caption.fontSize}px;
41
+ font-weight: 600;
42
+ letter-spacing: ${caption.letterSpacing}px;
43
+ line-height: ${caption.lineHeight}px;
44
+ text-align: end;
45
+ }
46
+
47
+ .sw-denomination-corner {
48
+ padding: 0;
49
+ }
50
+
51
+ .sw-denomination-label {
52
+ align-items: center;
53
+ border-inline-start: var(--sw-space-1) solid var(--sw-denomination-tone);
54
+ color: var(--sw-denomination-tone);
55
+ display: flex;
56
+ flex-wrap: wrap;
57
+ font-size: ${label.fontSize}px;
58
+ font-weight: ${label.fontWeight};
59
+ gap: var(--sw-space-2);
60
+ letter-spacing: ${label.letterSpacing}px;
61
+ line-height: ${label.lineHeight}px;
62
+ min-width: 0;
63
+ text-align: start;
64
+ }
65
+
66
+ .sw-denomination-icon {
67
+ display: inline-flex;
68
+ flex: none;
69
+ }
70
+
71
+ .sw-denomination-cell {
72
+ color: var(--sw-color-text);
73
+ font-size: ${data.fontSize}px;
74
+ font-variant-numeric: tabular-nums;
75
+ font-weight: 600;
76
+ letter-spacing: ${data.letterSpacing}px;
77
+ line-height: ${data.lineHeight}px;
78
+ text-align: end;
79
+ }
80
+
81
+ .sw-denomination-cell-zero {
82
+ color: var(--sw-color-muted);
83
+ font-weight: ${data.fontWeight};
84
+ opacity: var(--sw-quiet-opacity);
85
+ }
86
+
87
+ .sw-denomination-row-signed .sw-denomination-cell-positive {
88
+ color: var(--sw-color-success);
89
+ }
90
+
91
+ .sw-denomination-cell-negative {
92
+ color: var(--sw-color-danger);
93
+ }
94
+
95
+ .sw-denomination-total {
96
+ color: var(--sw-denomination-tone);
97
+ font-size: ${data.fontSize}px;
98
+ font-variant-numeric: tabular-nums;
99
+ font-weight: 600;
100
+ letter-spacing: ${data.letterSpacing}px;
101
+ line-height: ${data.lineHeight}px;
102
+ text-align: end;
103
+ white-space: nowrap;
104
+ }
105
+
106
+ .sw-denomination-total-inline {
107
+ display: none;
108
+ flex-basis: 100%;
109
+ font-size: ${caption.fontSize}px;
110
+ font-variant-numeric: tabular-nums;
111
+ font-weight: 600;
112
+ line-height: ${caption.lineHeight}px;
113
+ }
114
+
115
+ .sw-denomination-tiles {
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: var(--sw-space-3);
119
+ }
120
+
121
+ .sw-denomination-tiles .sw-denomination-label {
122
+ border-inline-start: 0;
123
+ }
124
+
125
+ .sw-denomination-tiles .sw-denomination-row {
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: var(--sw-space-2);
129
+ }
130
+
131
+ .sw-denomination-tile-list {
132
+ display: grid;
133
+ gap: var(--sw-space-2);
134
+ grid-template-columns: repeat(auto-fit, minmax(var(--sw-space-8), 1fr));
135
+ list-style: none;
136
+ margin: 0;
137
+ padding: 0;
138
+ }
139
+
140
+ .sw-denomination-tile {
141
+ align-items: center;
142
+ background: var(--sw-color-surface);
143
+ border: 1px solid var(--sw-color-border);
144
+ border-radius: var(--sw-radius-md);
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: var(--sw-space-1);
148
+ min-width: 0;
149
+ padding: var(--sw-space-3) var(--sw-space-2);
150
+ }
151
+
152
+ .sw-denomination-tile .sw-denomination-head,
153
+ .sw-denomination-tile .sw-denomination-cell {
154
+ text-align: center;
155
+ }
156
+
157
+ .sw-denomination-tile .sw-denomination-cell {
158
+ font-size: ${title.fontSize}px;
159
+ letter-spacing: ${title.letterSpacing}px;
160
+ line-height: ${title.lineHeight}px;
161
+ }
162
+
163
+ .sw-denomination-subtotal {
164
+ color: var(--sw-color-muted);
165
+ font-size: ${caption.fontSize}px;
166
+ font-variant-numeric: tabular-nums;
167
+ letter-spacing: ${caption.letterSpacing}px;
168
+ line-height: ${caption.lineHeight}px;
169
+ text-align: center;
170
+ }
171
+
172
+ @media ${breakpointQuery('below', 'md')} {
173
+ .sw-denomination-strip .sw-denomination-total { display: none; }
174
+ .sw-denomination-total-inline { display: block; }
175
+ .sw-denomination-strip .sw-denomination-label { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
176
+ .sw-denomination-strip th,
177
+ .sw-denomination-strip td { padding: var(--sw-space-1); }
178
+ .sw-denomination-tile-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
179
+ }
180
+
181
+ @media (forced-colors: active) {
182
+ .sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
183
+ .sw-denomination-cell-zero { color: GrayText; opacity: 1; }
184
+ .sw-denomination-cell-negative,
185
+ .sw-denomination-row-signed .sw-denomination-cell-positive,
186
+ .sw-denomination-total { color: CanvasText; }
187
+ .sw-denomination-tile { border-color: CanvasText; }
188
+ }`;
189
+ }
190
+ export function denominationGridClassCatalog() {
191
+ return [
192
+ 'sw-denomination-grid',
193
+ ...badgeTones.map((tone) => `sw-denomination-row-${tone}`),
194
+ 'sw-denomination-strip',
195
+ 'sw-denomination-head',
196
+ 'sw-denomination-corner',
197
+ 'sw-denomination-row',
198
+ 'sw-denomination-row-signed',
199
+ 'sw-denomination-label',
200
+ 'sw-denomination-label-text',
201
+ 'sw-denomination-icon',
202
+ 'sw-denomination-cell',
203
+ 'sw-denomination-cell-zero',
204
+ 'sw-denomination-cell-positive',
205
+ 'sw-denomination-cell-negative',
206
+ 'sw-denomination-total',
207
+ 'sw-denomination-total-inline',
208
+ 'sw-denomination-tiles',
209
+ 'sw-denomination-tile-list',
210
+ 'sw-denomination-tile',
211
+ 'sw-denomination-subtotal',
212
+ ];
213
+ }
@@ -1,5 +1,6 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  const label = typographyVariants.label;
3
+ const data = typographyVariants.data;
3
4
  export function cssFilterChipsClasses() {
4
5
  return `.sw-filter-chips {
5
6
  border: 0;
@@ -51,6 +52,7 @@ export function cssFilterChipsClasses() {
51
52
  display: inline-flex;
52
53
  font-family: var(--sw-font-sans);
53
54
  font-size: ${label.fontSize}px;
55
+ gap: var(--sw-space-2);
54
56
  line-height: ${label.lineHeight}px;
55
57
  min-height: var(--sw-control-md-min-height);
56
58
  min-width: 0;
@@ -58,12 +60,30 @@ export function cssFilterChipsClasses() {
58
60
  padding: var(--sw-space-1) var(--sw-space-3);
59
61
  }
60
62
 
63
+ .sw-filter-chip-count {
64
+ color: var(--sw-color-muted);
65
+ flex: none;
66
+ font-size: ${data.fontSize}px;
67
+ font-variant-numeric: tabular-nums;
68
+ font-weight: ${data.fontWeight};
69
+ letter-spacing: ${data.letterSpacing}px;
70
+ line-height: ${data.lineHeight}px;
71
+ }
72
+
73
+ .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face {
74
+ opacity: var(--sw-quiet-opacity);
75
+ }
76
+
61
77
  .sw-filter-chip-input:checked + .sw-filter-chip-face {
62
78
  background: var(--sw-color-accent);
63
79
  border-color: var(--sw-color-accent);
64
80
  color: var(--sw-color-onAccent);
65
81
  }
66
82
 
83
+ .sw-filter-chip-input:checked + .sw-filter-chip-face .sw-filter-chip-count {
84
+ color: inherit;
85
+ }
86
+
67
87
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face {
68
88
  outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
69
89
  outline-offset: var(--sw-focus-ring-offset);
@@ -78,6 +98,8 @@ export function cssFilterChipsClasses() {
78
98
  .sw-filter-chip-face { background: Canvas; border-color: CanvasText; color: CanvasText; }
79
99
  .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
80
100
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
101
+ .sw-filter-chip-count { color: inherit; }
102
+ .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face { opacity: 1; color: GrayText; border-color: GrayText; }
81
103
  }`;
82
104
  }
83
105
  export function filterChipsClassCatalog() {
@@ -88,5 +110,7 @@ export function filterChipsClassCatalog() {
88
110
  'sw-filter-chip',
89
111
  'sw-filter-chip-input',
90
112
  'sw-filter-chip-face',
113
+ 'sw-filter-chip-count',
114
+ 'sw-filter-chip-quiet',
91
115
  ];
92
116
  }
@@ -0,0 +1,7 @@
1
+ import { type Breakpoint } from './breakpoints.js';
2
+ export declare const gridColumnCounts: readonly [1, 2, 3, 4];
3
+ export type GridColumns = (typeof gridColumnCounts)[number];
4
+ export declare function gridColumnsClass(columns: GridColumns): string;
5
+ export declare function gridColumnsBelowClass(breakpoint: Breakpoint, columns: GridColumns): string;
6
+ export declare function cssGridClasses(): string;
7
+ export declare function gridClassCatalog(): string[];
@@ -0,0 +1,31 @@
1
+ import { breakpointQuery, breakpoints, } from './breakpoints.js';
2
+ export const gridColumnCounts = [1, 2, 3, 4];
3
+ export function gridColumnsClass(columns) {
4
+ return `sw-grid-cols-${columns}`;
5
+ }
6
+ export function gridColumnsBelowClass(breakpoint, columns) {
7
+ return `sw-grid-cols-below-${breakpoint}-${columns}`;
8
+ }
9
+ function columnsRule(className, columns) {
10
+ return `.${className} { grid-template-columns: repeat(${columns}, minmax(0, 1fr)); }`;
11
+ }
12
+ export function cssGridClasses() {
13
+ const base = `.sw-grid {
14
+ display: grid;
15
+ min-width: 0;
16
+ }`;
17
+ const columns = gridColumnCounts.map((count) => columnsRule(gridColumnsClass(count), count));
18
+ const below = breakpoints.map((breakpoint) => `@media ${breakpointQuery('below', breakpoint)} {
19
+ ${gridColumnCounts
20
+ .map((count) => columnsRule(gridColumnsBelowClass(breakpoint, count), count))
21
+ .join('\n')}
22
+ }`);
23
+ return [base, ...columns, ...below].join('\n');
24
+ }
25
+ export function gridClassCatalog() {
26
+ return [
27
+ 'sw-grid',
28
+ ...gridColumnCounts.map((count) => gridColumnsClass(count)),
29
+ ...breakpoints.flatMap((breakpoint) => gridColumnCounts.map((count) => gridColumnsBelowClass(breakpoint, count))),
30
+ ];
31
+ }
@@ -11,6 +11,8 @@ import { tooltipClassCatalog } from './css-tooltip.js';
11
11
  import { separatorClassCatalog } from './css-separator.js';
12
12
  import { responsiveClassCatalog } from './css-responsive.js';
13
13
  import { filterChipsClassCatalog } from './css-filter-chips.js';
14
+ import { gridClassCatalog } from './css-grid.js';
15
+ import { denominationGridClassCatalog } from './css-denomination-grid.js';
14
16
  import { fieldClassCatalog } from './css-field.js';
15
17
  import { cssComponentClasses } from './css-components.js';
16
18
  import { buttonClassCatalog } from './css-button.js';
@@ -34,6 +36,9 @@ export function utilityClassCatalog() {
34
36
  'sw-card',
35
37
  ...cardVariants.map((variant) => `sw-card-${variant}`),
36
38
  'sw-truncate',
39
+ 'sw-text-align-start',
40
+ 'sw-text-align-center',
41
+ 'sw-text-align-end',
37
42
  'sw-tabular',
38
43
  ...Object.keys(typographyVariants).map((name) => `sw-text-${name}`),
39
44
  ...buttonClassCatalog(),
@@ -53,6 +58,8 @@ export function utilityClassCatalog() {
53
58
  ...tooltipClassCatalog(),
54
59
  ...separatorClassCatalog(),
55
60
  ...filterChipsClassCatalog(),
61
+ ...gridClassCatalog(),
62
+ ...denominationGridClassCatalog(),
56
63
  ...fieldClassCatalog(),
57
64
  ...splitClassCatalog(),
58
65
  ...toastClassCatalog(),
@@ -0,0 +1,12 @@
1
+ export type DenominationCellState = 'zero' | 'positive' | 'negative';
2
+ export type DenominationCellView = {
3
+ state: DenominationCellState;
4
+ text: string;
5
+ };
6
+ /**
7
+ * Classifies one count for display. A null or zero count renders the zero
8
+ * label; a signed row prefixes positive counts with "+" so movement reads as
9
+ * a delta. Formatting stays plain digits: the consumer owns locale grouping.
10
+ */
11
+ export declare function denominationCellView(count: number | null, signed: boolean, zeroLabel: string): DenominationCellView;
12
+ export declare function isDenominationCount(count: number | null): boolean;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Classifies one count for display. A null or zero count renders the zero
3
+ * label; a signed row prefixes positive counts with "+" so movement reads as
4
+ * a delta. Formatting stays plain digits: the consumer owns locale grouping.
5
+ */
6
+ export function denominationCellView(count, signed, zeroLabel) {
7
+ if (count === null || count === 0)
8
+ return { state: 'zero', text: zeroLabel };
9
+ if (count < 0)
10
+ return { state: 'negative', text: String(count) };
11
+ return { state: 'positive', text: signed ? `+${count}` : String(count) };
12
+ }
13
+ export function isDenominationCount(count) {
14
+ return count === null || Number.isInteger(count);
15
+ }
package/dist/index.d.ts CHANGED
@@ -28,11 +28,15 @@ export { Select } from './components/Select.js';
28
28
  export type { SelectAction, SelectOption, SelectProps, } from './components/Select.js';
29
29
  export { Stack, type Align, type Justify, type StackProps, } from './components/Stack.js';
30
30
  export { Table, TableBody, TableCell, TableHeader, TableRow, type TableBodyProps, type TableCellProps, type TableDensity, type TableHeaderProps, type TableProps, type TableRowProps, } from './components/Table.js';
31
- export { Text, type TextElement, type TextProps } from './components/Text.js';
31
+ export { Text, type TextAlign, type TextElement, type TextProps, } from './components/Text.js';
32
32
  export { ThemeProvider, useTheme, type ThemeProviderProps, } from './theme/ThemeProvider.js';
33
33
  export { utilityClassCatalog } from './css/stylesheet.js';
34
34
  export type { VisibilityProps } from './visibility-classes.js';
35
35
  export { Separator } from './components/Separator.js';
36
36
  export type { SeparatorOrientation, SeparatorProps, } from './components/Separator.js';
37
+ export { Grid } from './components/Grid.js';
38
+ export type { GridColumns, GridProps } from './components/Grid.js';
39
+ export { DenominationGrid } from './components/DenominationGrid.js';
40
+ export type { DenominationGridColumn, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridTone, } from './components/DenominationGrid.js';
37
41
  export { FilterChips } from './components/FilterChips.js';
38
42
  export type { FilterChipOption, FilterChipsProps, } from './components/FilterChips.js';
package/dist/index.js CHANGED
@@ -23,8 +23,10 @@ export { SegmentedControl, } from './components/SegmentedControl.js';
23
23
  export { Select } from './components/Select.js';
24
24
  export { Stack, } from './components/Stack.js';
25
25
  export { Table, TableBody, TableCell, TableHeader, TableRow, } from './components/Table.js';
26
- export { Text } from './components/Text.js';
26
+ export { Text, } from './components/Text.js';
27
27
  export { ThemeProvider, useTheme, } from './theme/ThemeProvider.js';
28
28
  export { utilityClassCatalog } from './css/stylesheet.js';
29
29
  export { Separator } from './components/Separator.js';
30
+ export { Grid } from './components/Grid.js';
31
+ export { DenominationGrid } from './components/DenominationGrid.js';
30
32
  export { FilterChips } from './components/FilterChips.js';
package/dist/styles.css CHANGED
@@ -708,6 +708,10 @@ body {
708
708
  }
709
709
  }
710
710
 
711
+ .sw-text-align-start { text-align: start; }
712
+ .sw-text-align-center { text-align: center; }
713
+ .sw-text-align-end { text-align: end; }
714
+
711
715
  .sw-truncate {
712
716
  overflow: hidden;
713
717
  text-overflow: ellipsis;
@@ -726,6 +730,11 @@ body {
726
730
  .sw-text-caption { font-family: var(--sw-font-sans); font-size: 13px; line-height: 18px; font-weight: 400; letter-spacing: -0.08px; }
727
731
  .sw-text-data { font-family: var(--sw-font-sans); font-size: 13px; line-height: 18px; font-weight: 500; letter-spacing: -0.08px; font-variant-numeric: tabular-nums; }
728
732
 
733
+ @media not all and (min-width: 48rem) {
734
+ .sw-text-display { font-size: 32px; line-height: 38px; letter-spacing: -0.6px; }
735
+ .sw-text-heading { font-size: 24px; line-height: 30px; letter-spacing: -0.4px; }
736
+ }
737
+
729
738
  .sw-button {
730
739
  appearance: none;
731
740
  align-items: center;
@@ -1915,6 +1924,7 @@ body {
1915
1924
  display: inline-flex;
1916
1925
  font-family: var(--sw-font-sans);
1917
1926
  font-size: 15px;
1927
+ gap: var(--sw-space-2);
1918
1928
  line-height: 20px;
1919
1929
  min-height: var(--sw-control-md-min-height);
1920
1930
  min-width: 0;
@@ -1922,12 +1932,30 @@ body {
1922
1932
  padding: var(--sw-space-1) var(--sw-space-3);
1923
1933
  }
1924
1934
 
1935
+ .sw-filter-chip-count {
1936
+ color: var(--sw-color-muted);
1937
+ flex: none;
1938
+ font-size: 13px;
1939
+ font-variant-numeric: tabular-nums;
1940
+ font-weight: 500;
1941
+ letter-spacing: -0.08px;
1942
+ line-height: 18px;
1943
+ }
1944
+
1945
+ .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face {
1946
+ opacity: var(--sw-quiet-opacity);
1947
+ }
1948
+
1925
1949
  .sw-filter-chip-input:checked + .sw-filter-chip-face {
1926
1950
  background: var(--sw-color-accent);
1927
1951
  border-color: var(--sw-color-accent);
1928
1952
  color: var(--sw-color-onAccent);
1929
1953
  }
1930
1954
 
1955
+ .sw-filter-chip-input:checked + .sw-filter-chip-face .sw-filter-chip-count {
1956
+ color: inherit;
1957
+ }
1958
+
1931
1959
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face {
1932
1960
  outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
1933
1961
  outline-offset: var(--sw-focus-ring-offset);
@@ -1942,6 +1970,201 @@ body {
1942
1970
  .sw-filter-chip-face { background: Canvas; border-color: CanvasText; color: CanvasText; }
1943
1971
  .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
1944
1972
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
1973
+ .sw-filter-chip-count { color: inherit; }
1974
+ .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face { opacity: 1; color: GrayText; border-color: GrayText; }
1975
+ }
1976
+
1977
+ .sw-grid {
1978
+ display: grid;
1979
+ min-width: 0;
1980
+ }
1981
+ .sw-grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
1982
+ .sw-grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1983
+ .sw-grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1984
+ .sw-grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
1985
+ @media not all and (min-width: 48rem) {
1986
+ .sw-grid-cols-below-md-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
1987
+ .sw-grid-cols-below-md-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1988
+ .sw-grid-cols-below-md-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1989
+ .sw-grid-cols-below-md-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
1990
+ }
1991
+ .sw-denomination-grid {
1992
+ color: var(--sw-color-text);
1993
+ font-family: var(--sw-font-sans);
1994
+ max-width: 100%;
1995
+ min-width: 0;
1996
+ }
1997
+
1998
+ .sw-denomination-row-neutral { --sw-denomination-tone: var(--sw-color-muted); }
1999
+ .sw-denomination-row-accent { --sw-denomination-tone: var(--sw-color-accent); }
2000
+ .sw-denomination-row-success { --sw-denomination-tone: var(--sw-color-success); }
2001
+ .sw-denomination-row-danger { --sw-denomination-tone: var(--sw-color-danger); }
2002
+
2003
+ .sw-denomination-strip {
2004
+ border-collapse: collapse;
2005
+ width: 100%;
2006
+ }
2007
+
2008
+ .sw-denomination-strip th,
2009
+ .sw-denomination-strip td {
2010
+ padding: var(--sw-space-1) var(--sw-space-2);
2011
+ vertical-align: middle;
2012
+ }
2013
+
2014
+ .sw-denomination-strip tbody tr {
2015
+ border-top: 1px solid var(--sw-color-border);
2016
+ }
2017
+
2018
+ .sw-denomination-head {
2019
+ color: var(--sw-color-muted);
2020
+ font-size: 13px;
2021
+ font-weight: 600;
2022
+ letter-spacing: -0.08px;
2023
+ line-height: 18px;
2024
+ text-align: end;
2025
+ }
2026
+
2027
+ .sw-denomination-corner {
2028
+ padding: 0;
2029
+ }
2030
+
2031
+ .sw-denomination-label {
2032
+ align-items: center;
2033
+ border-inline-start: var(--sw-space-1) solid var(--sw-denomination-tone);
2034
+ color: var(--sw-denomination-tone);
2035
+ display: flex;
2036
+ flex-wrap: wrap;
2037
+ font-size: 15px;
2038
+ font-weight: 600;
2039
+ gap: var(--sw-space-2);
2040
+ letter-spacing: -0.2px;
2041
+ line-height: 20px;
2042
+ min-width: 0;
2043
+ text-align: start;
2044
+ }
2045
+
2046
+ .sw-denomination-icon {
2047
+ display: inline-flex;
2048
+ flex: none;
2049
+ }
2050
+
2051
+ .sw-denomination-cell {
2052
+ color: var(--sw-color-text);
2053
+ font-size: 13px;
2054
+ font-variant-numeric: tabular-nums;
2055
+ font-weight: 600;
2056
+ letter-spacing: -0.08px;
2057
+ line-height: 18px;
2058
+ text-align: end;
2059
+ }
2060
+
2061
+ .sw-denomination-cell-zero {
2062
+ color: var(--sw-color-muted);
2063
+ font-weight: 500;
2064
+ opacity: var(--sw-quiet-opacity);
2065
+ }
2066
+
2067
+ .sw-denomination-row-signed .sw-denomination-cell-positive {
2068
+ color: var(--sw-color-success);
2069
+ }
2070
+
2071
+ .sw-denomination-cell-negative {
2072
+ color: var(--sw-color-danger);
2073
+ }
2074
+
2075
+ .sw-denomination-total {
2076
+ color: var(--sw-denomination-tone);
2077
+ font-size: 13px;
2078
+ font-variant-numeric: tabular-nums;
2079
+ font-weight: 600;
2080
+ letter-spacing: -0.08px;
2081
+ line-height: 18px;
2082
+ text-align: end;
2083
+ white-space: nowrap;
2084
+ }
2085
+
2086
+ .sw-denomination-total-inline {
2087
+ display: none;
2088
+ flex-basis: 100%;
2089
+ font-size: 13px;
2090
+ font-variant-numeric: tabular-nums;
2091
+ font-weight: 600;
2092
+ line-height: 18px;
2093
+ }
2094
+
2095
+ .sw-denomination-tiles {
2096
+ display: flex;
2097
+ flex-direction: column;
2098
+ gap: var(--sw-space-3);
2099
+ }
2100
+
2101
+ .sw-denomination-tiles .sw-denomination-label {
2102
+ border-inline-start: 0;
2103
+ }
2104
+
2105
+ .sw-denomination-tiles .sw-denomination-row {
2106
+ display: flex;
2107
+ flex-direction: column;
2108
+ gap: var(--sw-space-2);
2109
+ }
2110
+
2111
+ .sw-denomination-tile-list {
2112
+ display: grid;
2113
+ gap: var(--sw-space-2);
2114
+ grid-template-columns: repeat(auto-fit, minmax(var(--sw-space-8), 1fr));
2115
+ list-style: none;
2116
+ margin: 0;
2117
+ padding: 0;
2118
+ }
2119
+
2120
+ .sw-denomination-tile {
2121
+ align-items: center;
2122
+ background: var(--sw-color-surface);
2123
+ border: 1px solid var(--sw-color-border);
2124
+ border-radius: var(--sw-radius-md);
2125
+ display: flex;
2126
+ flex-direction: column;
2127
+ gap: var(--sw-space-1);
2128
+ min-width: 0;
2129
+ padding: var(--sw-space-3) var(--sw-space-2);
2130
+ }
2131
+
2132
+ .sw-denomination-tile .sw-denomination-head,
2133
+ .sw-denomination-tile .sw-denomination-cell {
2134
+ text-align: center;
2135
+ }
2136
+
2137
+ .sw-denomination-tile .sw-denomination-cell {
2138
+ font-size: 20px;
2139
+ letter-spacing: -0.3px;
2140
+ line-height: 28px;
2141
+ }
2142
+
2143
+ .sw-denomination-subtotal {
2144
+ color: var(--sw-color-muted);
2145
+ font-size: 13px;
2146
+ font-variant-numeric: tabular-nums;
2147
+ letter-spacing: -0.08px;
2148
+ line-height: 18px;
2149
+ text-align: center;
2150
+ }
2151
+
2152
+ @media not all and (min-width: 48rem) {
2153
+ .sw-denomination-strip .sw-denomination-total { display: none; }
2154
+ .sw-denomination-total-inline { display: block; }
2155
+ .sw-denomination-strip .sw-denomination-label { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
2156
+ .sw-denomination-strip th,
2157
+ .sw-denomination-strip td { padding: var(--sw-space-1); }
2158
+ .sw-denomination-tile-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
2159
+ }
2160
+
2161
+ @media (forced-colors: active) {
2162
+ .sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
2163
+ .sw-denomination-cell-zero { color: GrayText; opacity: 1; }
2164
+ .sw-denomination-cell-negative,
2165
+ .sw-denomination-row-signed .sw-denomination-cell-positive,
2166
+ .sw-denomination-total { color: CanvasText; }
2167
+ .sw-denomination-tile { border-color: CanvasText; }
1945
2168
  }
1946
2169
 
1947
2170
  .sw-field-required { color: var(--sw-color-danger); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalewing/react",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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.0.0"
41
+ "@scalewing/tokens": "^1.1.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": "^19.0.0",