@domicile-desktop/component-library 0.0.0-alpha-e42226f4af44 → 0.0.0-alpha-fff3e247bb02
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/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@base-ui/react": "^1.8.0",
|
|
4
4
|
"@cprussin/option-result": "^2.0.1",
|
|
5
|
-
"@domicile-desktop/sdk": "0.0.0-alpha-
|
|
6
|
-
"@domicile-desktop/system-apps": "0.0.0-alpha-
|
|
5
|
+
"@domicile-desktop/sdk": "0.0.0-alpha-fff3e247bb02",
|
|
6
|
+
"@domicile-desktop/system-apps": "0.0.0-alpha-fff3e247bb02",
|
|
7
7
|
"@pandacss/dev": "^1.12.1",
|
|
8
8
|
"@phosphor-icons/react": "^2.1.10"
|
|
9
9
|
},
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"./folders": "./src/FilePicker/folders.ts",
|
|
53
53
|
"./Input": "./src/Input/Input.tsx",
|
|
54
54
|
"./Kbd": "./src/Kbd/Kbd.tsx",
|
|
55
|
+
"./LevelToast": "./src/LevelToast/LevelToast.tsx",
|
|
55
56
|
"./list-walk": "./src/list-walk.ts",
|
|
56
57
|
"./ModalDialog": "./src/ModalDialog/ModalDialog.tsx",
|
|
57
58
|
"./media-theme-source": "./src/ThemeSwitch/media-theme-source.ts",
|
|
@@ -115,5 +116,5 @@
|
|
|
115
116
|
"test:unit": "bun test"
|
|
116
117
|
},
|
|
117
118
|
"type": "module",
|
|
118
|
-
"version": "0.0.0-alpha-
|
|
119
|
+
"version": "0.0.0-alpha-fff3e247bb02"
|
|
119
120
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { css } from "../../styled-system/css";
|
|
4
|
+
import { circle, hstack } from "../../styled-system/patterns";
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
/**
|
|
8
|
+
* How many times the level changed. A new count keeps the toast up for
|
|
9
|
+
* another full hold, or shows it again once it has gone.
|
|
10
|
+
*/
|
|
11
|
+
changes: number;
|
|
12
|
+
/** Drawn large before the label. */
|
|
13
|
+
icon: ReactNode;
|
|
14
|
+
/** What the level is of, such as "Volume". */
|
|
15
|
+
label: string;
|
|
16
|
+
/** From 0 to 1. */
|
|
17
|
+
level: number;
|
|
18
|
+
/** Says "Muted" and grays the meter, such as for muted audio. */
|
|
19
|
+
muted: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A large toast showing a level, such as volume or brightness, that fades out
|
|
24
|
+
* a moment after its last change.
|
|
25
|
+
*
|
|
26
|
+
* A change while it is up updates it in place and starts the hold over, so
|
|
27
|
+
* holding a key does not replay its entrance. It hides on its own
|
|
28
|
+
* `animationend`, not a timer, so the duration lives only in the stylesheet.
|
|
29
|
+
* The caller positions it; it ignores the pointer.
|
|
30
|
+
*/
|
|
31
|
+
export const LevelToast = ({ changes, icon, label, level, muted }: Props) => {
|
|
32
|
+
const [goneAfter, setGoneAfter] = useState<number | undefined>();
|
|
33
|
+
|
|
34
|
+
return goneAfter === changes ? undefined : (
|
|
35
|
+
<output
|
|
36
|
+
className={toastStyles}
|
|
37
|
+
// Alternates the fade-out between two copies of one keyframe, so the
|
|
38
|
+
// browser restarts it while the entrance, unchanged, does not replay.
|
|
39
|
+
data-again={changes % 2 === 0 ? "" : undefined}
|
|
40
|
+
data-muted={muted ? "" : undefined}
|
|
41
|
+
onAnimationEnd={(event) => {
|
|
42
|
+
if (event.target === event.currentTarget) {
|
|
43
|
+
setGoneAfter(changes);
|
|
44
|
+
}
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<span className={panelStyles}>
|
|
48
|
+
<span className={iconStyles}>{icon}</span>
|
|
49
|
+
<span className={readoutStyles}>
|
|
50
|
+
<span className={labelRowStyles}>
|
|
51
|
+
<span>{label}</span>
|
|
52
|
+
<span className={valueStyles}>
|
|
53
|
+
{muted ? "Muted" : `${String(Math.round(level * 100))}%`}
|
|
54
|
+
</span>
|
|
55
|
+
</span>
|
|
56
|
+
<span
|
|
57
|
+
aria-valuemax={100}
|
|
58
|
+
aria-valuemin={0}
|
|
59
|
+
aria-valuenow={Math.round(level * 100)}
|
|
60
|
+
className={meterStyles}
|
|
61
|
+
data-muted={muted ? "" : undefined}
|
|
62
|
+
role="meter"
|
|
63
|
+
// Inline because the level is a runtime value Panda cannot extract.
|
|
64
|
+
style={{ "--level-toast-level": level } as CSSProperties}
|
|
65
|
+
/>
|
|
66
|
+
</span>
|
|
67
|
+
</span>
|
|
68
|
+
</output>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Holds, then fades the whole toast out.
|
|
73
|
+
const toastStyles = css({
|
|
74
|
+
"&[data-again]": {
|
|
75
|
+
animationName: "levelToastHoldAgain",
|
|
76
|
+
},
|
|
77
|
+
animation: "levelToastHold {durations.levelToast} {easings.linear} forwards",
|
|
78
|
+
display: "block",
|
|
79
|
+
pointerEvents: "none",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Rises in once per showing. A frosted card like `Toaster`'s.
|
|
83
|
+
const panelStyles = hstack({
|
|
84
|
+
animation: "levelToastEnter {durations.normal} {easings.outQuart}",
|
|
85
|
+
backdropFilter: "blur({spacing.4}) saturate(160%)",
|
|
86
|
+
backgroundColor: "color-mix(in oklab, {colors.card} 85%, transparent)",
|
|
87
|
+
border: "1px solid color-mix(in oklab, {colors.foreground} 12%, transparent)",
|
|
88
|
+
borderRadius: "3xl",
|
|
89
|
+
boxShadow: "lifted",
|
|
90
|
+
color: "foreground",
|
|
91
|
+
gap: 4,
|
|
92
|
+
inlineSize: 88,
|
|
93
|
+
paddingBlock: 4,
|
|
94
|
+
paddingInline: 5,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const iconStyles = circle({
|
|
98
|
+
"[data-muted] &": {
|
|
99
|
+
backgroundColor:
|
|
100
|
+
"color-mix(in oklab, {colors.foreground} 10%, transparent)",
|
|
101
|
+
color: "muted",
|
|
102
|
+
},
|
|
103
|
+
backgroundColor: "color-mix(in oklab, {colors.accent} 18%, transparent)",
|
|
104
|
+
color: "accent",
|
|
105
|
+
flexShrink: 0,
|
|
106
|
+
size: 12,
|
|
107
|
+
transition:
|
|
108
|
+
"color {durations.fast} {easings.out}, background-color {durations.fast} {easings.out}",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const readoutStyles = css({
|
|
112
|
+
display: "flex",
|
|
113
|
+
flex: 1,
|
|
114
|
+
flexDirection: "column",
|
|
115
|
+
gap: 2.5,
|
|
116
|
+
minInlineSize: 0,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const labelRowStyles = hstack({
|
|
120
|
+
fontSize: "md",
|
|
121
|
+
fontWeight: "medium",
|
|
122
|
+
justify: "space-between",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const valueStyles = css({
|
|
126
|
+
fontVariantNumeric: "tabular-nums",
|
|
127
|
+
fontWeight: "semibold",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// A track filled from the start edge. The fill eases to each new level, which
|
|
131
|
+
// shows while a key is held.
|
|
132
|
+
const meterStyles = css({
|
|
133
|
+
"&::after": {
|
|
134
|
+
backgroundColor: "accent",
|
|
135
|
+
borderRadius: "full",
|
|
136
|
+
content: '""',
|
|
137
|
+
inlineSize: "calc(var(--level-toast-level) * 100%)",
|
|
138
|
+
insetBlock: 0,
|
|
139
|
+
insetInlineStart: 0,
|
|
140
|
+
position: "absolute",
|
|
141
|
+
transition:
|
|
142
|
+
"inline-size {durations.fast} {easings.out}, background-color {durations.fast} {easings.out}",
|
|
143
|
+
},
|
|
144
|
+
"&[data-muted]::after": {
|
|
145
|
+
backgroundColor: "muted",
|
|
146
|
+
},
|
|
147
|
+
backgroundColor: "color-mix(in oklab, {colors.foreground} 14%, transparent)",
|
|
148
|
+
blockSize: 2,
|
|
149
|
+
borderRadius: "full",
|
|
150
|
+
overflow: "hidden",
|
|
151
|
+
position: "relative",
|
|
152
|
+
});
|
package/src/pandacss-preset.ts
CHANGED
|
@@ -25,6 +25,13 @@ const quarterStepSpacing = Object.fromEntries(
|
|
|
25
25
|
),
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
+
// `LevelToast` holds, then fades out over the last fifth.
|
|
29
|
+
const levelToastHold = {
|
|
30
|
+
"0%": { opacity: "1" },
|
|
31
|
+
"80%": { opacity: "1" },
|
|
32
|
+
"100%": { opacity: "0" },
|
|
33
|
+
};
|
|
34
|
+
|
|
28
35
|
export const domicilePreset = definePreset({
|
|
29
36
|
conditions: {
|
|
30
37
|
extend: {
|
|
@@ -153,6 +160,18 @@ export const domicilePreset = definePreset({
|
|
|
153
160
|
theme: {
|
|
154
161
|
extend: {
|
|
155
162
|
keyframes: {
|
|
163
|
+
// `LevelToast`'s entrance, once per showing.
|
|
164
|
+
levelToastEnter: {
|
|
165
|
+
"0%": {
|
|
166
|
+
opacity: "0",
|
|
167
|
+
transform: "translateY({spacing.3}) scale(0.96)",
|
|
168
|
+
},
|
|
169
|
+
"100%": { opacity: "1", transform: "none" },
|
|
170
|
+
},
|
|
171
|
+
// `LevelToast`'s hold and fade-out. Two copies so a change can
|
|
172
|
+
// restart it by switching names.
|
|
173
|
+
levelToastHold,
|
|
174
|
+
levelToastHoldAgain: levelToastHold,
|
|
156
175
|
// `Notice`: appears quickly, holds long enough to read, then fades
|
|
157
176
|
// out on its own.
|
|
158
177
|
noticeShown: {
|
|
@@ -405,6 +424,8 @@ export const domicilePreset = definePreset({
|
|
|
405
424
|
durations: {
|
|
406
425
|
// Wallpaper crossfade. Long because nothing waits on it.
|
|
407
426
|
crossfade: { value: "2s" },
|
|
427
|
+
// How long a `LevelToast` stays up after its last change.
|
|
428
|
+
levelToast: { value: "2s" },
|
|
408
429
|
// How long a `Notice` stays up. Longer than the scale above, which
|
|
409
430
|
// is for control feedback, not reading.
|
|
410
431
|
notice: { value: "1.5s" },
|
|
@@ -1307,6 +1307,10 @@ const tokens = {
|
|
|
1307
1307
|
"value": "2s",
|
|
1308
1308
|
"variable": "var(--gXQUBH)"
|
|
1309
1309
|
},
|
|
1310
|
+
"durations.levelToast": {
|
|
1311
|
+
"value": "2s",
|
|
1312
|
+
"variable": "var(--gbbHhs)"
|
|
1313
|
+
},
|
|
1310
1314
|
"durations.notice": {
|
|
1311
1315
|
"value": "1.5s",
|
|
1312
1316
|
"variable": "var(--infmmp)"
|
|
@@ -27,7 +27,7 @@ export type AnimationToken = "spin" | "ping" | "pulse" | "bounce"
|
|
|
27
27
|
|
|
28
28
|
export type BorderWidthToken = "fauxBold"
|
|
29
29
|
|
|
30
|
-
export type DurationToken = "fastest" | "faster" | "fast" | "normal" | "slow" | "slower" | "slowest" | "crossfade" | "notice" | "pulse" | "spin" | "wipe"
|
|
30
|
+
export type DurationToken = "fastest" | "faster" | "fast" | "normal" | "slow" | "slower" | "slowest" | "crossfade" | "levelToast" | "notice" | "pulse" | "spin" | "wipe"
|
|
31
31
|
|
|
32
32
|
export type EasingToken = "default" | "linear" | "in" | "out" | "in-out" | "emphasized" | "outBack" | "outQuart"
|
|
33
33
|
|
|
@@ -169,7 +169,7 @@ export interface UtilityValues {
|
|
|
169
169
|
transitionProperty: "common" | "colors" | "size" | "position" | "background";
|
|
170
170
|
transition: "all" | "common" | "size" | "position" | "background" | "colors" | "opacity" | "shadow" | "transform";
|
|
171
171
|
animation: Tokens["animations"];
|
|
172
|
-
animationName: "ping" | "bounce" | "noticeShown" | "pulse" | "spin" | "themeSwipeFromBottom" | "themeSwipeFromTop" | "toastCountdown";
|
|
172
|
+
animationName: "ping" | "bounce" | "levelToastEnter" | "levelToastHold" | "levelToastHoldAgain" | "noticeShown" | "pulse" | "spin" | "themeSwipeFromBottom" | "themeSwipeFromTop" | "toastCountdown";
|
|
173
173
|
animationTimingFunction: Tokens["easings"];
|
|
174
174
|
animationDuration: Tokens["durations"];
|
|
175
175
|
animationDelay: Tokens["durations"];
|