@scalewing/react 1.31.0 → 1.33.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +20 -6
  3. package/dist/components/ActionMenu.d.ts +10 -1
  4. package/dist/components/ActionMenu.js +7 -2
  5. package/dist/components/AppHeader.d.ts +9 -1
  6. package/dist/components/AppHeader.js +6 -2
  7. package/dist/components/Box.d.ts +28 -2
  8. package/dist/components/Box.js +4 -2
  9. package/dist/components/Button.d.ts +27 -2
  10. package/dist/components/Button.js +5 -3
  11. package/dist/components/ButtonGroup.d.ts +9 -1
  12. package/dist/components/ButtonGroup.js +8 -2
  13. package/dist/components/Combobox.js +2 -2
  14. package/dist/components/DateField.d.ts +9 -1
  15. package/dist/components/DateField.js +4 -3
  16. package/dist/components/DenominationGrid.d.ts +5 -3
  17. package/dist/components/DenominationGrid.js +21 -11
  18. package/dist/components/Dialog.d.ts +9 -1
  19. package/dist/components/Dialog.js +8 -4
  20. package/dist/components/Field.d.ts +8 -0
  21. package/dist/components/Field.js +4 -3
  22. package/dist/components/FieldLabelText.d.ts +21 -3
  23. package/dist/components/FieldLabelText.js +16 -4
  24. package/dist/components/FilterChips.d.ts +9 -1
  25. package/dist/components/FilterChips.js +4 -2
  26. package/dist/components/Nav.d.ts +14 -2
  27. package/dist/components/Nav.js +6 -2
  28. package/dist/components/SegmentedControl.d.ts +5 -2
  29. package/dist/components/SegmentedControl.js +2 -1
  30. package/dist/components/Select.d.ts +9 -1
  31. package/dist/components/Select.js +4 -3
  32. package/dist/components/Stepper.d.ts +13 -0
  33. package/dist/components/Stepper.js +8 -2
  34. package/dist/components/combobox/types.d.ts +8 -0
  35. package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -2
  36. package/dist/components/denomination-grid/DenominationStrip.js +3 -2
  37. package/dist/components/denomination-grid/DenominationTiles.d.ts +1 -1
  38. package/dist/components/denomination-grid/EntryCell.js +1 -1
  39. package/dist/components/denomination-grid/StripRow.js +5 -2
  40. package/dist/components/denomination-grid/types.d.ts +35 -1
  41. package/dist/components/dialog/DialogTitle.d.ts +5 -3
  42. package/dist/components/dialog/DialogTitle.js +4 -4
  43. package/dist/css/css-action-menu-trigger.d.ts +11 -0
  44. package/dist/css/css-action-menu-trigger.js +66 -0
  45. package/dist/css/css-action-menu.js +7 -2
  46. package/dist/css/css-app-header-bar.d.ts +10 -0
  47. package/dist/css/css-app-header-bar.js +26 -0
  48. package/dist/css/css-box-border.d.ts +6 -1
  49. package/dist/css/css-box-border.js +8 -4
  50. package/dist/css/css-box-link.d.ts +13 -0
  51. package/dist/css/css-box-link.js +50 -0
  52. package/dist/css/css-button-group-attached.js +5 -2
  53. package/dist/css/css-button-group-track.d.ts +11 -0
  54. package/dist/css/css-button-group-track.js +48 -0
  55. package/dist/css/css-button.d.ts +19 -2
  56. package/dist/css/css-button.js +31 -1
  57. package/dist/css/css-chrome.js +14 -1
  58. package/dist/css/css-combobox.js +2 -1
  59. package/dist/css/css-components.js +3 -0
  60. package/dist/css/css-data.js +3 -2
  61. package/dist/css/css-date-field.js +2 -1
  62. package/dist/css/css-denomination-appearance.d.ts +21 -0
  63. package/dist/css/css-denomination-appearance.js +114 -0
  64. package/dist/css/css-denomination-cell-tones.d.ts +8 -0
  65. package/dist/css/css-denomination-cell-tones.js +20 -1
  66. package/dist/css/css-denomination-grid.js +4 -0
  67. package/dist/css/css-dialog-banded.d.ts +11 -0
  68. package/dist/css/css-dialog-banded.js +84 -0
  69. package/dist/css/css-document.js +3 -0
  70. package/dist/css/css-multi-select.js +2 -1
  71. package/dist/css/css-nav-words.d.ts +13 -0
  72. package/dist/css/css-nav-words.js +51 -0
  73. package/dist/css/css-notice.d.ts +4 -11
  74. package/dist/css/css-notice.js +6 -22
  75. package/dist/css/css-scroll-region.d.ts +7 -3
  76. package/dist/css/css-scroll-region.js +8 -3
  77. package/dist/css/css-segmented-raised.d.ts +3 -0
  78. package/dist/css/css-segmented-raised.js +72 -0
  79. package/dist/css/css-segmented.js +9 -3
  80. package/dist/css/css-select.js +2 -1
  81. package/dist/css/css-split.js +2 -1
  82. package/dist/css/css-stepper.js +7 -0
  83. package/dist/css/quiet-fill.d.ts +7 -0
  84. package/dist/css/quiet-fill.js +7 -0
  85. package/dist/css/stacking.js +2 -1
  86. package/dist/css/stylesheet.js +4 -0
  87. package/dist/css/tone-tint.d.ts +21 -0
  88. package/dist/css/tone-tint.js +23 -0
  89. package/dist/index.d.ts +13 -10
  90. package/dist/index.js +10 -7
  91. package/dist/styles.css +313 -10
  92. package/package.json +1 -1
@@ -0,0 +1,21 @@
1
+ export declare const denominationGridFrames: readonly ["none", "table"];
2
+ /**
3
+ * How a strip sits on the page: `none` (the default) is open rows on the
4
+ * page's own surface; `table` is a framed table: the surface inside a
5
+ * hairline with rounded corners, the heading row on the subtle fill, a
6
+ * rule between columns, and the counts at label size, centred under
7
+ * their heads.
8
+ */
9
+ export type DenominationGridFrame = (typeof denominationGridFrames)[number];
10
+ export declare const denominationTotalEmphases: readonly ["default", "strong"];
11
+ /**
12
+ * How the row totals read: `default` is data type in the row's tone (a
13
+ * neutral row's muted); `strong` is label type in bold, a neutral row's in
14
+ * the text color and a toned row's in its tone.
15
+ */
16
+ export type DenominationTotalEmphasis = (typeof denominationTotalEmphases)[number];
17
+ export declare const denominationFramedClass = "sw-denomination-framed";
18
+ export declare const denominationScrollFramedClass = "sw-denomination-scroll-framed";
19
+ export declare const denominationTotalsStrongClass = "sw-denomination-totals-strong";
20
+ export declare function cssDenominationAppearanceClasses(): string;
21
+ export declare function denominationAppearanceClassCatalog(): string[];
@@ -0,0 +1,114 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ import { breakpointQuery } from './breakpoints.js';
3
+ const label = typographyVariants.label;
4
+ export const denominationGridFrames = ['none', 'table'];
5
+ export const denominationTotalEmphases = ['default', 'strong'];
6
+ export const denominationFramedClass = 'sw-denomination-framed';
7
+ export const denominationScrollFramedClass = 'sw-denomination-scroll-framed';
8
+ export const denominationTotalsStrongClass = 'sw-denomination-totals-strong';
9
+ const framed = `.sw-denomination-strip.${denominationFramedClass}`;
10
+ const strong = `.${denominationTotalsStrongClass}`;
11
+ /*
12
+ * A framed strip. The frame is the scroll region's border, so it stays put
13
+ * while a wide strip scrolls inside it and its shades still draw on the
14
+ * region's own shadow. The cells pad a space-3 step across (space-2 below
15
+ * md) and the body cells a space-4 step down, so a read-only row is about
16
+ * as tall as an entry row; the cell padding is zero-specificity past the
17
+ * frame class, so an entry cell keeps its own space-1 padding and, below
18
+ * md, the collapsed total column is collapsed again. The row labels pad
19
+ * the same step whatever their appearance. The pinned label sits on the
20
+ * surface at the same low specificity, so a quiet row's label keeps the
21
+ * subtle fill; the pinned corner takes the heading row's fill. The column
22
+ * rule is a border on every cell after a row's first, so forced colours
23
+ * keep it and RTL mirrors it; the collapsed total column draws none.
24
+ * Emitted after the grid's own rules, its label appearances and its
25
+ * narrow-screen padding, so it wins at every width.
26
+ */
27
+ function frameRules() {
28
+ return `.${denominationScrollFramedClass} {
29
+ background: var(--sw-color-surface);
30
+ border: 1px solid var(--sw-color-border);
31
+ border-radius: var(--sw-radius-sm);
32
+ box-sizing: border-box;
33
+ }
34
+
35
+ ${framed} :where(th, td) {
36
+ padding: var(--sw-space-4) var(--sw-space-3);
37
+ }
38
+
39
+ ${framed} thead :where(th, td),
40
+ ${framed} thead .sw-denomination-corner:first-child {
41
+ background: var(--sw-color-subtle);
42
+ padding-block: var(--sw-space-2);
43
+ }
44
+
45
+ ${framed} :is(th, td) + :is(th, td) {
46
+ border-inline-start: 1px solid var(--sw-color-border);
47
+ }
48
+
49
+ ${framed} :where(.sw-denomination-label) {
50
+ backdrop-filter: none;
51
+ -webkit-backdrop-filter: none;
52
+ background: var(--sw-color-surface);
53
+ }
54
+
55
+ ${framed} .sw-denomination-label {
56
+ padding-inline: var(--sw-space-3);
57
+ }
58
+
59
+ ${framed} .sw-denomination-head:not(.sw-denomination-total-head),
60
+ ${framed} .sw-denomination-cell {
61
+ text-align: center;
62
+ }
63
+
64
+ ${framed} .sw-denomination-cell {
65
+ font-size: ${label.fontSize}px;
66
+ letter-spacing: ${label.letterSpacing}px;
67
+ line-height: ${label.lineHeight}px;
68
+ }
69
+
70
+ @media ${breakpointQuery('below', 'md')} {
71
+ ${framed} :where(th, td),
72
+ ${framed} .sw-denomination-label { padding-inline: var(--sw-space-2); }
73
+ ${framed} .sw-denomination-total,
74
+ ${framed} .sw-denomination-total-head { border-inline-start: 0; padding: 0; }
75
+ }
76
+
77
+ @media (forced-colors: active) {
78
+ .${denominationScrollFramedClass},
79
+ ${framed} :is(th, td) + :is(th, td) { border-color: CanvasText; }
80
+ }`;
81
+ }
82
+ /*
83
+ * Strong totals: label type in bold, a neutral row's in the text color
84
+ * (its tone is the muted color) and a toned row's left in its tone, under
85
+ * the label on a phone too. One class more specific than the plain
86
+ * labels' inline total, so a neutral row's total is the text color there.
87
+ */
88
+ function totalRules() {
89
+ return `${strong} .sw-denomination-total {
90
+ font-size: ${label.fontSize}px;
91
+ font-weight: 700;
92
+ letter-spacing: ${label.letterSpacing}px;
93
+ line-height: ${label.lineHeight}px;
94
+ }
95
+
96
+ ${strong} .sw-denomination-total-inline { font-weight: 700; }
97
+
98
+ ${strong} .sw-denomination-row-neutral .sw-denomination-total,
99
+ ${strong} .sw-denomination-row-neutral .sw-denomination-total-inline {
100
+ color: var(--sw-color-text);
101
+ }`;
102
+ }
103
+ export function cssDenominationAppearanceClasses() {
104
+ return `${frameRules()}
105
+
106
+ ${totalRules()}`;
107
+ }
108
+ export function denominationAppearanceClassCatalog() {
109
+ return [
110
+ denominationScrollFramedClass,
111
+ denominationFramedClass,
112
+ denominationTotalsStrongClass,
113
+ ];
114
+ }
@@ -1,4 +1,12 @@
1
1
  import { type BadgeTone } from './css-data.js';
2
+ export declare const denominationCellToneAppearances: readonly ["text", "pill"];
3
+ /**
4
+ * How a toned count shows its tone: `text` sets the count in the tone;
5
+ * `pill` sets it on a rounded tint of the tone in the tone's ink, the
6
+ * Notice's fill and words, in a strip's read-only counts.
7
+ */
8
+ export type DenominationCellToneAppearance = (typeof denominationCellToneAppearances)[number];
9
+ export declare const denominationCellTonesPillClass = "sw-denomination-cell-tones-pill";
2
10
  /**
3
11
  * The classes that tone one count: a marker that applies the shared rules and
4
12
  * the tone that sets their color. A strip cell (`td`) and a tile (`li`) take
@@ -1,4 +1,7 @@
1
1
  import { badgeTones } from './css-data.js';
2
+ import { toneTintFill, toneTintInk } from './tone-tint.js';
3
+ export const denominationCellToneAppearances = ['text', 'pill'];
4
+ export const denominationCellTonesPillClass = 'sw-denomination-cell-tones-pill';
2
5
  /**
3
6
  * The tones a single count can take. `neutral` is the cell's own look, so it
4
7
  * adds no class: a cell is toned only by a tone that says something.
@@ -21,11 +24,15 @@ export function denominationCellToneClassNames(tone) {
21
24
  * carries meaning alone: the consumer's words say why a count is toned.
22
25
  * Forced colors replace the tone with the system text color, and a toned
23
26
  * zero keeps the zero's GrayText there, so it does not read as a count.
27
+ * Under the `pill` appearance a strip's read-only count sits on a rounded
28
+ * tint of its tone in the tone's ink (the Notice's fill and words); forced
29
+ * colors outline the pill instead, so the mark survives without its fill.
24
30
  */
25
31
  export function cssDenominationCellToneClasses() {
26
32
  const toneRules = cellTones
27
- .map((tone) => `.sw-denomination-cell-tone-${tone} { --sw-denomination-cell-tone: var(--sw-color-${tone}); }`)
33
+ .map((tone) => `.sw-denomination-cell-tone-${tone} { --sw-denomination-cell-tone: var(--sw-color-${tone}); --sw-denomination-cell-fill: ${toneTintFill(tone)}; --sw-denomination-cell-ink: ${toneTintInk(tone)}; }`)
28
34
  .join('\n');
35
+ const pill = `.${denominationCellTonesPillClass} .sw-denomination-cell-toned .sw-denomination-cell-value:not(:empty)`;
29
36
  return `${toneRules}
30
37
 
31
38
  .sw-denomination-grid .sw-denomination-cell.sw-denomination-cell-toned,
@@ -39,13 +46,25 @@ export function cssDenominationCellToneClasses() {
39
46
  box-shadow: inset 0 0 0 1px var(--sw-denomination-cell-tone);
40
47
  }
41
48
 
49
+ /* A pill: the count on its tone's tint, in the tone's ink (a Notice's words: 4.5:1 on the tint unless the palette's own tone is under it). */
50
+ ${pill} {
51
+ background: var(--sw-denomination-cell-fill);
52
+ border-radius: var(--sw-radius-sm);
53
+ color: var(--sw-denomination-cell-ink);
54
+ display: inline-block;
55
+ padding: calc(var(--sw-space-1) / 2) var(--sw-space-2);
56
+ }
57
+
42
58
  @media (forced-colors: active) {
43
59
  .sw-denomination-grid .sw-denomination-cell-zero.sw-denomination-cell-toned,
44
60
  .sw-denomination-grid .sw-denomination-cell-toned .sw-denomination-cell-zero { color: GrayText; }
61
+ ${pill} { color: inherit; outline: 1px solid CanvasText; }
45
62
  }`;
46
63
  }
47
64
  export function denominationCellToneClassCatalog() {
48
65
  return [
66
+ 'sw-denomination-cell-value',
67
+ denominationCellTonesPillClass,
49
68
  'sw-denomination-cell-toned',
50
69
  ...cellTones.map((tone) => `sw-denomination-cell-tone-${tone}`),
51
70
  ];
@@ -1,6 +1,7 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { breakpointQuery } from './breakpoints.js';
3
3
  import { badgeTones } from './css-data.js';
4
+ import { cssDenominationAppearanceClasses, denominationAppearanceClassCatalog, } from './css-denomination-appearance.js';
4
5
  import { cssDenominationLabelClasses, denominationLabelClassCatalog, } from './css-denomination-label.js';
5
6
  import { cssDenominationAlignedClasses, denominationAlignedClassCatalog, } from './css-denomination-aligned.js';
6
7
  import { cssDenominationEntryClasses, denominationEntryClassCatalog, } from './css-denomination-entry.js';
@@ -257,6 +258,8 @@ ${cssDenominationAlignedClasses()}
257
258
 
258
259
  ${cssDenominationLabelClasses()}
259
260
 
261
+ ${cssDenominationAppearanceClasses()}
262
+
260
263
  @media (forced-colors: active) {
261
264
  .sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
262
265
  .sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
@@ -299,5 +302,6 @@ export function denominationGridClassCatalog() {
299
302
  ...denominationEntryClassCatalog(),
300
303
  ...denominationAlignedClassCatalog(),
301
304
  ...denominationLabelClassCatalog(),
305
+ ...denominationAppearanceClassCatalog(),
302
306
  ];
303
307
  }
@@ -0,0 +1,11 @@
1
+ export declare const dialogAppearances: readonly ["glass", "banded"];
2
+ /**
3
+ * How the dialog's surface reads: `glass` (the default) is one frosted
4
+ * panel; `banded` is a solid surface whose title row and footer are bands
5
+ * across its full width, ruled off from the content by hairlines, with the
6
+ * close button on a quiet round fill.
7
+ */
8
+ export type DialogAppearance = (typeof dialogAppearances)[number];
9
+ export declare const dialogBandedClass = "sw-dialog-banded";
10
+ export declare function cssDialogBandedClasses(): string;
11
+ export declare function dialogBandedClassCatalog(): string[];
@@ -0,0 +1,84 @@
1
+ import { breakpointQuery, breakpoints } from './breakpoints.js';
2
+ import { dialogSheetClass } from './css-dialog.js';
3
+ import { zIndex } from './stacking.js';
4
+ import { quietFill } from './quiet-fill.js';
5
+ export const dialogAppearances = ['glass', 'banded'];
6
+ export const dialogBandedClass = 'sw-dialog-banded';
7
+ const banded = `.${dialogBandedClass}`;
8
+ /* The dialog's own padding, which a band steps past to reach its edges. */
9
+ const bleed = 'calc(-1 * var(--sw-space-5))';
10
+ /* A bottom sheet's side padding, which clears a wide safe area. */
11
+ const sheetSide = (side) => `max(var(--sw-space-5), env(safe-area-inset-${side}, 0px))`;
12
+ /*
13
+ * In a bottom sheet the dialog's side padding clears the safe areas, so
14
+ * the band steps past that padding instead of space-5, and pads itself
15
+ * the same, so its hairline still meets both edges.
16
+ */
17
+ function sheetBandRules() {
18
+ return breakpoints
19
+ .map((breakpoint) => `@media ${breakpointQuery('below', breakpoint)} {
20
+ .${dialogSheetClass(breakpoint)}${banded} .sw-dialog-header {
21
+ margin-left: calc(-1 * ${sheetSide('left')});
22
+ margin-right: calc(-1 * ${sheetSide('right')});
23
+ padding-left: ${sheetSide('left')};
24
+ padding-right: ${sheetSide('right')};
25
+ }
26
+ }`)
27
+ .join('\n\n');
28
+ }
29
+ /*
30
+ * A banded dialog: the solid surface (no blur) with the medium elevation in
31
+ * place of the glass border, so it reads as a sheet over the page. The
32
+ * title row bleeds to the top and side edges as a band, a space-4 step
33
+ * down and the dialog's space-5 across, over a hairline, and stays at the
34
+ * top while the content scrolls under it (the top-chrome layer, under any
35
+ * popup the content opens); the pinned footer is already such a band at
36
+ * the bottom. The close button sits on the muted 16 % fill as a circle
37
+ * inside the band's padding, in the text color, and keeps its square size
38
+ * and touch target; a hover darkens the fill where the pointer can hover.
39
+ * Forced colors keep the hairline and outline the dialog, since its shadow
40
+ * is not drawn there.
41
+ */
42
+ export function cssDialogBandedClasses() {
43
+ return `${banded} {
44
+ backdrop-filter: none;
45
+ -webkit-backdrop-filter: none;
46
+ background: var(--sw-color-surface);
47
+ border-color: transparent;
48
+ box-shadow: var(--sw-elevation-md);
49
+ }
50
+
51
+ ${banded} .sw-dialog-header {
52
+ align-items: center;
53
+ background: var(--sw-color-surface);
54
+ border-bottom: 1px solid var(--sw-color-border);
55
+ margin: ${bleed} ${bleed} 0;
56
+ padding: var(--sw-space-4) var(--sw-space-5);
57
+ position: sticky;
58
+ top: ${bleed};
59
+ ${zIndex('topChrome')}
60
+ }
61
+
62
+ ${sheetBandRules()}
63
+
64
+ ${banded} .sw-button.sw-dialog-close {
65
+ background: ${quietFill};
66
+ border-radius: var(--sw-radius-pill);
67
+ color: var(--sw-color-text);
68
+ margin: 0;
69
+ }
70
+
71
+ @media (hover: hover) {
72
+ ${banded} .sw-button.sw-dialog-close:hover {
73
+ background: color-mix(in srgb, var(--sw-color-muted) 24%, transparent);
74
+ }
75
+ }
76
+
77
+ @media (forced-colors: active) {
78
+ ${banded} { border-color: CanvasText; }
79
+ ${banded} .sw-dialog-header { border-bottom-color: CanvasText; }
80
+ }`;
81
+ }
82
+ export function dialogBandedClassCatalog() {
83
+ return [dialogBandedClass];
84
+ }
@@ -1,4 +1,5 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { cssBoxLinkClasses } from './css-box-link.js';
2
3
  const body = typographyVariants.body;
3
4
  /** The native controls that take typed text: the canvas's text controls. */
4
5
  export const typedInputs = [
@@ -97,6 +98,8 @@ body {
97
98
  outline-offset: var(--sw-focus-ring-offset);
98
99
  }
99
100
 
101
+ ${cssBoxLinkClasses()}
102
+
100
103
  /*
101
104
  * A programmatic focus target (tabindex="-1": a notice, a card or a heading
102
105
  * that a script focuses after a save) takes the accent ring past the ring
@@ -1,6 +1,7 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { chevronStroke } from './chevron.js';
3
3
  import { zIndex } from './stacking.js';
4
+ import { quietFill } from './quiet-fill.js';
4
5
  const label = typographyVariants.label;
5
6
  const body = typographyVariants.body;
6
7
  /*
@@ -120,7 +121,7 @@ export function cssMultiSelectClasses() {
120
121
  }
121
122
 
122
123
  .sw-multi-select-option:hover {
123
- background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
124
+ background: ${quietFill};
124
125
  }
125
126
 
126
127
  .sw-multi-select-icon {
@@ -0,0 +1,13 @@
1
+ export declare const navAppearances: readonly ["default", "words"];
2
+ /**
3
+ * How a Nav draws its destinations: `default` keeps each link or Button's
4
+ * own look; `words` sets every destination as plain words, the others in
5
+ * the muted color at medium weight and the current one (`aria-current`)
6
+ * in the text color at label weight, with no fill, visible border or
7
+ * underline. The words must be visible: a glyph-only destination keeps
8
+ * `default`.
9
+ */
10
+ export type NavAppearance = (typeof navAppearances)[number];
11
+ export declare const navWordsClass = "sw-nav-words";
12
+ export declare function cssNavWordsClasses(): string;
13
+ export declare function navWordsClassCatalog(): string[];
@@ -0,0 +1,51 @@
1
+ import { typographyVariants } from '@scalewing/tokens';
2
+ const label = typographyVariants.label;
3
+ export const navAppearances = ['default', 'words'];
4
+ export const navWordsClass = 'sw-nav-words';
5
+ const words = `.sw-nav.${navWordsClass}`;
6
+ const destinations = `:is(a, .sw-button)`;
7
+ const current = `[aria-current]:not([aria-current='false'])`;
8
+ /*
9
+ * Words-only destinations. A Button keeps its size, padding, touch target
10
+ * and focus ring; only its fill, border color and color go, so a primary
11
+ * or ghost Button in the Nav reads the same. Links lose the canvas's accent
12
+ * and underline, and take a transparent hairline border like a Button's.
13
+ * `:is()` counts as its most specific argument, so the selector is three
14
+ * classes (0,3,0): it outranks a Button's variant and the canvas's link
15
+ * rules. The current destination differs by color and weight, so the
16
+ * words must be visible: a glyph-only destination has no weight to change
17
+ * (keep `default` for those). Hover lifts the color only where the pointer
18
+ * hovers, and never on a disabled destination. Forced colors draw the
19
+ * current one's border in Highlight and underline its words, since color
20
+ * and weight may not show there.
21
+ */
22
+ export function cssNavWordsClasses() {
23
+ return `${words} ${destinations} {
24
+ background: transparent;
25
+ border: 1px solid transparent;
26
+ color: var(--sw-color-muted);
27
+ font-size: ${label.fontSize}px;
28
+ font-weight: 500;
29
+ letter-spacing: ${label.letterSpacing}px;
30
+ line-height: ${label.lineHeight}px;
31
+ text-decoration: none;
32
+ }
33
+
34
+ @media (hover: hover) {
35
+ ${words} ${destinations}:hover:not(:disabled, [aria-disabled='true']) {
36
+ color: var(--sw-color-text);
37
+ }
38
+ }
39
+
40
+ ${words} ${destinations}${current} {
41
+ color: var(--sw-color-text);
42
+ font-weight: ${label.fontWeight};
43
+ }
44
+
45
+ @media (forced-colors: active) {
46
+ ${words} ${destinations}${current} { border-color: Highlight; text-decoration: underline; }
47
+ }`;
48
+ }
49
+ export function navWordsClassCatalog() {
50
+ return [navWordsClass];
51
+ }
@@ -1,22 +1,15 @@
1
+ import { type TintColor } from './tone-tint.js';
1
2
  export declare const noticeTones: readonly ["info", "success", "warning", "danger"];
2
3
  export declare const noticeVariants: readonly ["default", "band"];
3
4
  /** `default` is a rounded note in the flow; `band` a full-width strip of page chrome. */
4
5
  export type NoticeVariant = (typeof noticeVariants)[number];
5
6
  /** The notice's meaning: information, a good outcome, a caution, a refusal. */
6
7
  export type NoticeTone = (typeof noticeTones)[number];
7
- /**
8
- * How much of a tone's color the notice's fill mixes into the surface. Info
9
- * uses the derived accent tint instead (ADR 0011), which already keeps the
10
- * accent readable on every palette.
11
- */
8
+ /** How much of a tone's color the notice's fill mixes into the surface. */
12
9
  export declare const noticeTintShare = 0.1;
13
- /**
14
- * How far the notice's words move from the tone's color toward the text
15
- * color, so they read darker than the tone on a light fill (lighter on a
16
- * dark one) and keep 4.5:1 on the tint (`notice.test.tsx`).
17
- */
10
+ /** How far the notice's words move from the tone's color toward the text color. */
18
11
  export declare const noticeInkTextShare = 0.3;
19
12
  /** The semantic color a tone is drawn from. */
20
- export declare function noticeToneColor(tone: NoticeTone): string;
13
+ export declare function noticeToneColor(tone: NoticeTone): TintColor;
21
14
  export declare function cssNoticeClasses(): string;
22
15
  export declare function noticeClassCatalog(): string[];
@@ -1,34 +1,18 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { toneInkTextShare, toneTintFill, toneTintInk, toneTintShare, } from './tone-tint.js';
2
3
  export const noticeTones = ['info', 'success', 'warning', 'danger'];
3
4
  export const noticeVariants = ['default', 'band'];
4
- /**
5
- * How much of a tone's color the notice's fill mixes into the surface. Info
6
- * uses the derived accent tint instead (ADR 0011), which already keeps the
7
- * accent readable on every palette.
8
- */
9
- export const noticeTintShare = 0.1;
10
- /**
11
- * How far the notice's words move from the tone's color toward the text
12
- * color, so they read darker than the tone on a light fill (lighter on a
13
- * dark one) and keep 4.5:1 on the tint (`notice.test.tsx`).
14
- */
15
- export const noticeInkTextShare = 0.3;
5
+ /** How much of a tone's color the notice's fill mixes into the surface. */
6
+ export const noticeTintShare = toneTintShare;
7
+ /** How far the notice's words move from the tone's color toward the text color. */
8
+ export const noticeInkTextShare = toneInkTextShare;
16
9
  /** The semantic color a tone is drawn from. */
17
10
  export function noticeToneColor(tone) {
18
11
  return tone === 'info' ? 'accent' : tone;
19
12
  }
20
- function toneFill(tone) {
21
- if (tone === 'info')
22
- return 'var(--sw-color-accentSubtle)';
23
- return `color-mix(in srgb, var(--sw-color-${tone}) ${noticeTintShare * 100}%, var(--sw-color-surface))`;
24
- }
25
- function toneInk(tone) {
26
- const toneShare = Math.round((1 - noticeInkTextShare) * 100);
27
- return `color-mix(in srgb, var(--sw-color-${noticeToneColor(tone)}) ${toneShare}%, var(--sw-color-text))`;
28
- }
29
13
  function toneRules() {
30
14
  return noticeTones
31
- .map((tone) => `.sw-notice-${tone} { --sw-notice-fill: ${toneFill(tone)}; --sw-notice-ink: ${toneInk(tone)}; }`)
15
+ .map((tone) => `.sw-notice-${tone} { --sw-notice-fill: ${toneTintFill(noticeToneColor(tone))}; --sw-notice-ink: ${toneTintInk(noticeToneColor(tone))}; }`)
32
16
  .join('\n');
33
17
  }
34
18
  const label = typographyVariants.label;
@@ -9,9 +9,13 @@ export declare function scrollShadeRules(selector: string): string;
9
9
  /**
10
10
  * A scroll region is a full-width box that scrolls its own content when the
11
11
  * content is wider (or taller) than the box, so a wide table never widens the
12
- * page. It is a keyboard stop, so its focus ring is drawn inside its edge
13
- * where an ancestor's clipping cannot hide it. While its content is scrolled
14
- * out past an inline edge, `ScrollRegion` adds `sw-scroll-more-start` or
12
+ * page. It is positioned, so it is the containing block of anything
13
+ * absolutely placed inside it that no nearer box holds, such as a visually
14
+ * hidden label (`sw-sr-only`) in a cell past its edge: that stays inside the
15
+ * scroll, not placed against the page, where it would widen it. It is a
16
+ * keyboard stop, so its focus ring is drawn inside its edge where an
17
+ * ancestor's clipping cannot hide it. While its content is scrolled out past
18
+ * an inline edge, `ScrollRegion` adds `sw-scroll-more-start` or
15
19
  * `sw-scroll-more-end`, and that edge draws a shade: the cue that the content
16
20
  * goes on, which overlay scrollbars (phones, macOS) do not give. In forced
17
21
  * colors the shade is not drawn and the system's scrollbar is the cue. Table
@@ -34,9 +34,13 @@ export function scrollShadeRules(selector) {
34
34
  /**
35
35
  * A scroll region is a full-width box that scrolls its own content when the
36
36
  * content is wider (or taller) than the box, so a wide table never widens the
37
- * page. It is a keyboard stop, so its focus ring is drawn inside its edge
38
- * where an ancestor's clipping cannot hide it. While its content is scrolled
39
- * out past an inline edge, `ScrollRegion` adds `sw-scroll-more-start` or
37
+ * page. It is positioned, so it is the containing block of anything
38
+ * absolutely placed inside it that no nearer box holds, such as a visually
39
+ * hidden label (`sw-sr-only`) in a cell past its edge: that stays inside the
40
+ * scroll, not placed against the page, where it would widen it. It is a
41
+ * keyboard stop, so its focus ring is drawn inside its edge where an
42
+ * ancestor's clipping cannot hide it. While its content is scrolled out past
43
+ * an inline edge, `ScrollRegion` adds `sw-scroll-more-start` or
40
44
  * `sw-scroll-more-end`, and that edge draws a shade: the cue that the content
41
45
  * goes on, which overlay scrollbars (phones, macOS) do not give. In forced
42
46
  * colors the shade is not drawn and the system's scrollbar is the cue. Table
@@ -45,6 +49,7 @@ export function scrollShadeRules(selector) {
45
49
  export function scrollRegionRules(selector) {
46
50
  return `${selector} {
47
51
  overflow: auto;
52
+ position: relative;
48
53
  width: 100%;
49
54
  }
50
55
 
@@ -0,0 +1,3 @@
1
+ export declare const segmentedRaisedClass = "sw-segmented-raised";
2
+ export declare function cssSegmentedRaisedClasses(): string;
3
+ export declare function segmentedRaisedClassCatalog(): string[];
@@ -0,0 +1,72 @@
1
+ import { trackInset, typographyVariants } from '@scalewing/tokens';
2
+ import { quietFill } from './quiet-fill.js';
3
+ const label = typographyVariants.label;
4
+ export const segmentedRaisedClass = 'sw-segmented-raised';
5
+ const raised = `.sw-segmented.${segmentedRaisedClass}`;
6
+ /*
7
+ * The raised track: the quiet grey fill (no border) with corners one track
8
+ * inset rounder than the segments' small radius, so the two curves stay
9
+ * concentric, sized to its segments even in a Stack (unlike compact).
10
+ * Every segment is the md control height in label type with its words in
11
+ * the text color at medium weight; with marks, a segment pads a step less
12
+ * before its mark. The selected one is the surface raised by the small
13
+ * elevation, its words at label weight. In the dark scheme a shadow does
14
+ * not show and the surface is about the track's grey, so the selected
15
+ * segment draws a hairline ring in the muted color instead (3:1 or more
16
+ * against the track in every dark palette, on the canvas or a card). Two classes,
17
+ * so these win over the compact track's rules and the reduced-transparency
18
+ * surface; the track's transparent border would also win over the error
19
+ * outline (`.sw-segmented[aria-invalid='true']`, the same specificity,
20
+ * earlier), so the danger border, and Mark in forced colors, is restated
21
+ * last. Forced colors outline the track and the selected segment.
22
+ */
23
+ export function cssSegmentedRaisedClasses() {
24
+ return `${raised} {
25
+ background: ${quietFill};
26
+ border-color: transparent;
27
+ border-radius: calc(var(--sw-radius-sm) + ${trackInset}px);
28
+ }
29
+
30
+ /* Sized to its segments even where the field is stretched (a Stack). */
31
+ .sw-segmented-field > ${raised} {
32
+ align-self: flex-start;
33
+ }
34
+
35
+ ${raised} .sw-segmented-item {
36
+ border-radius: var(--sw-radius-sm);
37
+ color: var(--sw-color-text);
38
+ font-size: ${label.fontSize}px;
39
+ font-weight: 500;
40
+ letter-spacing: ${label.letterSpacing}px;
41
+ line-height: ${label.lineHeight}px;
42
+ min-height: var(--sw-control-md-min-height);
43
+ padding-inline: var(--sw-space-4);
44
+ }
45
+
46
+ ${raised}.sw-segmented-with-icons .sw-segmented-item {
47
+ padding-inline: var(--sw-space-2) var(--sw-space-3);
48
+ }
49
+
50
+ ${raised} .sw-segmented-item-selected {
51
+ background: var(--sw-color-surface);
52
+ box-shadow: var(--sw-elevation-sm);
53
+ font-weight: ${label.fontWeight};
54
+ }
55
+
56
+ [data-theme='dark'] ${raised} .sw-segmented-item-selected {
57
+ box-shadow: inset 0 0 0 1px var(--sw-color-muted);
58
+ }
59
+
60
+ ${raised}[aria-invalid='true'] {
61
+ border-color: var(--sw-color-danger);
62
+ }
63
+
64
+ @media (forced-colors: active) {
65
+ ${raised} { border-color: CanvasText; }
66
+ ${raised} .sw-segmented-item-selected { outline: 2px solid Highlight; }
67
+ ${raised}[aria-invalid='true'] { border-color: Mark; }
68
+ }`;
69
+ }
70
+ export function segmentedRaisedClassCatalog() {
71
+ return [segmentedRaisedClass];
72
+ }
@@ -1,4 +1,5 @@
1
1
  import { trackInset, typographyVariants } from '@scalewing/tokens';
2
+ import { cssSegmentedRaisedClasses, segmentedRaisedClassCatalog, } from './css-segmented-raised.js';
2
3
  const body = typographyVariants.body;
3
4
  const data = typographyVariants.data;
4
5
  const label = typographyVariants.label;
@@ -9,13 +10,15 @@ const label = typographyVariants.label;
9
10
  * the row stays even; the track is the subtle fill (two classes, so
10
11
  * reduced transparency keeps it) and the selected item is the surface
11
12
  * raised by the small elevation, so the choice shows on a white canvas
12
- * too. A filled track keeps its own look and only gains the marks. The
13
- * error outline is restated after these rules so an icon track keeps it.
13
+ * too. A filled or raised track keeps its own look and only gains the
14
+ * marks (one `:not()` of both, so the compact rules keep their
15
+ * specificity). The error outline is restated after these rules so an
16
+ * icon track keeps it.
14
17
  * In forced colours the selected item is outlined in Highlight; a focused
15
18
  * selected item keeps that outline, offset by the focus ring's offset.
16
19
  */
17
20
  function iconRules() {
18
- const compact = '.sw-segmented-with-icons:not(.sw-segmented-filled)';
21
+ const compact = '.sw-segmented-with-icons:not(.sw-segmented-filled, .sw-segmented-raised)';
19
22
  return `.sw-segmented${compact} {
20
23
  background: var(--sw-color-subtle);
21
24
  border-color: transparent;
@@ -162,6 +165,8 @@ export function cssSegmentedClasses() {
162
165
 
163
166
  ${iconRules()}
164
167
 
168
+ ${cssSegmentedRaisedClasses()}
169
+
165
170
  .sw-segmented-disabled {
166
171
  opacity: var(--sw-disabled-opacity);
167
172
  }
@@ -181,5 +186,6 @@ export function segmentedClassCatalog() {
181
186
  'sw-segmented-with-icons',
182
187
  'sw-segmented-item-icon',
183
188
  'sw-segmented-disabled',
189
+ ...segmentedRaisedClassCatalog(),
184
190
  ];
185
191
  }