aplance-ui 1.5.0 → 1.5.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/package.json +1 -1
- package/registry/components/{ui → primitives}/scroll-rail/scroll-rail.module.css +48 -4
- package/registry/components/{ui → primitives}/scroll-rail/scroll-rail.tsx +84 -29
- package/registry/components/ui/data-table/pagination-bar.tsx +1 -1
- package/registry/components/ui/tabs/tabs.tsx +1 -1
- package/registry/registry.json +5 -6
package/package.json
CHANGED
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
min-width: 0;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
.rail_without_controls {
|
|
34
|
+
--scroll-rail-control: 0px;
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
/* The scroller. It shows no scrollbar, so the row never changes height, and
|
|
34
38
|
pads like a toolbar so its controls' focus rings show, since a scroller
|
|
35
39
|
clips both axes. */
|
|
@@ -88,17 +92,57 @@
|
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
/* A chevron, over the clear edge of its fade, on the row's middle.
|
|
92
|
-
|
|
95
|
+
/* A chevron, over the clear edge of its fade, on the row's middle. The
|
|
96
|
+
primitive owns only neutral button chrome; native props let a caller replace
|
|
97
|
+
the icon, label, classes, and inline styles without depending on Button. */
|
|
98
|
+
.control {
|
|
93
99
|
position: absolute;
|
|
94
100
|
top: 50%;
|
|
95
101
|
translate: 0 -50%;
|
|
102
|
+
z-index: 1;
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
width: var(--spacing-10);
|
|
107
|
+
height: var(--spacing-10);
|
|
108
|
+
padding: 0;
|
|
109
|
+
appearance: none;
|
|
110
|
+
font: inherit;
|
|
111
|
+
color: var(--color-foreground);
|
|
112
|
+
background-color: var(--color-background);
|
|
113
|
+
border: 1px solid var(--color-border);
|
|
114
|
+
border-radius: var(--radius-md);
|
|
115
|
+
box-shadow: var(--shadow-sm);
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
transition:
|
|
118
|
+
color var(--transition-base),
|
|
119
|
+
background-color var(--transition-base),
|
|
120
|
+
border-color var(--transition-base),
|
|
121
|
+
opacity var(--transition-base);
|
|
122
|
+
|
|
123
|
+
&:hover:not(:disabled) {
|
|
124
|
+
background-color: var(--color-muted);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&:focus-visible {
|
|
128
|
+
outline: 2px solid var(--color-primary);
|
|
129
|
+
outline-offset: 2px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&:disabled {
|
|
133
|
+
cursor: not-allowed;
|
|
134
|
+
opacity: 0.5;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
> svg {
|
|
138
|
+
pointer-events: none;
|
|
139
|
+
}
|
|
96
140
|
}
|
|
97
141
|
|
|
98
|
-
.
|
|
142
|
+
.control_start {
|
|
99
143
|
left: 0;
|
|
100
144
|
}
|
|
101
145
|
|
|
102
|
-
.
|
|
146
|
+
.control_end {
|
|
103
147
|
right: 0;
|
|
104
148
|
}
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
useEffect,
|
|
4
4
|
useRef,
|
|
5
5
|
useState,
|
|
6
|
+
type ComponentPropsWithoutRef,
|
|
6
7
|
type ReactNode,
|
|
7
8
|
type Ref,
|
|
8
9
|
} from "react";
|
|
9
10
|
import clsx from "clsx";
|
|
10
11
|
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
|
|
11
12
|
import { useResizeObserver } from "../../../hooks/useResizeObserver";
|
|
12
|
-
import { Button } from "../button/button";
|
|
13
13
|
import s from "./scroll-rail.module.css";
|
|
14
14
|
|
|
15
15
|
// A row that scrolls sideways, as a row of chips or tabs does on a phone:
|
|
@@ -30,12 +30,36 @@ import s from "./scroll-rail.module.css";
|
|
|
30
30
|
/** How far a chevron scrolls, in widths of the row. */
|
|
31
31
|
const PAGE = 0.8;
|
|
32
32
|
|
|
33
|
-
export type
|
|
33
|
+
export type ScrollRailControlProps = Omit<
|
|
34
|
+
ComponentPropsWithoutRef<"button">,
|
|
35
|
+
"type"
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
export type ScrollRailControls = {
|
|
39
|
+
/** Class shared by both arrow buttons. */
|
|
40
|
+
className?: string;
|
|
41
|
+
/** Native button props for the previous arrow. `children` replaces its icon. */
|
|
42
|
+
previous?: ScrollRailControlProps;
|
|
43
|
+
/** Native button props for the next arrow. `children` replaces its icon. */
|
|
44
|
+
next?: ScrollRailControlProps;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ScrollRailProps = Omit<
|
|
48
|
+
ComponentPropsWithoutRef<"div">,
|
|
49
|
+
"children"
|
|
50
|
+
> & {
|
|
34
51
|
children: ReactNode;
|
|
35
52
|
/** The rail's own box, as a flex item where it sits. */
|
|
36
53
|
className?: string;
|
|
54
|
+
/** Class merged onto the horizontal scroller. */
|
|
55
|
+
trackClassName?: string;
|
|
37
56
|
/** The row's layout around its items, such as their gap. */
|
|
38
57
|
contentClassName?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Customize the overflow arrows with native button props, or hide them with
|
|
60
|
+
* `false`. A custom onClick runs first and can prevent the built-in scroll.
|
|
61
|
+
*/
|
|
62
|
+
controls?: false | ScrollRailControls;
|
|
39
63
|
/** The scroller, for a caller that scrolls it itself, as Tabs keeps its
|
|
40
64
|
* active tab in view. */
|
|
41
65
|
ref?: Ref<HTMLDivElement>;
|
|
@@ -54,8 +78,11 @@ const wheelUnit = (event: WheelEvent, track: HTMLElement) =>
|
|
|
54
78
|
export const ScrollRail = ({
|
|
55
79
|
children,
|
|
56
80
|
className,
|
|
81
|
+
trackClassName,
|
|
57
82
|
contentClassName,
|
|
83
|
+
controls,
|
|
58
84
|
ref,
|
|
85
|
+
...rootProps
|
|
59
86
|
}: ScrollRailProps) => {
|
|
60
87
|
const trackRef = useRef<HTMLDivElement | null>(null);
|
|
61
88
|
const setTrack = useCallback(
|
|
@@ -85,6 +112,12 @@ export const ScrollRail = ({
|
|
|
85
112
|
useResizeObserver(trackRef, measure);
|
|
86
113
|
useResizeObserver(contentRef, measure);
|
|
87
114
|
|
|
115
|
+
// Measure once after mount as well. ResizeObserver reports later size
|
|
116
|
+
// changes, but its initial notification is not synchronous in every host.
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
measure();
|
|
119
|
+
}, [measure]);
|
|
120
|
+
|
|
88
121
|
useEffect(() => {
|
|
89
122
|
const track = trackRef.current;
|
|
90
123
|
if (!track) return;
|
|
@@ -114,46 +147,68 @@ export const ScrollRail = ({
|
|
|
114
147
|
});
|
|
115
148
|
};
|
|
116
149
|
|
|
150
|
+
const renderControl = (direction: -1 | 1) => {
|
|
151
|
+
const visible = direction === -1 ? ends.start : ends.end;
|
|
152
|
+
if (!visible || controls === false) return null;
|
|
153
|
+
|
|
154
|
+
const control = direction === -1 ? controls?.previous : controls?.next;
|
|
155
|
+
const {
|
|
156
|
+
children: controlIcon,
|
|
157
|
+
className: controlClassName,
|
|
158
|
+
onClick,
|
|
159
|
+
"aria-label": ariaLabel,
|
|
160
|
+
...controlProps
|
|
161
|
+
} = control ?? {};
|
|
162
|
+
const label = direction === -1 ? "Scroll left" : "Scroll right";
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<button
|
|
166
|
+
{...controlProps}
|
|
167
|
+
type="button"
|
|
168
|
+
className={clsx(
|
|
169
|
+
s.control,
|
|
170
|
+
direction === -1 ? s.control_start : s.control_end,
|
|
171
|
+
controls?.className,
|
|
172
|
+
controlClassName,
|
|
173
|
+
)}
|
|
174
|
+
aria-label={ariaLabel ?? label}
|
|
175
|
+
onClick={(event) => {
|
|
176
|
+
onClick?.(event);
|
|
177
|
+
if (!event.defaultPrevented) page(direction);
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
{controlIcon ??
|
|
181
|
+
(direction === -1 ? (
|
|
182
|
+
<IconChevronLeft size={14} stroke={2} />
|
|
183
|
+
) : (
|
|
184
|
+
<IconChevronRight size={14} stroke={2} />
|
|
185
|
+
))}
|
|
186
|
+
</button>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
117
190
|
return (
|
|
118
191
|
<div
|
|
192
|
+
{...rootProps}
|
|
119
193
|
className={clsx(
|
|
120
194
|
s.rail,
|
|
121
195
|
ends.start && s.rail_start,
|
|
122
196
|
ends.end && s.rail_end,
|
|
197
|
+
controls === false && s.rail_without_controls,
|
|
123
198
|
className,
|
|
124
199
|
)}
|
|
125
200
|
>
|
|
126
|
-
<div
|
|
201
|
+
<div
|
|
202
|
+
ref={setTrack}
|
|
203
|
+
className={clsx(s.track, trackClassName)}
|
|
204
|
+
onScroll={measure}
|
|
205
|
+
>
|
|
127
206
|
<div ref={contentRef} className={clsx(s.content, contentClassName)}>
|
|
128
207
|
{children}
|
|
129
208
|
</div>
|
|
130
209
|
</div>
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
type="button"
|
|
134
|
-
variant="bordered"
|
|
135
|
-
size="sm"
|
|
136
|
-
layout="box"
|
|
137
|
-
className={clsx(s.more, s.more_start)}
|
|
138
|
-
aria-label="Scroll left"
|
|
139
|
-
onClick={() => page(-1)}
|
|
140
|
-
>
|
|
141
|
-
<IconChevronLeft size={14} stroke={2} />
|
|
142
|
-
</Button>
|
|
143
|
-
) : null}
|
|
144
|
-
{ends.end ? (
|
|
145
|
-
<Button
|
|
146
|
-
type="button"
|
|
147
|
-
variant="bordered"
|
|
148
|
-
size="sm"
|
|
149
|
-
layout="box"
|
|
150
|
-
className={clsx(s.more, s.more_end)}
|
|
151
|
-
aria-label="Scroll right"
|
|
152
|
-
onClick={() => page(1)}
|
|
153
|
-
>
|
|
154
|
-
<IconChevronRight size={14} stroke={2} />
|
|
155
|
-
</Button>
|
|
156
|
-
) : null}
|
|
210
|
+
{renderControl(-1)}
|
|
211
|
+
{renderControl(1)}
|
|
157
212
|
</div>
|
|
158
213
|
);
|
|
159
214
|
};
|
|
@@ -8,7 +8,7 @@ import clsx from "clsx";
|
|
|
8
8
|
import { Button } from "../button/button";
|
|
9
9
|
import { CustomSelect } from "../input-fields/custom-select/custom-select";
|
|
10
10
|
import { Loader } from "../loader/loader";
|
|
11
|
-
import { ScrollRail } from "
|
|
11
|
+
import { ScrollRail } from "../../primitives/scroll-rail/scroll-rail";
|
|
12
12
|
import type { PaginationBarProps } from "./types";
|
|
13
13
|
import s from "./data-table.module.css";
|
|
14
14
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "react";
|
|
11
11
|
import clsx from "clsx";
|
|
12
12
|
import { useResizeObserver } from "../../../hooks/useResizeObserver";
|
|
13
|
-
import { ScrollRail } from "
|
|
13
|
+
import { ScrollRail } from "../../primitives/scroll-rail/scroll-rail";
|
|
14
14
|
import s from "./tabs.module.css";
|
|
15
15
|
|
|
16
16
|
// Variants & Types
|
package/registry/registry.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ui.aplance.com/registry.schema.json",
|
|
3
3
|
"version": 4,
|
|
4
|
-
"generated": "2026-11-
|
|
4
|
+
"generated": "2026-11-11T12:39:14.314Z",
|
|
5
5
|
"units": [
|
|
6
6
|
{
|
|
7
7
|
"name": "accordion",
|
|
@@ -573,16 +573,15 @@
|
|
|
573
573
|
{
|
|
574
574
|
"name": "scroll-rail",
|
|
575
575
|
"kind": "component",
|
|
576
|
-
"group": "
|
|
576
|
+
"group": "primitives",
|
|
577
577
|
"files": [
|
|
578
|
-
"components/
|
|
579
|
-
"components/
|
|
578
|
+
"components/primitives/scroll-rail/scroll-rail.module.css",
|
|
579
|
+
"components/primitives/scroll-rail/scroll-rail.tsx"
|
|
580
580
|
],
|
|
581
581
|
"clientFiles": [
|
|
582
|
-
"components/
|
|
582
|
+
"components/primitives/scroll-rail/scroll-rail.tsx"
|
|
583
583
|
],
|
|
584
584
|
"deps": [
|
|
585
|
-
"button",
|
|
586
585
|
"useResizeObserver"
|
|
587
586
|
],
|
|
588
587
|
"externalDeps": [
|