@scalewing/react 1.9.0 → 1.10.1
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 +41 -0
- package/README.md +29 -1
- package/dist/anchored-position.d.ts +17 -5
- package/dist/anchored-position.js +19 -4
- package/dist/calendar-button-labels.d.ts +19 -0
- package/dist/calendar-button-labels.js +10 -0
- package/dist/calendar-labels.d.ts +7 -0
- package/dist/calendar-labels.js +9 -0
- package/dist/calendar-month.d.ts +2 -0
- package/dist/calendar-month.js +5 -0
- package/dist/components/ActionMenu.js +2 -58
- package/dist/components/CalendarButton.d.ts +43 -0
- package/dist/components/CalendarButton.js +47 -0
- package/dist/components/DateField.js +12 -23
- package/dist/components/action-menu/ActionMenuList.d.ts +21 -0
- package/dist/components/action-menu/ActionMenuList.js +27 -0
- package/dist/components/date-field/CalendarDialog.js +1 -1
- package/dist/components/date-field/DateFieldGlyph.d.ts +3 -3
- package/dist/components/date-field/DateFieldGlyph.js +3 -3
- package/dist/components/date-field/use-calendar-popup.d.ts +36 -0
- package/dist/components/date-field/use-calendar-popup.js +43 -0
- package/dist/components/use-anchored-popover.d.ts +14 -0
- package/dist/components/{date-field/use-anchored-popover.js → use-anchored-popover.js} +31 -9
- package/dist/css/css-action-menu.js +25 -6
- package/dist/css/css-calendar-button.d.ts +2 -0
- package/dist/css/css-calendar-button.js +32 -0
- package/dist/css/css-components.js +5 -25
- package/dist/css/css-date-field.js +2 -1
- package/dist/css/css-palettes.js +11 -1
- package/dist/css/css-text.d.ts +1 -0
- package/dist/css/css-text.js +42 -0
- package/dist/css/stacking.js +6 -3
- package/dist/css/stylesheet.js +2 -0
- package/dist/css/touch-target.d.ts +2 -0
- package/dist/css/touch-target.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/palette/amber.css +2 -0
- package/dist/palette/capri.css +2 -0
- package/dist/palette/cerulean.css +2 -0
- package/dist/palette/cornflower.css +2 -0
- package/dist/palette/fuchsia.css +2 -0
- package/dist/palette/harvest.css +2 -0
- package/dist/palette/ink.css +2 -0
- package/dist/palette/mocha.css +2 -0
- package/dist/palette/navy.css +2 -0
- package/dist/palette/postcard.css +2 -0
- package/dist/palette/raspberry.css +2 -0
- package/dist/palette/signal.css +2 -0
- package/dist/palette/sky.css +2 -0
- package/dist/palette/soft-blue.css +2 -0
- package/dist/palette/sunburst.css +2 -0
- package/dist/palette/synthwave.css +2 -0
- package/dist/palette/tangerine.css +2 -0
- package/dist/styles.css +92 -13
- package/package.json +1 -1
- package/dist/components/date-field/use-anchored-popover.d.ts +0 -8
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
export type CalendarPopupOptions = {
|
|
3
|
+
value: string;
|
|
4
|
+
disabled: boolean;
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
/** Runs before a chosen date closes the calendar, such as a text reset. */
|
|
7
|
+
beforeSelect?: () => void;
|
|
8
|
+
};
|
|
9
|
+
export type CalendarPopup = {
|
|
10
|
+
/** The button that toggles the calendar and takes focus back. */
|
|
11
|
+
buttonRef: RefObject<HTMLButtonElement | null>;
|
|
12
|
+
shown: boolean;
|
|
13
|
+
toggle: () => void;
|
|
14
|
+
/** ARIA for the toggling button, as the WAI-ARIA date picker asks. */
|
|
15
|
+
trigger: {
|
|
16
|
+
'aria-controls': string | undefined;
|
|
17
|
+
'aria-expanded': boolean;
|
|
18
|
+
'aria-haspopup': 'dialog';
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* CalendarDialog props that belong to the open and close cycle; pass
|
|
22
|
+
* `buttonRef` beside them.
|
|
23
|
+
*/
|
|
24
|
+
dialog: {
|
|
25
|
+
id: string;
|
|
26
|
+
onClose: (restoreFocus: boolean) => void;
|
|
27
|
+
onSelect: (value: string) => void;
|
|
28
|
+
today: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The open state of a calendar dialog toggled by one button, shared by
|
|
33
|
+
* DateField and CalendarButton. Disabling closes it for good; choosing a
|
|
34
|
+
* date closes it, returns focus to the button, and reports a changed date.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useCalendarPopup({ value, disabled, onChange, beforeSelect, }: CalendarPopupOptions): CalendarPopup;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useId, useRef, useState } from 'react';
|
|
2
|
+
import { todayDateOnly } from '../../date-only.js';
|
|
3
|
+
/**
|
|
4
|
+
* The open state of a calendar dialog toggled by one button, shared by
|
|
5
|
+
* DateField and CalendarButton. Disabling closes it for good; choosing a
|
|
6
|
+
* date closes it, returns focus to the button, and reports a changed date.
|
|
7
|
+
*/
|
|
8
|
+
export function useCalendarPopup({ value, disabled, onChange, beforeSelect, }) {
|
|
9
|
+
const buttonRef = useRef(null);
|
|
10
|
+
const [open, setOpen] = useState(false);
|
|
11
|
+
// Disabling closes the calendar for good, not just while disabled.
|
|
12
|
+
if (disabled && open)
|
|
13
|
+
setOpen(false);
|
|
14
|
+
const shown = open && !disabled;
|
|
15
|
+
const id = useId();
|
|
16
|
+
function close(restoreFocus) {
|
|
17
|
+
setOpen(false);
|
|
18
|
+
if (restoreFocus)
|
|
19
|
+
buttonRef.current?.focus();
|
|
20
|
+
}
|
|
21
|
+
function select(next) {
|
|
22
|
+
beforeSelect?.();
|
|
23
|
+
close(true);
|
|
24
|
+
if (next !== value)
|
|
25
|
+
onChange(next);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
buttonRef,
|
|
29
|
+
shown,
|
|
30
|
+
toggle: () => (shown ? close(true) : setOpen(true)),
|
|
31
|
+
trigger: {
|
|
32
|
+
'aria-controls': shown ? id : undefined,
|
|
33
|
+
'aria-expanded': shown,
|
|
34
|
+
'aria-haspopup': 'dialog',
|
|
35
|
+
},
|
|
36
|
+
dialog: {
|
|
37
|
+
id,
|
|
38
|
+
onClose: close,
|
|
39
|
+
onSelect: select,
|
|
40
|
+
today: todayDateOnly(),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
export type AnchoredPopoverPlacement = {
|
|
3
|
+
/** Line up with the anchor's end when the start-aligned popover overflows. */
|
|
4
|
+
flipInline?: boolean;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Shows a mounted element on the top layer as a manual popover, keeps it
|
|
8
|
+
* beside `anchorRef` through scroll, window resize, and a change in its own
|
|
9
|
+
* or the anchor's size, a `space-1` gap from it and a
|
|
10
|
+
* `space-2` inset from the viewport edges, and calls `onDismiss` for a press
|
|
11
|
+
* outside it and outside `ignoreRef` (the button that toggles it).
|
|
12
|
+
* Browsers without the popover API get the same fixed placement.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useAnchoredPopover(popoverRef: RefObject<HTMLElement | null>, anchorRef: RefObject<HTMLElement | null>, ignoreRef: RefObject<HTMLElement | null>, onDismiss: () => void, { flipInline }?: AnchoredPopoverPlacement): void;
|
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
import { spacingScale } from '@scalewing/tokens';
|
|
2
2
|
import { useLayoutEffect, useRef } from 'react';
|
|
3
|
-
import { anchoredPosition } from '
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { anchoredPosition } from '../anchored-position.js';
|
|
4
|
+
/**
|
|
5
|
+
* The viewport a fixed popover can use: the root's client box, which leaves
|
|
6
|
+
* out a classic scrollbar (`innerWidth` and `100vw` include it). Falls back
|
|
7
|
+
* to the window size where nothing is laid out, as in jsdom.
|
|
8
|
+
*/
|
|
9
|
+
function layoutViewport() {
|
|
10
|
+
const root = document.documentElement;
|
|
11
|
+
return {
|
|
12
|
+
width: root.clientWidth || window.innerWidth,
|
|
13
|
+
height: root.clientHeight || window.innerHeight,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function placePopover(node, anchor, { flipInline = false }) {
|
|
17
|
+
const position = anchoredPosition(anchor.getBoundingClientRect(), { width: node.offsetWidth, height: node.offsetHeight }, layoutViewport(), {
|
|
6
18
|
gap: spacingScale[1],
|
|
7
19
|
inset: spacingScale[2],
|
|
8
20
|
rtl: getComputedStyle(anchor).direction === 'rtl',
|
|
21
|
+
flipInline,
|
|
9
22
|
});
|
|
10
23
|
node.style.left = `${position.left}px`;
|
|
11
24
|
node.style.top = `${position.top}px`;
|
|
12
25
|
}
|
|
13
26
|
/**
|
|
14
27
|
* Shows a mounted element on the top layer as a manual popover, keeps it
|
|
15
|
-
* beside `anchorRef` through scroll
|
|
16
|
-
*
|
|
28
|
+
* beside `anchorRef` through scroll, window resize, and a change in its own
|
|
29
|
+
* or the anchor's size, a `space-1` gap from it and a
|
|
30
|
+
* `space-2` inset from the viewport edges, and calls `onDismiss` for a press
|
|
31
|
+
* outside it and outside `ignoreRef` (the button that toggles it).
|
|
17
32
|
* Browsers without the popover API get the same fixed placement.
|
|
18
33
|
*/
|
|
19
|
-
export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss) {
|
|
34
|
+
export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss, { flipInline = false } = {}) {
|
|
20
35
|
const dismissRef = useRef(onDismiss);
|
|
21
36
|
useLayoutEffect(() => {
|
|
22
37
|
dismissRef.current = onDismiss;
|
|
@@ -34,7 +49,7 @@ export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss)
|
|
|
34
49
|
if (!node.matches(':popover-open'))
|
|
35
50
|
node.showPopover();
|
|
36
51
|
}
|
|
37
|
-
placePopover(node, anchor);
|
|
52
|
+
placePopover(node, anchor, { flipInline });
|
|
38
53
|
function onPointerDown(event) {
|
|
39
54
|
const target = event.target;
|
|
40
55
|
if (!(target instanceof Node))
|
|
@@ -45,17 +60,24 @@ export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss)
|
|
|
45
60
|
}
|
|
46
61
|
function onMove() {
|
|
47
62
|
if (node && anchor)
|
|
48
|
-
placePopover(node, anchor);
|
|
63
|
+
placePopover(node, anchor, { flipInline });
|
|
49
64
|
}
|
|
65
|
+
// A popover or anchor that changes size while open (new commands, a
|
|
66
|
+
// month with another row of weeks, a relabelled trigger) moves too.
|
|
67
|
+
// ResizeObserver is missing in some runtimes, such as jsdom.
|
|
68
|
+
const resizes = typeof ResizeObserver === 'function' ? new ResizeObserver(onMove) : null;
|
|
69
|
+
resizes?.observe(node);
|
|
70
|
+
resizes?.observe(anchor);
|
|
50
71
|
document.addEventListener('pointerdown', onPointerDown);
|
|
51
72
|
window.addEventListener('resize', onMove);
|
|
52
73
|
window.addEventListener('scroll', onMove, true);
|
|
53
74
|
return () => {
|
|
75
|
+
resizes?.disconnect();
|
|
54
76
|
document.removeEventListener('pointerdown', onPointerDown);
|
|
55
77
|
window.removeEventListener('resize', onMove);
|
|
56
78
|
window.removeEventListener('scroll', onMove, true);
|
|
57
79
|
if (supportsPopover && node.matches(':popover-open'))
|
|
58
80
|
node.hidePopover();
|
|
59
81
|
};
|
|
60
|
-
}, [anchorRef, ignoreRef, popoverRef]);
|
|
82
|
+
}, [anchorRef, flipInline, ignoreRef, popoverRef]);
|
|
61
83
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { zIndex } from './stacking.js';
|
|
3
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
3
4
|
const label = typographyVariants.label;
|
|
4
5
|
export function cssActionMenuClasses() {
|
|
5
6
|
return `.sw-action-menu {
|
|
@@ -39,20 +40,22 @@ export function cssActionMenuClasses() {
|
|
|
39
40
|
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
40
41
|
box-sizing: border-box;
|
|
41
42
|
color: var(--sw-color-text);
|
|
43
|
+
/* The popover layer's inset: 0 would ignore the placed left in right-to-left. */
|
|
44
|
+
inset: auto;
|
|
42
45
|
margin: 0;
|
|
43
46
|
max-height: var(--sw-select-max);
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
/* On the top layer 100% is the viewport without a classic scrollbar. */
|
|
48
|
+
max-width: calc(100% - var(--sw-space-2) - var(--sw-space-2));
|
|
49
|
+
/* Never narrower than a touch target. */
|
|
50
|
+
min-width: var(--sw-control-md-min-height);
|
|
46
51
|
overflow: auto;
|
|
47
52
|
padding: var(--sw-space-1);
|
|
48
53
|
position: fixed;
|
|
54
|
+
/* As wide as its longest command, until max-width wraps it. */
|
|
55
|
+
width: max-content;
|
|
49
56
|
${zIndex('popup')}
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
.sw-action-menu-list[hidden] {
|
|
53
|
-
display: none;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
59
|
.sw-action-menu-item {
|
|
57
60
|
align-items: center;
|
|
58
61
|
appearance: none;
|
|
@@ -65,7 +68,11 @@ export function cssActionMenuClasses() {
|
|
|
65
68
|
font-family: var(--sw-font-sans);
|
|
66
69
|
font-size: ${label.fontSize}px;
|
|
67
70
|
gap: var(--sw-space-2);
|
|
71
|
+
line-height: ${label.lineHeight}px;
|
|
68
72
|
min-height: var(--sw-control-xs-min-height);
|
|
73
|
+
/* A long command wraps inside the capped menu, a word at a time if it must. */
|
|
74
|
+
overflow-wrap: anywhere;
|
|
75
|
+
padding-block: var(--sw-space-1);
|
|
69
76
|
padding-inline: var(--sw-control-xs-padding-inline);
|
|
70
77
|
text-align: start;
|
|
71
78
|
width: 100%;
|
|
@@ -83,6 +90,18 @@ export function cssActionMenuClasses() {
|
|
|
83
90
|
|
|
84
91
|
.sw-action-menu-item-danger {
|
|
85
92
|
color: var(--sw-color-danger);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* A coarse pointer gets the full touch target: the trigger at least square, and every command as tall. */
|
|
96
|
+
@media ${coarsePointerQuery} {
|
|
97
|
+
.sw-action-menu-trigger {
|
|
98
|
+
min-height: ${touchTarget};
|
|
99
|
+
min-width: ${touchTarget};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.sw-action-menu-item {
|
|
103
|
+
min-height: ${touchTarget};
|
|
104
|
+
}
|
|
86
105
|
}`;
|
|
87
106
|
}
|
|
88
107
|
export function actionMenuClassCatalog() {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { buttonSizes } from '@scalewing/tokens';
|
|
2
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
3
|
+
/* Square at every Button size: as wide as the size's control height. */
|
|
4
|
+
function squareRules() {
|
|
5
|
+
return buttonSizes
|
|
6
|
+
.map((size) => `.sw-button-${size}.sw-calendar-button { min-width: var(--sw-control-${size}-min-height); }`)
|
|
7
|
+
.join('\n');
|
|
8
|
+
}
|
|
9
|
+
export function cssCalendarButtonClasses() {
|
|
10
|
+
return `.sw-button.sw-calendar-button {
|
|
11
|
+
flex: none;
|
|
12
|
+
padding-inline: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
${squareRules()}
|
|
16
|
+
|
|
17
|
+
.sw-calendar-button:not(.sw-button-md) .sw-date-field-glyph {
|
|
18
|
+
height: var(--sw-space-4);
|
|
19
|
+
width: var(--sw-space-4);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* A coarse pointer gets the full touch target at the smaller sizes too. */
|
|
23
|
+
@media ${coarsePointerQuery} {
|
|
24
|
+
.sw-button.sw-calendar-button {
|
|
25
|
+
min-height: ${touchTarget};
|
|
26
|
+
min-width: ${touchTarget};
|
|
27
|
+
}
|
|
28
|
+
}`;
|
|
29
|
+
}
|
|
30
|
+
export function calendarButtonClassCatalog() {
|
|
31
|
+
return ['sw-calendar-button'];
|
|
32
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { compactTypographyVariants, typographyVariants, } from '@scalewing/tokens';
|
|
2
|
-
import { breakpointQuery } from './breakpoints.js';
|
|
3
1
|
import { cssActionBarClasses } from './css-action-bar.js';
|
|
4
2
|
import { cssActionMenuClasses } from './css-action-menu.js';
|
|
5
3
|
import { cssAccordionClasses } from './css-accordion.js';
|
|
6
4
|
import { cssButtonClasses } from './css-button.js';
|
|
5
|
+
import { cssCalendarButtonClasses } from './css-calendar-button.js';
|
|
7
6
|
import { cssChartClasses } from './css-chart.js';
|
|
8
7
|
import { cssCheckboxClasses } from './css-checkbox.js';
|
|
9
8
|
import { cssRadioGroupClasses } from './css-radio-group.js';
|
|
@@ -14,6 +13,7 @@ import { cssDialogClasses } from './css-dialog.js';
|
|
|
14
13
|
import { cssSelectClasses } from './css-select.js';
|
|
15
14
|
import { cssSplitClasses } from './css-split.js';
|
|
16
15
|
import { cssSwitchClasses } from './css-switch.js';
|
|
16
|
+
import { cssTextClasses } from './css-text.js';
|
|
17
17
|
import { cssSpinnerClasses } from './css-spinner.js';
|
|
18
18
|
import { cssProgressClasses } from './css-progress.js';
|
|
19
19
|
import { cssTooltipClasses } from './css-tooltip.js';
|
|
@@ -57,22 +57,6 @@ function reducedTransparencyRules() {
|
|
|
57
57
|
}
|
|
58
58
|
}`;
|
|
59
59
|
}
|
|
60
|
-
function textVariantRules() {
|
|
61
|
-
return Object.entries(typographyVariants)
|
|
62
|
-
.map(([name, variant]) => {
|
|
63
|
-
const tabular = 'tabularNums' in variant && variant.tabularNums
|
|
64
|
-
? ' font-variant-numeric: tabular-nums;'
|
|
65
|
-
: '';
|
|
66
|
-
return `.sw-text-${name} { font-family: var(--sw-font-sans); font-size: ${variant.fontSize}px; line-height: ${variant.lineHeight}px; font-weight: ${variant.fontWeight}; letter-spacing: ${variant.letterSpacing}px;${tabular} }`;
|
|
67
|
-
})
|
|
68
|
-
.join('\n');
|
|
69
|
-
}
|
|
70
|
-
function compactTextVariantRules() {
|
|
71
|
-
const rules = Object.entries(compactTypographyVariants)
|
|
72
|
-
.map(([name, variant]) => ` .sw-text-${name} { font-size: ${variant.fontSize}px; line-height: ${variant.lineHeight}px; letter-spacing: ${variant.letterSpacing}px; }`)
|
|
73
|
-
.join('\n');
|
|
74
|
-
return `@media ${breakpointQuery('below', 'md')} {\n${rules}\n}`;
|
|
75
|
-
}
|
|
76
60
|
export function cssComponentClasses() {
|
|
77
61
|
return `.sw-card {
|
|
78
62
|
border-radius: var(--sw-radius-lg);
|
|
@@ -105,10 +89,6 @@ ${cssPopupHostRules()}
|
|
|
105
89
|
background: var(--sw-color-subtle);
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
.sw-text-align-start { text-align: start; }
|
|
109
|
-
.sw-text-align-center { text-align: center; }
|
|
110
|
-
.sw-text-align-end { text-align: end; }
|
|
111
|
-
|
|
112
92
|
.sw-truncate {
|
|
113
93
|
overflow: hidden;
|
|
114
94
|
text-overflow: ellipsis;
|
|
@@ -119,9 +99,7 @@ ${cssPopupHostRules()}
|
|
|
119
99
|
font-variant-numeric: tabular-nums;
|
|
120
100
|
}
|
|
121
101
|
|
|
122
|
-
${
|
|
123
|
-
|
|
124
|
-
${compactTextVariantRules()}
|
|
102
|
+
${cssTextClasses()}
|
|
125
103
|
|
|
126
104
|
${cssButtonClasses()}
|
|
127
105
|
|
|
@@ -133,6 +111,8 @@ ${cssDataClasses()}
|
|
|
133
111
|
|
|
134
112
|
${cssDateFieldClasses()}
|
|
135
113
|
|
|
114
|
+
${cssCalendarButtonClasses()}
|
|
115
|
+
|
|
136
116
|
${cssChartClasses()}
|
|
137
117
|
|
|
138
118
|
${cssChromeClasses()}
|
|
@@ -142,7 +142,8 @@ function calendarRules() {
|
|
|
142
142
|
height: auto;
|
|
143
143
|
inset: auto;
|
|
144
144
|
margin: 0;
|
|
145
|
-
|
|
145
|
+
/* On the top layer 100% is the viewport without a classic scrollbar. */
|
|
146
|
+
max-width: calc(100% - var(--sw-space-2) - var(--sw-space-2));
|
|
146
147
|
overflow: visible;
|
|
147
148
|
padding: var(--sw-space-3);
|
|
148
149
|
position: fixed;
|
package/dist/css/css-palettes.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { colorsForPalette, createTheme, paletteHasStylesheet, palettesWithStylesheets, secondaryActionBorder, semanticColorKeys, } from '@scalewing/tokens';
|
|
2
|
+
/**
|
|
3
|
+
* A palette sets the colours it overlays, plus the accent tint whenever it
|
|
4
|
+
* moves the accent or surface the tint is mixed from.
|
|
5
|
+
*/
|
|
6
|
+
function overlaidColorKey(key, overlay) {
|
|
7
|
+
if (key === 'accentSubtle') {
|
|
8
|
+
return Boolean(overlay.accentSubtle || overlay.accent || overlay.surface);
|
|
9
|
+
}
|
|
10
|
+
return Boolean(overlay[key]);
|
|
11
|
+
}
|
|
2
12
|
function paletteCustomProperties(id, scheme) {
|
|
3
13
|
const overlay = colorsForPalette(id, scheme);
|
|
4
14
|
const theme = createTheme({ colorScheme: scheme, palette: id });
|
|
5
15
|
const lines = [];
|
|
6
16
|
for (const key of semanticColorKeys) {
|
|
7
|
-
if (overlay
|
|
17
|
+
if (overlaidColorKey(key, overlay)) {
|
|
8
18
|
lines.push(` --sw-color-${key}: ${theme.colors[key]};`);
|
|
9
19
|
}
|
|
10
20
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cssTextClasses(): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { compactTypographyVariants, typographyVariants, } from '@scalewing/tokens';
|
|
2
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
3
|
+
function textClassNames() {
|
|
4
|
+
return Object.keys(typographyVariants).map((name) => `.sw-text-${name}`);
|
|
5
|
+
}
|
|
6
|
+
/*
|
|
7
|
+
* Text renders `p` and `h1`–`h4` by default. The browser gives those a
|
|
8
|
+
* block margin, which a flex Stack or Inline does not collapse, so it would
|
|
9
|
+
* add to every gap. `:where()` keeps the reset at zero specificity: it beats
|
|
10
|
+
* the browser's margins (author styles win over the user agent's) and loses
|
|
11
|
+
* to any authored margin, such as `sw-sr-only` or a consumer's class.
|
|
12
|
+
*/
|
|
13
|
+
function textMarginReset() {
|
|
14
|
+
return `:where(${textClassNames().join(', ')}) { margin: 0; }`;
|
|
15
|
+
}
|
|
16
|
+
function textVariantRules() {
|
|
17
|
+
return Object.entries(typographyVariants)
|
|
18
|
+
.map(([name, variant]) => {
|
|
19
|
+
const tabular = 'tabularNums' in variant && variant.tabularNums
|
|
20
|
+
? ' font-variant-numeric: tabular-nums;'
|
|
21
|
+
: '';
|
|
22
|
+
return `.sw-text-${name} { font-family: var(--sw-font-sans); font-size: ${variant.fontSize}px; line-height: ${variant.lineHeight}px; font-weight: ${variant.fontWeight}; letter-spacing: ${variant.letterSpacing}px;${tabular} }`;
|
|
23
|
+
})
|
|
24
|
+
.join('\n');
|
|
25
|
+
}
|
|
26
|
+
function compactTextVariantRules() {
|
|
27
|
+
const rules = Object.entries(compactTypographyVariants)
|
|
28
|
+
.map(([name, variant]) => ` .sw-text-${name} { font-size: ${variant.fontSize}px; line-height: ${variant.lineHeight}px; letter-spacing: ${variant.letterSpacing}px; }`)
|
|
29
|
+
.join('\n');
|
|
30
|
+
return `@media ${breakpointQuery('below', 'md')} {\n${rules}\n}`;
|
|
31
|
+
}
|
|
32
|
+
export function cssTextClasses() {
|
|
33
|
+
return `.sw-text-align-start { text-align: start; }
|
|
34
|
+
.sw-text-align-center { text-align: center; }
|
|
35
|
+
.sw-text-align-end { text-align: end; }
|
|
36
|
+
|
|
37
|
+
${textMarginReset()}
|
|
38
|
+
|
|
39
|
+
${textVariantRules()}
|
|
40
|
+
|
|
41
|
+
${compactTextVariantRules()}`;
|
|
42
|
+
}
|
package/dist/css/stacking.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* 4. `topChrome`: the sticky AppHeader, over a lifted surface that scrolls up
|
|
14
14
|
* under it (the header is the page's way back, and a popup opens downward).
|
|
15
15
|
* 5. `popup`: a popup outside any lifted surface (Select list, ActionMenu
|
|
16
|
-
* list, Tooltip, DateField calendar) over
|
|
16
|
+
* list, Tooltip, the DateField and CalendarButton calendar) over
|
|
17
|
+
* everything in the page.
|
|
17
18
|
*
|
|
18
19
|
* Dialog and Toast sit on the browser's top layer, above all of these.
|
|
19
20
|
*/
|
|
@@ -29,9 +30,11 @@ export function zIndex(layer) {
|
|
|
29
30
|
}
|
|
30
31
|
const openPopups = [
|
|
31
32
|
'.sw-select-list',
|
|
32
|
-
'.sw-action-menu-list:not([hidden])',
|
|
33
|
-
'.sw-tooltip:not([hidden])',
|
|
34
33
|
// Rendered only while open; on the top layer where the popover API exists.
|
|
34
|
+
'.sw-action-menu-list',
|
|
35
|
+
'.sw-tooltip:not([hidden])',
|
|
36
|
+
// DateField and CalendarButton share it. Rendered only while open; on the
|
|
37
|
+
// top layer where the popover API exists.
|
|
35
38
|
'.sw-date-field-calendar',
|
|
36
39
|
].join(', ');
|
|
37
40
|
const popupHosts = ['.sw-card-glass', '.sw-accordion'];
|
package/dist/css/stylesheet.js
CHANGED
|
@@ -4,6 +4,7 @@ import { actionMenuClassCatalog } from './css-action-menu.js';
|
|
|
4
4
|
import { accordionClassCatalog } from './css-accordion.js';
|
|
5
5
|
import { switchClassCatalog } from './css-switch.js';
|
|
6
6
|
import { dateFieldClassCatalog } from './css-date-field.js';
|
|
7
|
+
import { calendarButtonClassCatalog } from './css-calendar-button.js';
|
|
7
8
|
import { checkboxClassCatalog } from './css-checkbox.js';
|
|
8
9
|
import { radioGroupClassCatalog } from './css-radio-group.js';
|
|
9
10
|
import { spinnerClassCatalog } from './css-spinner.js';
|
|
@@ -57,6 +58,7 @@ export function utilityClassCatalog() {
|
|
|
57
58
|
...actionBarClassCatalog(),
|
|
58
59
|
...switchClassCatalog(),
|
|
59
60
|
...dateFieldClassCatalog(),
|
|
61
|
+
...calendarButtonClassCatalog(),
|
|
60
62
|
...checkboxClassCatalog(),
|
|
61
63
|
...radioGroupClassCatalog(),
|
|
62
64
|
...spinnerClassCatalog(),
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The comfortable touch target on a coarse pointer: the md control height,
|
|
3
|
+
* 44 px, both ways. Controls drawn smaller for a fine pointer (CalendarButton
|
|
4
|
+
* at sm and xs, the ActionMenu trigger and commands) grow to it on touch.
|
|
5
|
+
*/
|
|
6
|
+
export const touchTarget = 'var(--sw-control-md-min-height)';
|
|
7
|
+
export const coarsePointerQuery = '(pointer: coarse)';
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { Accordion } from './components/Accordion.js';
|
|
|
4
4
|
export type { AccordionProps, AccordionSize } from './components/Accordion.js';
|
|
5
5
|
export { Switch, type SwitchProps } from './components/Switch.js';
|
|
6
6
|
export { DateField, type DateFieldLabels, type DateFieldProps, type WeekStart, } from './components/DateField.js';
|
|
7
|
+
export { CalendarButton, type CalendarButtonLabels, type CalendarButtonProps, } from './components/CalendarButton.js';
|
|
7
8
|
export { Checkbox, type CheckboxProps } from './components/Checkbox.js';
|
|
8
9
|
export { Spinner, type SpinnerProps, type SpinnerSize, } from './components/Spinner.js';
|
|
9
10
|
export { Progress, type ProgressProps, type ProgressTone, } from './components/Progress.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { ActionBar } from './components/ActionBar.js';
|
|
|
3
3
|
export { Accordion } from './components/Accordion.js';
|
|
4
4
|
export { Switch } from './components/Switch.js';
|
|
5
5
|
export { DateField, } from './components/DateField.js';
|
|
6
|
+
export { CalendarButton, } from './components/CalendarButton.js';
|
|
6
7
|
export { Checkbox } from './components/Checkbox.js';
|
|
7
8
|
export { Spinner, } from './components/Spinner.js';
|
|
8
9
|
export { Progress, } from './components/Progress.js';
|
package/dist/palette/amber.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #B45309;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #FAF3EE;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #FCD34D;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #322E23;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(68, 61, 38, 0.72);
|
package/dist/palette/capri.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #0077B6;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #F2F8FB;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #48CAE4;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #202D32;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(36, 59, 66, 0.72);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #0066CC;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #E6F0FA;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #5AC8FA;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #222D34;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(39, 59, 70, 0.72);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #4F6BED;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #FFFFFF;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #93A4FF;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #282A35;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(49, 52, 71, 0.72);
|
package/dist/palette/fuchsia.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #C026D3;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #FEFBFE;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #E879F9;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #302534;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(65, 45, 69, 0.72);
|
package/dist/palette/harvest.css
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
--sw-color-muted: #5C5346;
|
|
8
8
|
--sw-color-accent: #3E3E24;
|
|
9
9
|
--sw-color-onAccent: #FFFFFF;
|
|
10
|
+
--sw-color-accentSubtle: #DAD5C4;
|
|
10
11
|
--sw-color-border: #C9C2AE;
|
|
11
12
|
--sw-button-secondary-border: #C9C2AE;
|
|
12
13
|
--sw-glass-blur: 24px;
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
--sw-color-muted: #B8B090;
|
|
24
25
|
--sw-color-accent: #CEA453;
|
|
25
26
|
--sw-color-onAccent: #101214;
|
|
27
|
+
--sw-color-accentSubtle: #2F4A2C;
|
|
26
28
|
--sw-color-border: #3D5238;
|
|
27
29
|
--sw-button-secondary-border: #3D5238;
|
|
28
30
|
--sw-glass-blur: 24px;
|
package/dist/palette/ink.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #1D1D1F;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #E8E8E9;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #F5F5F7;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #323234;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(67, 67, 69, 0.72);
|
package/dist/palette/mocha.css
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
--sw-color-muted: #6B5348;
|
|
8
8
|
--sw-color-accent: #473333;
|
|
9
9
|
--sw-color-onAccent: #FFFFFF;
|
|
10
|
+
--sw-color-accentSubtle: #E6DBCF;
|
|
10
11
|
--sw-color-border: #E0CBA8;
|
|
11
12
|
--sw-button-secondary-border: #E0CBA8;
|
|
12
13
|
--sw-glass-blur: 24px;
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
--sw-color-muted: #D4C4A8;
|
|
24
25
|
--sw-color-accent: #E8C69F;
|
|
25
26
|
--sw-color-onAccent: #101214;
|
|
27
|
+
--sw-color-accentSubtle: #684F4B;
|
|
26
28
|
--sw-color-border: #6B5353;
|
|
27
29
|
--sw-button-secondary-border: #6B5353;
|
|
28
30
|
--sw-glass-blur: 24px;
|
package/dist/palette/navy.css
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[data-theme='light'] {
|
|
4
4
|
--sw-color-accent: #0A2540;
|
|
5
5
|
--sw-color-onAccent: #FFFFFF;
|
|
6
|
+
--sw-color-accentSubtle: #E7E9EC;
|
|
6
7
|
--sw-glass-blur: 24px;
|
|
7
8
|
--sw-glass-saturate: 1;
|
|
8
9
|
--sw-glass-fill: rgba(255, 255, 255, 1);
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
[data-theme='dark'] {
|
|
14
15
|
--sw-color-accent: #7EB8FF;
|
|
15
16
|
--sw-color-onAccent: #101214;
|
|
17
|
+
--sw-color-accentSubtle: #262C35;
|
|
16
18
|
--sw-glass-blur: 24px;
|
|
17
19
|
--sw-glass-saturate: 1.8;
|
|
18
20
|
--sw-glass-fill: rgba(46, 56, 71, 0.72);
|