@rtzll/reflow 0.3.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 +3 -8
- package/dist/breadcrumb.css +10 -0
- package/dist/breadcrumb.js +38 -40
- 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 +27 -14
- package/dist/hover-card.d.ts +32 -0
- package/dist/hover-card.js +119 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/overlays.js +14 -2
- package/dist/popover.css +69 -0
- package/dist/popover.d.ts +38 -0
- package/dist/popover.js +57 -0
- package/dist/segmented-control.css +172 -0
- package/dist/segmented-control.d.ts +34 -0
- package/dist/segmented-control.js +38 -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 +4 -0
- package/dist/systems.js +5 -1
- 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/popover.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "remix/component/jsx-runtime";
|
|
2
|
+
import { on } from 'remix/component';
|
|
3
|
+
import * as popover from '@remix-run/ui/popover';
|
|
4
|
+
import { Button } from './basic.js';
|
|
5
|
+
// A field to type into, or an element the content marks to take focus. Buttons
|
|
6
|
+
// and links are left to Tab, so opening never fires a control's own tooltip.
|
|
7
|
+
const initialFocus = '[autofocus], input:not(:disabled, [readonly], [type="hidden"]), select:not(:disabled), textarea:not(:disabled, [readonly])';
|
|
8
|
+
/**
|
|
9
|
+
* A panel anchored to a trigger, for content that needs more room than a
|
|
10
|
+
* tooltip: a filter, inline settings, a share sheet. A click opens it; Escape
|
|
11
|
+
* and a click outside close it. It is a non-modal dialog: focus moves to its
|
|
12
|
+
* first field, or to the panel, on open and returns to the trigger on close.
|
|
13
|
+
*/
|
|
14
|
+
export function Popover(handle) {
|
|
15
|
+
let localOpen = handle.props.defaultOpen ?? false;
|
|
16
|
+
const panelId = `${handle.id}-panel`;
|
|
17
|
+
const titleId = `${handle.id}-title`;
|
|
18
|
+
const descriptionId = `${handle.id}-description`;
|
|
19
|
+
const isOpen = () => handle.props.open ?? localOpen;
|
|
20
|
+
function setOpen(next) {
|
|
21
|
+
if (isOpen() === next)
|
|
22
|
+
return;
|
|
23
|
+
localOpen = next;
|
|
24
|
+
handle.props.onOpenChange?.(next);
|
|
25
|
+
void handle.update();
|
|
26
|
+
}
|
|
27
|
+
// On open, focus the panel's first field, or the panel itself.
|
|
28
|
+
const focusInside = on('toggle', (event) => {
|
|
29
|
+
if (event.newState !== 'open')
|
|
30
|
+
return;
|
|
31
|
+
const panel = event.currentTarget;
|
|
32
|
+
const target = panel.querySelector(initialFocus) ?? panel;
|
|
33
|
+
target.focus({ preventScroll: true });
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
const { label, title, description, children, trigger, triggerVariant = 'secondary', triggerSize, renderTrigger, placement = 'bottom', offset = 8, size, } = handle.props;
|
|
37
|
+
const open = isOpen();
|
|
38
|
+
const triggerProps = {
|
|
39
|
+
'aria-haspopup': 'dialog',
|
|
40
|
+
'aria-expanded': open,
|
|
41
|
+
'aria-controls': panelId,
|
|
42
|
+
mix: [
|
|
43
|
+
popover.anchor({ placement, offset }),
|
|
44
|
+
popover.focusOnHide(),
|
|
45
|
+
on('click', () => setOpen(!isOpen())),
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
return (_jsx("span", { class: "rf-popover", "data-density": size, children: _jsxs(popover.Context, { children: [renderTrigger ? (renderTrigger(triggerProps)) : (_jsx(Button, { variant: triggerVariant, size: triggerSize ?? size, ...triggerProps, children: trigger })), _jsxs("div", { class: "rf-menu-surface rf-popover-surface", id: panelId, role: "dialog", tabIndex: -1, "aria-label": title == null ? label : undefined, "aria-labelledby": title == null ? undefined : titleId, "aria-describedby": description == null ? undefined : descriptionId, "data-density": size, mix: [
|
|
49
|
+
popover.surface({
|
|
50
|
+
open,
|
|
51
|
+
closeOnAnchorClick: false,
|
|
52
|
+
onHide: () => setOpen(false),
|
|
53
|
+
}),
|
|
54
|
+
focusInside,
|
|
55
|
+
], children: [(title != null || description != null) && (_jsxs("header", { class: "rf-popover-header", children: [title != null && (_jsx("h2", { class: "rf-popover-title", id: titleId, children: title })), description != null && (_jsx("p", { class: "rf-popover-description", id: descriptionId, children: description }))] })), children != null && _jsx("div", { class: "rf-popover-body", children: children })] })] }) }));
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* SegmentedControl: a filled track around a row of segments, with a raised
|
|
2
|
+
thumb that glides to the selected one. The track's padding keeps the whole
|
|
3
|
+
control on the size ladder: 4px around 28px segments makes 36px, 2px around
|
|
4
|
+
24px makes 28px compact. Corners stay concentric: the track's radius is the
|
|
5
|
+
thumb's plus the padding. */
|
|
6
|
+
.rf-segmented {
|
|
7
|
+
--rf-segment-pad: 4px;
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
min-inline-size: 0;
|
|
10
|
+
max-inline-size: 100%;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
border: 0;
|
|
14
|
+
color: var(--rf-fg);
|
|
15
|
+
}
|
|
16
|
+
.rf-segmented[data-density='compact'],
|
|
17
|
+
[data-density='compact'] .rf-segmented:not([data-density='default']) {
|
|
18
|
+
--rf-segment-pad: 2px;
|
|
19
|
+
}
|
|
20
|
+
.rf-segmented[data-fill] {
|
|
21
|
+
display: flex;
|
|
22
|
+
inline-size: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.rf-segmented-track {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
flex: 1;
|
|
28
|
+
align-items: stretch;
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
min-inline-size: 0;
|
|
31
|
+
block-size: var(--rf-control-height, 36px);
|
|
32
|
+
padding: var(--rf-segment-pad);
|
|
33
|
+
border-radius: calc(var(--rf-radius, 8px) + var(--rf-segment-pad));
|
|
34
|
+
background: var(--rf-hover);
|
|
35
|
+
}
|
|
36
|
+
.rf-segmented[data-fill] .rf-segmented-track {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-auto-columns: minmax(0, 1fr);
|
|
39
|
+
grid-auto-flow: column;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.rf-segment {
|
|
43
|
+
position: relative;
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
gap: 6px;
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
min-inline-size: 0;
|
|
50
|
+
padding: 0 var(--rf-control-padding, 12px);
|
|
51
|
+
border-radius: var(--rf-radius, 8px);
|
|
52
|
+
color: var(--rf-muted);
|
|
53
|
+
font-size: var(--rf-text-body, 13px);
|
|
54
|
+
line-height: var(--rf-leading-body, 20px);
|
|
55
|
+
white-space: nowrap;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
user-select: none;
|
|
58
|
+
-webkit-tap-highlight-color: transparent;
|
|
59
|
+
transition: color 80ms ease;
|
|
60
|
+
}
|
|
61
|
+
.rf-segment[data-icon-only] {
|
|
62
|
+
inline-size: calc(var(--rf-control-height, 36px) - var(--rf-segment-pad) * 2);
|
|
63
|
+
padding: 0;
|
|
64
|
+
}
|
|
65
|
+
.rf-segmented[data-fill] .rf-segment[data-icon-only] {
|
|
66
|
+
inline-size: auto;
|
|
67
|
+
}
|
|
68
|
+
.rf-segment:is([data-rf-hovered], :has(:checked), :has(:focus-visible)) {
|
|
69
|
+
color: var(--rf-fg);
|
|
70
|
+
}
|
|
71
|
+
.rf-segment[aria-disabled='true'] {
|
|
72
|
+
opacity: 0.4;
|
|
73
|
+
cursor: default;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* The radio stays focusable and in the form, out of sight. */
|
|
77
|
+
.rf-segment-input {
|
|
78
|
+
position: absolute;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
inline-size: 1px;
|
|
81
|
+
block-size: 1px;
|
|
82
|
+
margin: 0;
|
|
83
|
+
padding: 0;
|
|
84
|
+
border: 0;
|
|
85
|
+
clip-path: inset(50%);
|
|
86
|
+
white-space: nowrap;
|
|
87
|
+
appearance: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.rf-segment-icon {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
flex: none;
|
|
93
|
+
}
|
|
94
|
+
.rf-segment-icon > svg {
|
|
95
|
+
inline-size: var(--rf-icon-size, 16px);
|
|
96
|
+
block-size: var(--rf-icon-size, 16px);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* An invisible copy at the strong weight reserves the width, so the selected
|
|
100
|
+
label's weight shift never moves its neighbours. */
|
|
101
|
+
.rf-segment-text {
|
|
102
|
+
display: inline-grid;
|
|
103
|
+
min-inline-size: 0;
|
|
104
|
+
}
|
|
105
|
+
.rf-segment-sizer,
|
|
106
|
+
.rf-segment-label {
|
|
107
|
+
grid-area: 1 / 1;
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
text-overflow: ellipsis;
|
|
110
|
+
}
|
|
111
|
+
.rf-segment-sizer {
|
|
112
|
+
visibility: hidden;
|
|
113
|
+
font-variation-settings: var(--rf-weight-strong);
|
|
114
|
+
}
|
|
115
|
+
.rf-segment-label {
|
|
116
|
+
font-variation-settings: var(--rf-weight-normal);
|
|
117
|
+
transition: font-variation-settings 80ms ease;
|
|
118
|
+
}
|
|
119
|
+
.rf-segment:has(:checked) .rf-segment-label {
|
|
120
|
+
font-variation-settings: var(--rf-weight-strong);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* The thumb rides the shared selection layer; pressing any segment squeezes it. */
|
|
124
|
+
.rf-segmented-thumb {
|
|
125
|
+
background: var(--rf-surface-5);
|
|
126
|
+
box-shadow:
|
|
127
|
+
0 1px 2px rgb(0 0 0 / 12%),
|
|
128
|
+
0 0 0 1px light-dark(rgb(0 0 0 / 4%), rgb(255 255 255 / 4%));
|
|
129
|
+
transition:
|
|
130
|
+
opacity 80ms ease,
|
|
131
|
+
scale var(--rf-spring-fast);
|
|
132
|
+
}
|
|
133
|
+
.rf-segmented-thumb[data-moving] {
|
|
134
|
+
transition:
|
|
135
|
+
var(--rf-highlight-travel),
|
|
136
|
+
opacity 80ms ease,
|
|
137
|
+
scale var(--rf-spring-fast);
|
|
138
|
+
}
|
|
139
|
+
.rf-segmented-track:has(.rf-segment:not([aria-disabled='true']):active) .rf-segmented-thumb {
|
|
140
|
+
scale: 0.97;
|
|
141
|
+
}
|
|
142
|
+
.rf-segmented-hover {
|
|
143
|
+
background: var(--rf-hover);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Before the layers mount, the checked segment carries the thumb itself. */
|
|
147
|
+
.rf-segmented-track:not([data-rf-has-selection]) .rf-segment:has(:checked) {
|
|
148
|
+
background: var(--rf-surface-5);
|
|
149
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 12%);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.rf-segment:has(:focus-visible) {
|
|
153
|
+
outline: 1px solid var(--rf-accent, #6b97ff);
|
|
154
|
+
outline-offset: 1px;
|
|
155
|
+
}
|
|
156
|
+
@media (forced-colors: none) {
|
|
157
|
+
.rf-segmented-track:has(> .rf-focus-ring[data-visible]) .rf-segment:has(:focus-visible) {
|
|
158
|
+
outline-color: transparent;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
@media (forced-colors: active) {
|
|
162
|
+
.rf-segment:has(:checked) {
|
|
163
|
+
outline: 1px solid Highlight;
|
|
164
|
+
outline-offset: -1px;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
@media (prefers-reduced-motion: reduce) {
|
|
168
|
+
.rf-segmented-thumb,
|
|
169
|
+
.rf-segmented-thumb[data-moving] {
|
|
170
|
+
transition: opacity 80ms ease;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Handle, type RemixNode } from 'remix/component';
|
|
2
|
+
export interface SegmentedControlOption {
|
|
3
|
+
value: string;
|
|
4
|
+
/** Names the segment; for an icon-only segment it is read out and shown in a Tooltip. */
|
|
5
|
+
label: string;
|
|
6
|
+
icon?: RemixNode;
|
|
7
|
+
/** Shows only the icon. The label stays for screen readers and appears in a Tooltip on hover. */
|
|
8
|
+
iconOnly?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SegmentedControlProps {
|
|
12
|
+
/** Names the group for assistive technology. */
|
|
13
|
+
label: string;
|
|
14
|
+
options: readonly SegmentedControlOption[];
|
|
15
|
+
/** The selected option in controlled mode. */
|
|
16
|
+
value?: string;
|
|
17
|
+
/** The option selected first in uncontrolled mode. Defaults to the first enabled option. */
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
onValueChange?: (value: string) => void;
|
|
20
|
+
/** Submits the selected value with a surrounding form. */
|
|
21
|
+
name?: string;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
size?: 'default' | 'compact';
|
|
24
|
+
/** Stretches the control to its container, with equal-width segments. */
|
|
25
|
+
fill?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One choice from a short row of options, such as a view or a time range. A
|
|
29
|
+
* segmented control is never empty: without a value, the first enabled option
|
|
30
|
+
* is selected. Each segment is a native radio, so arrow keys move the
|
|
31
|
+
* selection and the value takes part in forms. A raised thumb glides to the
|
|
32
|
+
* selected segment and squeezes while a segment is pressed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function SegmentedControl(handle: Handle<SegmentedControlProps>): () => import("remix/component").RemixElement;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "remix/component/jsx-runtime";
|
|
2
|
+
import { on } from 'remix/component';
|
|
3
|
+
import { fluidHover } from './interactive.js';
|
|
4
|
+
import { Tooltip } from './overlays.js';
|
|
5
|
+
/**
|
|
6
|
+
* One choice from a short row of options, such as a view or a time range. A
|
|
7
|
+
* segmented control is never empty: without a value, the first enabled option
|
|
8
|
+
* is selected. Each segment is a native radio, so arrow keys move the
|
|
9
|
+
* selection and the value takes part in forms. A raised thumb glides to the
|
|
10
|
+
* selected segment and squeezes while a segment is pressed.
|
|
11
|
+
*/
|
|
12
|
+
export function SegmentedControl(handle) {
|
|
13
|
+
const indicators = fluidHover({
|
|
14
|
+
axis: 'x',
|
|
15
|
+
selectedSelector: ':has(input:checked)',
|
|
16
|
+
focusSelector: ':has(input:focus-visible)',
|
|
17
|
+
});
|
|
18
|
+
return () => {
|
|
19
|
+
const { label, options, value, defaultValue, onValueChange, name = handle.id, disabled, size, fill, } = handle.props;
|
|
20
|
+
const selectable = (candidate) => options.some((option) => option.value === candidate && !option.disabled);
|
|
21
|
+
const fallback = options.find((option) => !option.disabled)?.value;
|
|
22
|
+
const selected = value !== undefined
|
|
23
|
+
? selectable(value)
|
|
24
|
+
? value
|
|
25
|
+
: fallback
|
|
26
|
+
: selectable(defaultValue)
|
|
27
|
+
? defaultValue
|
|
28
|
+
: fallback;
|
|
29
|
+
const segment = (option, mix) => (_jsxs("label", { class: "rf-segment", "data-rf-hover-item": true, "data-icon-only": option.iconOnly && option.icon != null ? '' : undefined, "aria-disabled": disabled || option.disabled || undefined, mix: mix, children: [_jsx("input", { type: "radio", class: "rf-segment-input", name: name, value: option.value, disabled: option.disabled, ...(value === undefined
|
|
30
|
+
? { defaultChecked: selected === option.value }
|
|
31
|
+
: { checked: selected === option.value }), mix: onValueChange &&
|
|
32
|
+
on('change', (event) => {
|
|
33
|
+
if (event.currentTarget.checked)
|
|
34
|
+
onValueChange(option.value);
|
|
35
|
+
}) }), option.icon != null && (_jsx("span", { class: "rf-segment-icon", "aria-hidden": "true", children: option.icon })), option.iconOnly && option.icon != null ? (_jsx("span", { class: "rf-basic-sr-only", children: option.label })) : (_jsxs("span", { class: "rf-segment-text", children: [_jsx("span", { class: "rf-segment-sizer", "aria-hidden": "true", children: option.label }), _jsx("span", { class: "rf-segment-label", children: option.label })] }))] }, option.value));
|
|
36
|
+
return (_jsxs("fieldset", { class: "rf-segmented", "data-density": size, "data-fill": fill ? '' : undefined, disabled: disabled, children: [_jsx("legend", { class: "rf-basic-sr-only", children: label }), _jsxs("div", { class: "rf-segmented-track", mix: indicators, children: [_jsx("span", { class: "rf-highlight rf-segmented-thumb", "aria-hidden": "true", "data-rf-selection-layer": true }), _jsx("span", { class: "rf-highlight rf-segmented-hover", "aria-hidden": "true", "data-rf-hover-layer": true }), _jsx("span", { class: "rf-highlight rf-focus-ring", "aria-hidden": "true", "data-rf-focus-layer": true }), options.map((option) => option.iconOnly && option.icon != null ? (_jsx(Tooltip, { content: option.label, renderTrigger: (trigger) => segment(option, trigger.mix) }, option.value)) : (segment(option)))] })] }));
|
|
37
|
+
};
|
|
38
|
+
}
|
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/systems.js
CHANGED
|
@@ -55,7 +55,11 @@ export function reveal(options = {}) {
|
|
|
55
55
|
return;
|
|
56
56
|
observer.disconnect();
|
|
57
57
|
animation = element.animate([
|
|
58
|
-
{
|
|
58
|
+
{
|
|
59
|
+
opacity: preset ? 0 : 0.3,
|
|
60
|
+
translate: `0 ${preset?.distance ?? 6}px`,
|
|
61
|
+
scale: preset?.scale ?? 1,
|
|
62
|
+
},
|
|
59
63
|
{ opacity: 1, translate: '0 0', scale: 1 },
|
|
60
64
|
], {
|
|
61
65
|
...(preset
|