@rtzll/reflow 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/basic.js +2 -7
- package/dist/compositions.css +3 -3
- package/dist/drawer.css +240 -0
- package/dist/drawer.d.ts +45 -0
- package/dist/drawer.js +228 -0
- package/dist/foundations.css +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/sidebar.css +14 -57
- package/dist/sidebar.js +9 -13
- package/dist/status-icon.d.ts +3 -0
- package/dist/status-icon.js +7 -0
- package/dist/styles.css +2 -0
- package/dist/toaster.css +268 -0
- package/dist/toaster.d.ts +79 -0
- package/dist/toaster.js +418 -0
- package/package.json +1 -1
package/dist/sidebar.css
CHANGED
|
@@ -926,68 +926,27 @@
|
|
|
926
926
|
margin-inline-start: 8px;
|
|
927
927
|
}
|
|
928
928
|
|
|
929
|
-
/* Mobile drawer: below 768px the rail
|
|
929
|
+
/* Mobile drawer: below 768px the rail moves into a left Drawer, which brings
|
|
930
|
+
the slide, the drag to close and the backdrop; it keeps the rail's width,
|
|
931
|
+
surface and shadow. */
|
|
930
932
|
.rf-sidebar-drawer-trigger {
|
|
931
933
|
display: none;
|
|
932
934
|
}
|
|
933
|
-
.rf-sidebar-drawer {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
inset-inline-start: 0;
|
|
937
|
-
box-sizing: border-box;
|
|
938
|
-
width: min(288px, 100vw);
|
|
939
|
-
max-width: none;
|
|
940
|
-
height: 100dvh;
|
|
941
|
-
max-height: none;
|
|
942
|
-
margin: 0;
|
|
943
|
-
padding: 0;
|
|
935
|
+
.rf-drawer.rf-sidebar-drawer[data-side] {
|
|
936
|
+
--rf-drawer-bg: var(--rf-surface-2);
|
|
937
|
+
inline-size: min(288px, 100vw);
|
|
944
938
|
overflow: hidden;
|
|
945
|
-
|
|
946
|
-
background: var(--rf-surface-2);
|
|
947
|
-
box-shadow: var(--rf-sb-surface-shadow);
|
|
948
|
-
color: var(--rf-fg);
|
|
949
|
-
translate: -100% 0;
|
|
950
|
-
transition:
|
|
951
|
-
translate var(--rf-exit-moderate),
|
|
952
|
-
display var(--rf-exit-moderate) allow-discrete,
|
|
953
|
-
overlay var(--rf-exit-moderate) allow-discrete;
|
|
954
|
-
}
|
|
955
|
-
.rf-sidebar-drawer[open] {
|
|
956
|
-
translate: 0 0;
|
|
957
|
-
transition:
|
|
958
|
-
translate var(--rf-spring-moderate),
|
|
959
|
-
display var(--rf-spring-moderate) allow-discrete,
|
|
960
|
-
overlay var(--rf-spring-moderate) allow-discrete;
|
|
961
|
-
}
|
|
962
|
-
@starting-style {
|
|
963
|
-
.rf-sidebar-drawer[open] {
|
|
964
|
-
translate: -100% 0;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
.rf-sidebar-drawer::backdrop {
|
|
968
|
-
background: light-dark(rgb(0 0 0 / 40%), rgb(0 0 0 / 80%));
|
|
969
|
-
opacity: 0;
|
|
970
|
-
transition:
|
|
971
|
-
opacity var(--rf-exit-moderate),
|
|
972
|
-
display var(--rf-exit-moderate) allow-discrete,
|
|
973
|
-
overlay var(--rf-exit-moderate) allow-discrete;
|
|
974
|
-
}
|
|
975
|
-
.rf-sidebar-drawer[open]::backdrop {
|
|
976
|
-
opacity: 1;
|
|
977
|
-
transition:
|
|
978
|
-
opacity 160ms ease,
|
|
979
|
-
display 160ms allow-discrete,
|
|
980
|
-
overlay 160ms allow-discrete;
|
|
939
|
+
box-shadow: var(--rf-drawer-tail), var(--rf-sb-surface-shadow);
|
|
981
940
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
}
|
|
941
|
+
.rf-sidebar-drawer .rf-drawer-body {
|
|
942
|
+
display: flex;
|
|
943
|
+
flex-direction: column;
|
|
986
944
|
}
|
|
987
|
-
.rf-sidebar-drawer-
|
|
988
|
-
|
|
945
|
+
.rf-sidebar-drawer .rf-sidebar-inner {
|
|
946
|
+
flex: 1;
|
|
947
|
+
min-block-size: 0;
|
|
989
948
|
}
|
|
990
|
-
.rf-sidebar-drawer-
|
|
949
|
+
.rf-sidebar-drawer .rf-drawer-body > .rf-basic-sr-only:focus-visible {
|
|
991
950
|
clip-path: none;
|
|
992
951
|
width: auto;
|
|
993
952
|
height: auto;
|
|
@@ -996,8 +955,6 @@
|
|
|
996
955
|
position: static;
|
|
997
956
|
}
|
|
998
957
|
@media (prefers-reduced-motion: reduce) {
|
|
999
|
-
.rf-sidebar-drawer,
|
|
1000
|
-
.rf-sidebar-drawer::backdrop,
|
|
1001
958
|
.rf-sidebar-layout,
|
|
1002
959
|
.rf-sidebar-card {
|
|
1003
960
|
transition-duration: 0s !important;
|
package/dist/sidebar.js
CHANGED
|
@@ -4,6 +4,7 @@ import { on, ref } from 'remix/component';
|
|
|
4
4
|
import { Button, Card, CardGroup, CardHeader, CardTitle, CardDescription, CardMedia, CardImage, } from './basic.js';
|
|
5
5
|
import { fluidHover } from './interactive.js';
|
|
6
6
|
import { ScrollArea } from './scroll-area.js';
|
|
7
|
+
import { Drawer } from './drawer.js';
|
|
7
8
|
/** How far past the minimum width a drag has to travel before it previews the collapse. */
|
|
8
9
|
const collapseSlop = 56;
|
|
9
10
|
function SidebarNavigation(handle) {
|
|
@@ -113,13 +114,14 @@ export function Sidebar(handle) {
|
|
|
113
114
|
let exitingPeek;
|
|
114
115
|
let peekTimer;
|
|
115
116
|
let drag;
|
|
116
|
-
let
|
|
117
|
+
let drawerOpen = false;
|
|
117
118
|
const panelId = `${handle.id}-sidebar`;
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const setDrawer = (open) => {
|
|
120
|
+
if (drawerOpen === open)
|
|
121
|
+
return;
|
|
122
|
+
drawerOpen = open;
|
|
123
|
+
void handle.update();
|
|
124
|
+
};
|
|
123
125
|
function limits() {
|
|
124
126
|
const minimum = handle.props.minWidth ?? 160;
|
|
125
127
|
return { minimum, maximum: Math.max(minimum, handle.props.maxWidth ?? 360) };
|
|
@@ -375,13 +377,7 @@ export function Sidebar(handle) {
|
|
|
375
377
|
if (!peeking)
|
|
376
378
|
cancelPeekTimer();
|
|
377
379
|
}),
|
|
378
|
-
], children: _jsx(Icon, { name: "panel-left" }) }), _jsx(Button, { class: "rf-sidebar-drawer-trigger", variant: "ghost", size: "compact", "
|
|
379
|
-
captureDrawer,
|
|
380
|
-
on('click', (event) => {
|
|
381
|
-
if (event.target === drawer)
|
|
382
|
-
drawer?.close();
|
|
383
|
-
}),
|
|
384
|
-
], children: _jsxs("div", { class: "rf-sidebar-drawer-content", children: [_jsx("button", { type: "button", class: "rf-basic-sr-only", mix: on('click', () => drawer?.close()), children: "Close navigation" }), _jsx(SidebarNavigation, { ...navigation, variant: "default", onNavigate: () => drawer?.close() })] }) })] }));
|
|
380
|
+
], children: _jsx(Icon, { name: "panel-left" }) }), _jsxs(Drawer, { side: "left", title: label, flush: true, class: "rf-sidebar-drawer", open: drawerOpen, onOpenChange: setDrawer, renderTrigger: (trigger) => (_jsx(Button, { class: "rf-sidebar-drawer-trigger", variant: "ghost", size: "compact", ...trigger, children: "Open navigation" })), children: [_jsx("button", { type: "button", class: "rf-basic-sr-only", mix: on('click', () => setDrawer(false)), children: "Close navigation" }), _jsx(SidebarNavigation, { ...navigation, variant: "default", onNavigate: () => setDrawer(false) })] })] })), toolbar != null && _jsx("div", { class: "rf-sidebar-toolbar-content", children: toolbar })] })), children] })] }));
|
|
385
381
|
};
|
|
386
382
|
}
|
|
387
383
|
/** A measured, dismissible pile of real Cards. Keyboard focus opens the inline pile too. */
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** The status glyphs Banner and toasts share. Internal: not exported from the package. */
|
|
2
|
+
export type StatusIconName = 'default' | 'info' | 'success' | 'warning' | 'error';
|
|
3
|
+
export declare function statusIcon(status: StatusIconName): import("remix/component").RemixElement;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "remix/component/jsx-runtime";
|
|
2
|
+
export function statusIcon(status) {
|
|
3
|
+
if (status === 'default') {
|
|
4
|
+
return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 16v-4m0-4h.01" })] }));
|
|
5
|
+
}
|
|
6
|
+
return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", children: [status === 'warning' ? (_jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z", fill: "currentColor", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round" })) : (_jsx("circle", { cx: "12", cy: "12", r: "10.5", fill: "currentColor" })), _jsx("g", { stroke: "var(--rf-bg)", "stroke-width": "2.25", "stroke-linecap": "round", "stroke-linejoin": "round", children: status === 'success' ? (_jsx("path", { d: "m8.5 12.25 2.5 2.5 4.5-5" })) : status === 'warning' ? (_jsx("path", { d: "M12 9.5v3.5M12 17h.01" })) : status === 'error' ? (_jsx("path", { d: "M12 7.5v5M12 16.5h.01" })) : (_jsx("path", { d: "M12 16.5v-5M12 7.5h.01" })) })] }));
|
|
7
|
+
}
|
package/dist/styles.css
CHANGED
package/dist/toaster.css
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/* Toaster: a pile of toasts fixed to one corner or edge of the viewport.
|
|
2
|
+
The newest toast sits in front; the ones behind peek out and narrow on
|
|
3
|
+
QueuedStack's stacking values. Pointed at or focused, the pile spreads
|
|
4
|
+
out a gap apart. The script writes each toast's depth, height and spread
|
|
5
|
+
offset; the lift turns offsets upward for the bottom positions. */
|
|
6
|
+
.rf-toaster-region {
|
|
7
|
+
display: block;
|
|
8
|
+
outline: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.rf-toaster {
|
|
12
|
+
--rf-toaster-offset: 24px;
|
|
13
|
+
--rf-toast-width: 356px;
|
|
14
|
+
--rf-toast-lift: -1;
|
|
15
|
+
position: fixed;
|
|
16
|
+
z-index: 2147483000;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
inline-size: var(--rf-toast-width);
|
|
19
|
+
max-inline-size: calc(100vw - 2 * var(--rf-toaster-offset));
|
|
20
|
+
block-size: var(--rf-toaster-piled-height, 0px);
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
list-style: none;
|
|
24
|
+
color: var(--rf-fg);
|
|
25
|
+
font: var(--rf-text-body) / var(--rf-leading-body) var(--rf-font);
|
|
26
|
+
-webkit-font-smoothing: antialiased;
|
|
27
|
+
}
|
|
28
|
+
.rf-toaster[data-expanded] {
|
|
29
|
+
block-size: var(--rf-toaster-spread-height, 0px);
|
|
30
|
+
}
|
|
31
|
+
.rf-toaster[data-position^='top'] {
|
|
32
|
+
--rf-toast-lift: 1;
|
|
33
|
+
top: var(--rf-toaster-offset);
|
|
34
|
+
}
|
|
35
|
+
.rf-toaster[data-position^='bottom'] {
|
|
36
|
+
bottom: var(--rf-toaster-offset);
|
|
37
|
+
}
|
|
38
|
+
.rf-toaster[data-position$='left'] {
|
|
39
|
+
left: var(--rf-toaster-offset);
|
|
40
|
+
}
|
|
41
|
+
.rf-toaster[data-position$='right'] {
|
|
42
|
+
right: var(--rf-toaster-offset);
|
|
43
|
+
}
|
|
44
|
+
.rf-toaster[data-position$='center'] {
|
|
45
|
+
left: 50%;
|
|
46
|
+
translate: -50% 0;
|
|
47
|
+
}
|
|
48
|
+
@media (max-width: 600px) {
|
|
49
|
+
.rf-toaster {
|
|
50
|
+
--rf-toaster-offset: 16px;
|
|
51
|
+
inset-inline: var(--rf-toaster-offset);
|
|
52
|
+
inline-size: auto;
|
|
53
|
+
translate: none;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.rf-toast {
|
|
58
|
+
--rf-status-color: var(--rf-fg);
|
|
59
|
+
--rf-toast-y: calc(var(--rf-toast-lift) * var(--rf-toast-depth, 0) * var(--rf-stack-peek));
|
|
60
|
+
position: absolute;
|
|
61
|
+
inset-inline: 0;
|
|
62
|
+
z-index: calc(100 - var(--rf-toast-depth, 0));
|
|
63
|
+
box-sizing: border-box;
|
|
64
|
+
block-size: var(--rf-toast-front-height, auto);
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
border-radius: var(--rf-panel-radius);
|
|
67
|
+
outline: none;
|
|
68
|
+
background: var(--rf-surface-3, white);
|
|
69
|
+
box-shadow:
|
|
70
|
+
inset 0 1px light-dark(transparent, rgb(255 255 255 / 2%)),
|
|
71
|
+
inset 0 0 0 1px light-dark(transparent, rgb(255 255 255 / 2%)),
|
|
72
|
+
0 0 0 1px light-dark(rgb(0 0 0 / 6%), rgb(0 0 0 / 12%)),
|
|
73
|
+
0 1px 1px -0.5px light-dark(rgb(0 0 0 / 6%), rgb(0 0 0 / 18%)),
|
|
74
|
+
0 3px 3px -1.5px light-dark(rgb(0 0 0 / 6%), rgb(0 0 0 / 18%)),
|
|
75
|
+
0 12px 28px light-dark(rgb(0 0 0 / 10%), rgb(0 0 0 / 24%));
|
|
76
|
+
translate: var(--rf-swipe-x, 0px) calc(var(--rf-toast-y) + var(--rf-swipe-y, 0px));
|
|
77
|
+
scale: calc(1 - var(--rf-toast-depth, 0) * var(--rf-stack-scale-step)) 1;
|
|
78
|
+
touch-action: none;
|
|
79
|
+
transition:
|
|
80
|
+
translate var(--rf-spring-moderate),
|
|
81
|
+
scale var(--rf-spring-moderate),
|
|
82
|
+
block-size var(--rf-spring-moderate),
|
|
83
|
+
opacity var(--rf-spring-moderate);
|
|
84
|
+
}
|
|
85
|
+
.rf-toaster[data-position^='top'] > .rf-toast {
|
|
86
|
+
top: 0;
|
|
87
|
+
}
|
|
88
|
+
.rf-toaster[data-position^='bottom'] > .rf-toast {
|
|
89
|
+
bottom: 0;
|
|
90
|
+
}
|
|
91
|
+
.rf-toast[data-front] {
|
|
92
|
+
block-size: var(--rf-toast-height, auto);
|
|
93
|
+
}
|
|
94
|
+
.rf-toast:focus-visible {
|
|
95
|
+
outline: 2px solid var(--rf-accent);
|
|
96
|
+
outline-offset: 2px;
|
|
97
|
+
}
|
|
98
|
+
/* A new toast slides in from the edge. */
|
|
99
|
+
@starting-style {
|
|
100
|
+
.rf-toast {
|
|
101
|
+
--rf-toast-y: calc(var(--rf-toast-lift) * -100%);
|
|
102
|
+
opacity: 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.rf-toaster[data-expanded] > .rf-toast {
|
|
107
|
+
--rf-toast-y: calc(var(--rf-toast-lift) * var(--rf-toast-offset, 0px));
|
|
108
|
+
block-size: var(--rf-toast-height, auto);
|
|
109
|
+
scale: 1;
|
|
110
|
+
}
|
|
111
|
+
.rf-toast[data-hidden] {
|
|
112
|
+
visibility: hidden;
|
|
113
|
+
opacity: 0;
|
|
114
|
+
pointer-events: none;
|
|
115
|
+
}
|
|
116
|
+
.rf-toast[data-swiping] {
|
|
117
|
+
cursor: grabbing;
|
|
118
|
+
user-select: none;
|
|
119
|
+
transition: none;
|
|
120
|
+
}
|
|
121
|
+
/* Leaving, the front toast drops back to its edge; a swiped one carries on
|
|
122
|
+
the way it was thrown; the others fade where they are. */
|
|
123
|
+
.rf-toast[data-removed] {
|
|
124
|
+
opacity: 0;
|
|
125
|
+
pointer-events: none;
|
|
126
|
+
transition:
|
|
127
|
+
translate var(--rf-exit-slow),
|
|
128
|
+
scale var(--rf-exit-slow),
|
|
129
|
+
opacity var(--rf-exit-moderate);
|
|
130
|
+
}
|
|
131
|
+
.rf-toast[data-removed][data-front]:not([data-swipe-out]) {
|
|
132
|
+
--rf-toast-y: calc(var(--rf-toast-lift) * -100%);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.rf-toast-inner {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: flex-start;
|
|
138
|
+
gap: 12px;
|
|
139
|
+
padding: 16px;
|
|
140
|
+
transition: opacity var(--rf-spring-moderate);
|
|
141
|
+
}
|
|
142
|
+
/* Piled, only the front toast shows its content. */
|
|
143
|
+
.rf-toaster:not([data-expanded]) > .rf-toast:not([data-front]) > .rf-toast-inner {
|
|
144
|
+
opacity: 0;
|
|
145
|
+
}
|
|
146
|
+
.rf-toast[data-status='info'] {
|
|
147
|
+
--rf-status-color: light-dark(#3b82f6, var(--rf-info));
|
|
148
|
+
}
|
|
149
|
+
.rf-toast[data-status='success'] {
|
|
150
|
+
--rf-status-color: light-dark(#22c55e, var(--rf-success));
|
|
151
|
+
}
|
|
152
|
+
.rf-toast[data-status='warning'] {
|
|
153
|
+
--rf-status-color: light-dark(#f59e0b, var(--rf-warning));
|
|
154
|
+
}
|
|
155
|
+
.rf-toast[data-status='error'] {
|
|
156
|
+
--rf-status-color: light-dark(#ef4444, var(--rf-danger));
|
|
157
|
+
}
|
|
158
|
+
.rf-toast[data-status='loading'] {
|
|
159
|
+
--rf-status-color: var(--rf-muted);
|
|
160
|
+
}
|
|
161
|
+
.rf-toast-icon {
|
|
162
|
+
display: grid;
|
|
163
|
+
flex: none;
|
|
164
|
+
place-items: center;
|
|
165
|
+
inline-size: 20px;
|
|
166
|
+
block-size: var(--rf-leading-subtitle);
|
|
167
|
+
color: var(--rf-status-color);
|
|
168
|
+
}
|
|
169
|
+
.rf-toast-icon > svg {
|
|
170
|
+
inline-size: 20px;
|
|
171
|
+
block-size: 20px;
|
|
172
|
+
}
|
|
173
|
+
.rf-toast-spinner g {
|
|
174
|
+
transform-box: fill-box;
|
|
175
|
+
transform-origin: center;
|
|
176
|
+
animation: rf-button-spinner-turn 1s cubic-bezier(0.65, 0, 0.35, 1) infinite;
|
|
177
|
+
}
|
|
178
|
+
.rf-toast-spinner circle {
|
|
179
|
+
stroke-dasharray: 0.01 49.99;
|
|
180
|
+
stroke-dashoffset: -25;
|
|
181
|
+
animation: rf-button-spinner-breathe 1s cubic-bezier(0.65, 0, 0.35, 1) infinite;
|
|
182
|
+
}
|
|
183
|
+
.rf-toast-content {
|
|
184
|
+
display: flex;
|
|
185
|
+
flex: 1;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
gap: 2px;
|
|
188
|
+
min-inline-size: 0;
|
|
189
|
+
}
|
|
190
|
+
.rf-toast-title {
|
|
191
|
+
font-size: var(--rf-text-subtitle);
|
|
192
|
+
line-height: var(--rf-leading-subtitle);
|
|
193
|
+
font-variation-settings: var(--rf-weight-strong);
|
|
194
|
+
overflow-wrap: anywhere;
|
|
195
|
+
}
|
|
196
|
+
.rf-toast-description {
|
|
197
|
+
color: var(--rf-muted);
|
|
198
|
+
overflow-wrap: anywhere;
|
|
199
|
+
}
|
|
200
|
+
.rf-toast-action {
|
|
201
|
+
flex: none;
|
|
202
|
+
margin-block: calc((var(--rf-leading-subtitle) - 28px) / 2);
|
|
203
|
+
}
|
|
204
|
+
.rf-toast-close {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex: none;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
box-sizing: border-box;
|
|
210
|
+
inline-size: 28px;
|
|
211
|
+
block-size: 28px;
|
|
212
|
+
margin-block: calc((var(--rf-leading-subtitle) - 28px) / 2);
|
|
213
|
+
margin-inline: -4px -6px;
|
|
214
|
+
padding: 0;
|
|
215
|
+
border: 0;
|
|
216
|
+
border-radius: var(--rf-radius);
|
|
217
|
+
outline: none;
|
|
218
|
+
background: transparent;
|
|
219
|
+
color: var(--rf-muted);
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition:
|
|
222
|
+
color var(--rf-fast),
|
|
223
|
+
background-color var(--rf-fast);
|
|
224
|
+
}
|
|
225
|
+
.rf-toast-close:focus-visible {
|
|
226
|
+
box-shadow: 0 0 0 1px var(--rf-accent);
|
|
227
|
+
}
|
|
228
|
+
@media (hover: hover) {
|
|
229
|
+
.rf-toast-close:hover {
|
|
230
|
+
background: var(--rf-hover);
|
|
231
|
+
color: var(--rf-fg);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
.rf-toast-close > svg {
|
|
235
|
+
inline-size: 15px;
|
|
236
|
+
block-size: 15px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
[data-density='compact'] .rf-toast-inner {
|
|
240
|
+
gap: 10px;
|
|
241
|
+
padding: 12px;
|
|
242
|
+
}
|
|
243
|
+
[data-density='compact'] .rf-toast-icon,
|
|
244
|
+
[data-density='compact'] .rf-toast-icon > svg {
|
|
245
|
+
inline-size: 16px;
|
|
246
|
+
}
|
|
247
|
+
[data-density='compact'] .rf-toast-icon > svg {
|
|
248
|
+
block-size: 16px;
|
|
249
|
+
}
|
|
250
|
+
[data-density='compact'] .rf-toast-close > svg {
|
|
251
|
+
inline-size: 13px;
|
|
252
|
+
block-size: 13px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@media (prefers-reduced-motion: reduce) {
|
|
256
|
+
.rf-toast,
|
|
257
|
+
.rf-toast[data-removed] {
|
|
258
|
+
transition: opacity var(--rf-spring-moderate);
|
|
259
|
+
}
|
|
260
|
+
.rf-toast-spinner :is(g, circle) {
|
|
261
|
+
animation: none;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
@media (forced-colors: active) {
|
|
265
|
+
.rf-toast {
|
|
266
|
+
border: 1px solid CanvasText;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type Handle, type RemixNode } from 'remix/component';
|
|
2
|
+
export type ToastStatus = 'default' | 'info' | 'success' | 'warning' | 'error' | 'loading';
|
|
3
|
+
export type ToasterPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
4
|
+
export interface ToastAction {
|
|
5
|
+
label: string;
|
|
6
|
+
/** Runs on click. The toast then closes, unless the handler calls `event.preventDefault()`. */
|
|
7
|
+
onClick: (event: MouseEvent) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface ToastOptions {
|
|
10
|
+
/** Reuse an id to update a toast in place instead of adding another. */
|
|
11
|
+
id?: string;
|
|
12
|
+
description?: RemixNode;
|
|
13
|
+
/** Milliseconds before it closes on its own. `Infinity` keeps it until dismissed. Defaults to the Toaster's duration. */
|
|
14
|
+
duration?: number;
|
|
15
|
+
action?: ToastAction;
|
|
16
|
+
/** Whether a swipe, Escape or the close button can close it. Default true. */
|
|
17
|
+
dismissible?: boolean;
|
|
18
|
+
/** Replaces the status icon. */
|
|
19
|
+
icon?: RemixNode;
|
|
20
|
+
/** Called when the toast is closed by a swipe, Escape, the close button, an action or `toast.dismiss`. */
|
|
21
|
+
onDismiss?: (toast: Toast) => void;
|
|
22
|
+
/** Called when the toast closes on its own after its duration. */
|
|
23
|
+
onAutoClose?: (toast: Toast) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface Toast extends ToastOptions {
|
|
26
|
+
id: string;
|
|
27
|
+
title: RemixNode;
|
|
28
|
+
status: ToastStatus;
|
|
29
|
+
}
|
|
30
|
+
export interface ToastPromiseMessages<T> {
|
|
31
|
+
loading: RemixNode;
|
|
32
|
+
success: RemixNode | ((value: T) => RemixNode);
|
|
33
|
+
error: RemixNode | ((error: unknown) => RemixNode);
|
|
34
|
+
description?: RemixNode;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Raises a toast in the app's `<Toaster>` and returns its id. `toast.success`,
|
|
38
|
+
* `toast.info`, `toast.warning`, `toast.error` and `toast.loading` set its
|
|
39
|
+
* status; `toast.promise` follows a promise from loading to its outcome;
|
|
40
|
+
* `toast.dismiss` closes one toast, or all of them.
|
|
41
|
+
*/
|
|
42
|
+
export declare const toast: ((title: RemixNode, options?: ToastOptions) => string) & {
|
|
43
|
+
success: (title: RemixNode, options?: ToastOptions) => string;
|
|
44
|
+
info: (title: RemixNode, options?: ToastOptions) => string;
|
|
45
|
+
warning: (title: RemixNode, options?: ToastOptions) => string;
|
|
46
|
+
error: (title: RemixNode, options?: ToastOptions) => string;
|
|
47
|
+
loading: (title: RemixNode, options?: ToastOptions) => string;
|
|
48
|
+
promise<T>(promise: Promise<T> | (() => Promise<T>), messages: ToastPromiseMessages<T>, options?: ToastOptions): string;
|
|
49
|
+
dismiss: (id?: string) => void;
|
|
50
|
+
};
|
|
51
|
+
export interface ToasterProps {
|
|
52
|
+
/** Where the stack sits. Default bottom-right. */
|
|
53
|
+
position?: ToasterPosition;
|
|
54
|
+
/** Show the stack spread out instead of piled. Default false. */
|
|
55
|
+
expand?: boolean;
|
|
56
|
+
/** How many toasts stay in view; older ones wait behind them. Default 3. */
|
|
57
|
+
visibleToasts?: number;
|
|
58
|
+
/** Milliseconds each toast stays before closing on its own. Default 4000. */
|
|
59
|
+
duration?: number;
|
|
60
|
+
/** Give every dismissible toast a close button. Default false. */
|
|
61
|
+
closeButton?: boolean;
|
|
62
|
+
/** Moves focus to the newest toast. Default alt+t. */
|
|
63
|
+
hotkey?: string;
|
|
64
|
+
/** Names the notifications region. Default "Notifications". */
|
|
65
|
+
label?: string;
|
|
66
|
+
/** Distance from the viewport's edges in pixels. Default 24, or 16 on narrow screens. */
|
|
67
|
+
offset?: number;
|
|
68
|
+
size?: 'default' | 'compact';
|
|
69
|
+
class?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Shows the toasts raised with `toast()`. Place one in the app, outside
|
|
73
|
+
* anything that scrolls. The newest toast sits in front with up to two
|
|
74
|
+
* peeking behind it, on QueuedStack's stacking values; pointing at or
|
|
75
|
+
* focusing the stack spreads it out and pauses the timers. A swipe toward
|
|
76
|
+
* the nearest edge, Escape, or the close button dismisses a toast, and the
|
|
77
|
+
* hotkey moves focus to the newest one.
|
|
78
|
+
*/
|
|
79
|
+
export declare function Toaster(handle: Handle<ToasterProps>): () => import("remix/component").RemixElement;
|