@rtzll/reflow 0.5.0 → 0.7.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/avatar.css +119 -0
- package/dist/avatar.d.ts +42 -0
- package/dist/avatar.js +74 -0
- package/dist/basic.d.ts +1 -20
- package/dist/basic.js +1 -20
- package/dist/carousel.css +67 -0
- package/dist/carousel.d.ts +36 -0
- package/dist/carousel.js +277 -0
- package/dist/chat-layout.css +124 -0
- package/dist/chat-layout.d.ts +24 -0
- package/dist/chat-layout.js +108 -0
- package/dist/citation.css +121 -0
- package/dist/citation.d.ts +35 -0
- package/dist/citation.js +45 -0
- package/dist/code-block.css +292 -0
- package/dist/code-block.d.ts +59 -0
- package/dist/code-block.js +117 -0
- package/dist/colors.d.ts +21 -0
- package/dist/colors.js +21 -0
- package/dist/compositions.css +51 -0
- package/dist/compositions.d.ts +8 -1
- package/dist/compositions.js +8 -0
- package/dist/drawer.css +33 -37
- package/dist/highlight.d.ts +36 -0
- package/dist/highlight.js +134 -0
- package/dist/hover-card.d.ts +6 -1
- package/dist/hover-card.js +19 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/queued-stack.js +50 -1
- package/dist/styles.css +5 -0
- package/dist/toaster.js +3 -2
- package/package.json +19 -3
package/dist/avatar.css
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Avatar: initials on a soft tint of their colour, covered by an image
|
|
2
|
+
once it loads. Sizes follow --rf-avatar-size; the initials scale with it.
|
|
3
|
+
A ring in the surface colour separates overlapping avatars in a group. */
|
|
4
|
+
.rf-avatar {
|
|
5
|
+
--rf-avatar-size: 32px;
|
|
6
|
+
--rf-avatar-ring: var(--rf-bg);
|
|
7
|
+
position: relative;
|
|
8
|
+
display: inline-grid;
|
|
9
|
+
flex: none;
|
|
10
|
+
place-items: center;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
inline-size: var(--rf-avatar-size);
|
|
13
|
+
block-size: var(--rf-avatar-size);
|
|
14
|
+
border-radius: 9999px;
|
|
15
|
+
background: color-mix(in oklab, var(--rf-avatar-color, var(--rf-fg)) 20%, var(--rf-bg));
|
|
16
|
+
color: color-mix(in oklab, var(--rf-avatar-color, var(--rf-fg)) 60%, var(--rf-fg));
|
|
17
|
+
font: 550 calc(var(--rf-avatar-size) * 0.4) / 1 var(--rf-font);
|
|
18
|
+
letter-spacing: 0.01em;
|
|
19
|
+
user-select: none;
|
|
20
|
+
vertical-align: middle;
|
|
21
|
+
}
|
|
22
|
+
.rf-avatar[data-shape='square'] {
|
|
23
|
+
border-radius: max(4px, calc(var(--rf-avatar-size) * 0.25));
|
|
24
|
+
}
|
|
25
|
+
[data-shape='sharp'] .rf-avatar[data-shape='square'] {
|
|
26
|
+
border-radius: var(--rf-radius);
|
|
27
|
+
}
|
|
28
|
+
.rf-avatar > * {
|
|
29
|
+
grid-area: 1 / 1;
|
|
30
|
+
}
|
|
31
|
+
.rf-avatar-image {
|
|
32
|
+
inline-size: 100%;
|
|
33
|
+
block-size: 100%;
|
|
34
|
+
border-radius: inherit;
|
|
35
|
+
object-fit: cover;
|
|
36
|
+
}
|
|
37
|
+
.rf-avatar[data-image='error'] .rf-avatar-image {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* The status dot sits on the corner, ringed in the surface colour. */
|
|
42
|
+
.rf-avatar-status {
|
|
43
|
+
place-self: end;
|
|
44
|
+
inline-size: max(6px, calc(var(--rf-avatar-size) * 0.28));
|
|
45
|
+
block-size: max(6px, calc(var(--rf-avatar-size) * 0.28));
|
|
46
|
+
margin: -1px;
|
|
47
|
+
border-radius: 9999px;
|
|
48
|
+
box-shadow: 0 0 0 2px var(--rf-avatar-ring);
|
|
49
|
+
background: var(--rf-muted);
|
|
50
|
+
}
|
|
51
|
+
.rf-avatar-status[data-status='online'] {
|
|
52
|
+
background: light-dark(#22c55e, var(--rf-success));
|
|
53
|
+
}
|
|
54
|
+
.rf-avatar-status[data-status='away'] {
|
|
55
|
+
background: light-dark(#f59e0b, var(--rf-warning));
|
|
56
|
+
}
|
|
57
|
+
.rf-avatar-status[data-status='busy'] {
|
|
58
|
+
background: light-dark(#ef4444, var(--rf-danger));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* AvatarGroup: avatars overlap by a quarter of their size and spread apart
|
|
62
|
+
on the moderate spring while pointed at. */
|
|
63
|
+
.rf-avatar-group {
|
|
64
|
+
--rf-avatar-overlap: calc(var(--rf-avatar-size) * -0.25);
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0;
|
|
69
|
+
list-style: none;
|
|
70
|
+
}
|
|
71
|
+
.rf-avatar-group > li {
|
|
72
|
+
position: relative;
|
|
73
|
+
display: flex;
|
|
74
|
+
transition: margin-inline-start var(--rf-spring-moderate);
|
|
75
|
+
}
|
|
76
|
+
.rf-avatar-group > li + li {
|
|
77
|
+
margin-inline-start: var(--rf-avatar-overlap);
|
|
78
|
+
}
|
|
79
|
+
@media (hover: hover) {
|
|
80
|
+
.rf-avatar-group:hover > li + li {
|
|
81
|
+
margin-inline-start: 2px;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
.rf-avatar-slot {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
border-radius: 9999px;
|
|
87
|
+
}
|
|
88
|
+
.rf-avatar-group .rf-avatar {
|
|
89
|
+
box-shadow: 0 0 0 2px var(--rf-avatar-ring);
|
|
90
|
+
}
|
|
91
|
+
.rf-avatar-more {
|
|
92
|
+
--rf-avatar-color: var(--rf-muted);
|
|
93
|
+
background: var(--rf-neutral-fill);
|
|
94
|
+
color: var(--rf-fg);
|
|
95
|
+
font-size: calc(var(--rf-avatar-size) * 0.34);
|
|
96
|
+
font-variant-numeric: tabular-nums;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@media (prefers-reduced-motion: reduce) {
|
|
100
|
+
.rf-avatar-group > li {
|
|
101
|
+
transition: none;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
@media (forced-colors: active) {
|
|
105
|
+
.rf-avatar {
|
|
106
|
+
border: 1px solid CanvasText;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* The ring takes the colour of the surface the avatars sit on. */
|
|
111
|
+
.rf-popover-surface .rf-avatar {
|
|
112
|
+
--rf-avatar-ring: var(--rf-surface-3, white);
|
|
113
|
+
}
|
|
114
|
+
.rf-dialog .rf-avatar {
|
|
115
|
+
--rf-avatar-ring: var(--rf-surface-5);
|
|
116
|
+
}
|
|
117
|
+
.rf-drawer .rf-avatar {
|
|
118
|
+
--rf-avatar-ring: var(--rf-drawer-bg);
|
|
119
|
+
}
|
package/dist/avatar.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Handle } from 'remix/component';
|
|
2
|
+
import type { BadgeColor } from './basic.tsx';
|
|
3
|
+
export type AvatarStatus = 'online' | 'away' | 'busy' | 'offline';
|
|
4
|
+
export interface AvatarProps {
|
|
5
|
+
/** Who or what it shows: the accessible name, and the source of the initials. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** An image. Until it loads, or if it fails, the initials show. */
|
|
8
|
+
src?: string;
|
|
9
|
+
/** Width and height in pixels. Default 32. */
|
|
10
|
+
size?: number;
|
|
11
|
+
shape?: 'circle' | 'square';
|
|
12
|
+
/** The initials' colour. Defaults to one picked from the name, the same every time. */
|
|
13
|
+
color?: BadgeColor;
|
|
14
|
+
/** A dot in the corner, named with the avatar. */
|
|
15
|
+
status?: AvatarStatus;
|
|
16
|
+
/** Hides the avatar from assistive technology, for when the name is shown beside it. */
|
|
17
|
+
decorative?: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A picture or the initials of a person, team or agent. The initials sit
|
|
22
|
+
* on a colour picked from the name, so the same name always looks the same;
|
|
23
|
+
* an image covers them once it loads, and they show again if it fails. A
|
|
24
|
+
* status dot is named with the avatar. Mark it decorative when the name is
|
|
25
|
+
* already written beside it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function Avatar(handle: Handle<AvatarProps>): () => import("remix/component").RemixElement;
|
|
28
|
+
export interface AvatarGroupProps {
|
|
29
|
+
avatars: readonly Omit<AvatarProps, 'size' | 'shape' | 'decorative' | 'class'>[];
|
|
30
|
+
/** How many avatars show, the count of the rest included. Default 4. */
|
|
31
|
+
max?: number;
|
|
32
|
+
size?: number;
|
|
33
|
+
shape?: 'circle' | 'square';
|
|
34
|
+
/** Names the group. Defaults to the number of people. */
|
|
35
|
+
label?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A row of overlapping avatars, such as the people on a document. Each
|
|
39
|
+
* names itself in a tooltip; past `max`, a count stands in for the rest and
|
|
40
|
+
* names them. Pointing at the row spreads it apart.
|
|
41
|
+
*/
|
|
42
|
+
export declare function AvatarGroup(handle: Handle<AvatarGroupProps>): () => import("remix/component").RemixElement;
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "remix/component/jsx-runtime";
|
|
2
|
+
import { ref } from 'remix/component';
|
|
3
|
+
import { badgeColors } from './colors.js';
|
|
4
|
+
import { Tooltip } from './overlays.js';
|
|
5
|
+
// Hues far enough apart to tell neighbours in a group apart, legible on
|
|
6
|
+
// both themes.
|
|
7
|
+
const autoColors = [
|
|
8
|
+
'red',
|
|
9
|
+
'orange',
|
|
10
|
+
'amber',
|
|
11
|
+
'green',
|
|
12
|
+
'teal',
|
|
13
|
+
'blue',
|
|
14
|
+
'violet',
|
|
15
|
+
'pink',
|
|
16
|
+
];
|
|
17
|
+
// FNV-1a, which spreads similar names evenly.
|
|
18
|
+
function pickColor(name) {
|
|
19
|
+
let hash = 0x811c9dc5;
|
|
20
|
+
for (const char of name.trim().toLowerCase()) {
|
|
21
|
+
hash ^= char.codePointAt(0);
|
|
22
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
return autoColors[hash % autoColors.length];
|
|
25
|
+
}
|
|
26
|
+
function initials(name) {
|
|
27
|
+
const words = name.trim().split(/\s+/);
|
|
28
|
+
const first = (word) => Array.from(word ?? '').find((char) => /[\p{L}\p{N}]/u.test(char)) ?? '';
|
|
29
|
+
const letters = first(words[0]) + (words.length > 1 ? first(words[words.length - 1]) : '');
|
|
30
|
+
return letters.toLocaleUpperCase();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A picture or the initials of a person, team or agent. The initials sit
|
|
34
|
+
* on a colour picked from the name, so the same name always looks the same;
|
|
35
|
+
* an image covers them once it loads, and they show again if it fails. A
|
|
36
|
+
* status dot is named with the avatar. Mark it decorative when the name is
|
|
37
|
+
* already written beside it.
|
|
38
|
+
*/
|
|
39
|
+
export function Avatar(handle) {
|
|
40
|
+
// A failed image hands back to the initials, even if it failed before hydration.
|
|
41
|
+
const image = ref((node, signal) => {
|
|
42
|
+
if (!(node instanceof HTMLImageElement))
|
|
43
|
+
return;
|
|
44
|
+
const root = node.closest('.rf-avatar');
|
|
45
|
+
const fail = () => root?.setAttribute('data-image', 'error');
|
|
46
|
+
node.addEventListener('error', fail, { signal });
|
|
47
|
+
node.addEventListener('load', () => root?.removeAttribute('data-image'), { signal });
|
|
48
|
+
if (node.complete && node.naturalWidth === 0 && node.getAttribute('src'))
|
|
49
|
+
fail();
|
|
50
|
+
});
|
|
51
|
+
return () => {
|
|
52
|
+
const { name, src, size = 32, shape = 'circle', status, decorative } = handle.props;
|
|
53
|
+
const color = handle.props.color ?? pickColor(name);
|
|
54
|
+
const label = status ? `${name} (${status})` : name;
|
|
55
|
+
return (_jsxs("span", { class: handle.props.class ? `rf-avatar ${handle.props.class}` : 'rf-avatar', role: decorative ? undefined : 'img', "aria-label": decorative ? undefined : label, "aria-hidden": decorative || undefined, "data-shape": shape, "data-color": color, style: { '--rf-avatar-size': `${size}px`, '--rf-avatar-color': badgeColors[color] }, children: [_jsx("span", { class: "rf-avatar-initials", "aria-hidden": "true", children: initials(name) }), src && _jsx("img", { class: "rf-avatar-image", src: src, alt: "", mix: image }), status && _jsx("span", { class: "rf-avatar-status", "data-status": status, "aria-hidden": "true" })] }));
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A row of overlapping avatars, such as the people on a document. Each
|
|
60
|
+
* names itself in a tooltip; past `max`, a count stands in for the rest and
|
|
61
|
+
* names them. Pointing at the row spreads it apart.
|
|
62
|
+
*/
|
|
63
|
+
export function AvatarGroup(handle) {
|
|
64
|
+
return () => {
|
|
65
|
+
const { avatars, size = 32, shape } = handle.props;
|
|
66
|
+
const max = Math.max(1, handle.props.max ?? 4);
|
|
67
|
+
const overflow = avatars.length > max;
|
|
68
|
+
const shown = overflow ? avatars.slice(0, max - 1) : avatars;
|
|
69
|
+
const rest = overflow ? avatars.slice(max - 1) : [];
|
|
70
|
+
const label = handle.props.label ?? `${avatars.length} ${avatars.length === 1 ? 'person' : 'people'}`;
|
|
71
|
+
const names = rest.map((avatar) => avatar.name).join(', ');
|
|
72
|
+
return (_jsxs("ul", { class: "rf-avatar-group", "aria-label": label, style: { '--rf-avatar-size': `${size}px` }, children: [shown.map((avatar, index) => (_jsx("li", { style: { zIndex: shown.length - index + 1 }, children: _jsx(Tooltip, { content: avatar.name, renderTrigger: (trigger) => (_jsx("span", { class: "rf-avatar-slot", ...trigger, children: _jsx(Avatar, { ...avatar, size: size, shape: shape }) })) }) }, `${avatar.name}-${index}`))), overflow && (_jsx("li", { children: _jsx(Tooltip, { content: names, renderTrigger: (trigger) => (_jsx("span", { class: "rf-avatar-slot", ...trigger, children: _jsxs("span", { class: "rf-avatar rf-avatar-more", role: "img", "aria-label": `${rest.length} more: ${names}`, "data-shape": shape ?? 'circle', style: { '--rf-avatar-size': `${size}px` }, children: ["+", rest.length] }) })) }) }))] }));
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/basic.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IconName } from './icon.tsx';
|
|
2
2
|
import type { Handle, Props, RemixNode } from 'remix/component';
|
|
3
|
+
import { badgeColors } from './colors.ts';
|
|
3
4
|
export type ControlSize = 'default' | 'compact';
|
|
4
5
|
export type ButtonSize = ControlSize | 'icon' | 'icon-compact';
|
|
5
6
|
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'destructive';
|
|
@@ -24,26 +25,6 @@ type StyledProps<Tag extends keyof JSX.IntrinsicElements> = Omit<Props<Tag>, 'cl
|
|
|
24
25
|
class?: string;
|
|
25
26
|
className?: string;
|
|
26
27
|
};
|
|
27
|
-
declare const badgeColors: {
|
|
28
|
-
readonly accent: 'var(--rf-accent)';
|
|
29
|
-
readonly gray: '#a3a3a3';
|
|
30
|
-
readonly red: '#ef4444';
|
|
31
|
-
readonly orange: '#f97316';
|
|
32
|
-
readonly amber: '#f59e0b';
|
|
33
|
-
readonly yellow: '#eab308';
|
|
34
|
-
readonly lime: '#84cc16';
|
|
35
|
-
readonly green: '#22c55e';
|
|
36
|
-
readonly emerald: '#10b981';
|
|
37
|
-
readonly teal: '#14b8a6';
|
|
38
|
-
readonly cyan: '#06b6d4';
|
|
39
|
-
readonly blue: '#3b82f6';
|
|
40
|
-
readonly indigo: '#6366f1';
|
|
41
|
-
readonly violet: '#8b5cf6';
|
|
42
|
-
readonly purple: '#a855f7';
|
|
43
|
-
readonly fuchsia: '#d946ef';
|
|
44
|
-
readonly pink: '#ec4899';
|
|
45
|
-
readonly rose: '#f43f5e';
|
|
46
|
-
};
|
|
47
28
|
export type BadgeColor = keyof typeof badgeColors;
|
|
48
29
|
export type BadgeProps = Omit<StyledProps<'span'>, 'color' | 'style'> & {
|
|
49
30
|
color?: BadgeColor;
|
package/dist/basic.js
CHANGED
|
@@ -5,6 +5,7 @@ import { jsx } from 'remix/component/jsx-runtime';
|
|
|
5
5
|
import { fluidHover } from './interactive.js';
|
|
6
6
|
import { Tooltip } from './overlays.js';
|
|
7
7
|
import { statusIcon } from './status-icon.js';
|
|
8
|
+
import { badgeColors } from './colors.js';
|
|
8
9
|
export function Button(handle) {
|
|
9
10
|
return () => {
|
|
10
11
|
const props = handle.props;
|
|
@@ -36,26 +37,6 @@ export function Button(handle) {
|
|
|
36
37
|
function classList(...classes) {
|
|
37
38
|
return classes.filter(Boolean).join(' ');
|
|
38
39
|
}
|
|
39
|
-
const badgeColors = {
|
|
40
|
-
accent: 'var(--rf-accent)',
|
|
41
|
-
gray: '#a3a3a3',
|
|
42
|
-
red: '#ef4444',
|
|
43
|
-
orange: '#f97316',
|
|
44
|
-
amber: '#f59e0b',
|
|
45
|
-
yellow: '#eab308',
|
|
46
|
-
lime: '#84cc16',
|
|
47
|
-
green: '#22c55e',
|
|
48
|
-
emerald: '#10b981',
|
|
49
|
-
teal: '#14b8a6',
|
|
50
|
-
cyan: '#06b6d4',
|
|
51
|
-
blue: '#3b82f6',
|
|
52
|
-
indigo: '#6366f1',
|
|
53
|
-
violet: '#8b5cf6',
|
|
54
|
-
purple: '#a855f7',
|
|
55
|
-
fuchsia: '#d946ef',
|
|
56
|
-
pink: '#ec4899',
|
|
57
|
-
rose: '#f43f5e',
|
|
58
|
-
};
|
|
59
40
|
export function Badge(handle) {
|
|
60
41
|
return () => {
|
|
61
42
|
const { class: classProp, className, color = 'gray', size, variant = 'solid', style, children, ...props } = handle.props;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* Carousel: slides in a row on a native scroll container that snaps each
|
|
2
|
+
one into place. Set --rf-carousel-slide for the slides' width (100% shows
|
|
3
|
+
one at a time) and --rf-carousel-gap for the space between them. While a
|
|
4
|
+
mouse drags it, snapping waits until the slide it lands on is chosen. */
|
|
5
|
+
.rf-carousel {
|
|
6
|
+
--rf-carousel-slide: 100%;
|
|
7
|
+
--rf-carousel-gap: 12px;
|
|
8
|
+
display: grid;
|
|
9
|
+
gap: 12px;
|
|
10
|
+
min-inline-size: 0;
|
|
11
|
+
}
|
|
12
|
+
.rf-carousel-viewport {
|
|
13
|
+
position: relative;
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-auto-columns: var(--rf-carousel-slide);
|
|
16
|
+
grid-auto-flow: column;
|
|
17
|
+
gap: var(--rf-carousel-gap);
|
|
18
|
+
overflow-x: auto;
|
|
19
|
+
overflow-y: hidden;
|
|
20
|
+
overscroll-behavior-x: contain;
|
|
21
|
+
scroll-snap-type: x mandatory;
|
|
22
|
+
scrollbar-width: none;
|
|
23
|
+
border-radius: var(--rf-panel-radius);
|
|
24
|
+
outline: none;
|
|
25
|
+
}
|
|
26
|
+
.rf-carousel-viewport::-webkit-scrollbar {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|
|
29
|
+
.rf-carousel-viewport:focus-visible {
|
|
30
|
+
outline: 2px solid var(--rf-accent);
|
|
31
|
+
outline-offset: 2px;
|
|
32
|
+
}
|
|
33
|
+
@media (hover: hover) and (pointer: fine) {
|
|
34
|
+
.rf-carousel-viewport {
|
|
35
|
+
cursor: grab;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
.rf-carousel-viewport[data-dragging] {
|
|
39
|
+
cursor: grabbing;
|
|
40
|
+
scroll-snap-type: none;
|
|
41
|
+
user-select: none;
|
|
42
|
+
}
|
|
43
|
+
.rf-carousel-slide {
|
|
44
|
+
min-inline-size: 0;
|
|
45
|
+
scroll-snap-align: start;
|
|
46
|
+
scroll-snap-stop: normal;
|
|
47
|
+
}
|
|
48
|
+
.rf-carousel[data-align='center'] .rf-carousel-slide {
|
|
49
|
+
scroll-snap-align: center;
|
|
50
|
+
}
|
|
51
|
+
/* Dragging moves the slides, not their images. */
|
|
52
|
+
.rf-carousel-slide img {
|
|
53
|
+
-webkit-user-drag: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rf-carousel-controls {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
gap: 4px;
|
|
61
|
+
}
|
|
62
|
+
.rf-carousel-controls .rf-carousel-dots {
|
|
63
|
+
margin-inline: 4px;
|
|
64
|
+
}
|
|
65
|
+
.rf-carousel-play {
|
|
66
|
+
margin-inline-start: 4px;
|
|
67
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Handle, type RemixNode } from 'remix/component';
|
|
2
|
+
export interface CarouselProps {
|
|
3
|
+
/** Names the carousel, such as "Featured projects". */
|
|
4
|
+
label: string;
|
|
5
|
+
/** The slides, one per child. */
|
|
6
|
+
children?: RemixNode;
|
|
7
|
+
/** Names each slide, for its announcement and its dot. */
|
|
8
|
+
slideLabels?: readonly string[];
|
|
9
|
+
/** The current slide, when controlled by the parent. */
|
|
10
|
+
index?: number;
|
|
11
|
+
defaultIndex?: number;
|
|
12
|
+
/** Called when a button, a dot, a drag, a scroll or autoplay changes the slide. */
|
|
13
|
+
onIndexChange?: (index: number) => void;
|
|
14
|
+
/** Shows CarouselDots under the slides. Default true. */
|
|
15
|
+
dots?: boolean;
|
|
16
|
+
/** Shows previous and next buttons. Default true. */
|
|
17
|
+
controls?: boolean;
|
|
18
|
+
/** Moves on after this many milliseconds, timed and shown by the dots, with a button to pause. */
|
|
19
|
+
autoplay?: {
|
|
20
|
+
duration: number;
|
|
21
|
+
};
|
|
22
|
+
/** Where a slide comes to rest. Default start. */
|
|
23
|
+
align?: 'start' | 'center';
|
|
24
|
+
size?: 'default' | 'compact';
|
|
25
|
+
class?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Slides in a row on a real scroll container: touch, trackpads, the
|
|
29
|
+
* keyboard and screen readers scroll it natively, and CSS scroll snap
|
|
30
|
+
* brings each slide to rest. Mice can drag it, with momentum. Previous and
|
|
31
|
+
* next buttons and CarouselDots follow the slide in view and move it;
|
|
32
|
+
* autoplay runs through the dots and pauses while the carousel is pointed
|
|
33
|
+
* at or focused, or with its pause button. It is a labelled carousel whose
|
|
34
|
+
* slides read as "3 of 7", and it touches nothing outside its own element.
|
|
35
|
+
*/
|
|
36
|
+
export declare function Carousel(handle: Handle<CarouselProps>): () => import("remix/component").RemixElement;
|