aplance-ui 1.4.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/primitives/icon/icon.tsx +4 -6
- package/registry/components/primitives/modal/modal.module.css +5 -0
- package/registry/components/primitives/modal/modal.tsx +32 -34
- package/registry/components/primitives/popover/popover.module.css +4 -2
- package/registry/components/primitives/popover/popover.tsx +16 -17
- package/registry/components/primitives/scroll-rail/scroll-rail.module.css +148 -0
- package/registry/components/primitives/scroll-rail/scroll-rail.tsx +214 -0
- package/registry/components/ui/accordion/accordion.module.css +52 -0
- package/registry/components/ui/accordion/accordion.tsx +97 -25
- package/registry/components/ui/avatar/avatar.tsx +2 -2
- package/registry/components/ui/breadcrumbs/breadcrumbs.module.css +9 -9
- package/registry/components/ui/button/button.module.css +43 -5
- package/registry/components/ui/button/button.tsx +2 -1
- package/registry/components/ui/copy-button/copy-button.module.css +2 -0
- package/registry/components/ui/data-table/data-table.module.css +68 -25
- package/registry/components/ui/data-table/data-table.tsx +1238 -1229
- package/registry/components/ui/data-table/filters.tsx +21 -0
- package/registry/components/ui/data-table/pagination-bar.tsx +31 -17
- package/registry/components/ui/data-table/types.ts +11 -1
- package/registry/components/ui/data-table/useDataTable.tsx +1 -2
- package/registry/components/ui/data-table/utils.ts +4 -3
- package/registry/components/ui/input-fields/common/input-field-common.module.css +8 -2
- package/registry/components/ui/input-fields/common/picker-popover-common.module.css +4 -0
- package/registry/components/ui/input-fields/custom-select/custom-select.module.css +115 -84
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +63 -11
- package/registry/components/ui/input-fields/date-picker/date-picker.tsx +51 -55
- package/registry/components/ui/input-fields/input/input.tsx +348 -348
- package/registry/components/ui/input-fields/media-input/media-input.tsx +3 -1
- package/registry/components/ui/input-fields/media-input/types.ts +8 -2
- package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +5 -5
- package/registry/components/ui/input-fields/media-input/utils.ts +18 -5
- package/registry/components/ui/tabs/tabs.module.css +8 -12
- package/registry/components/ui/tabs/tabs.tsx +75 -44
- package/registry/components/ui/tooltip/tooltip.module.css +8 -3
- package/registry/components/ui/tooltip/tooltip.tsx +16 -3
- package/registry/lib/string.ts +3 -3
- package/registry/registry.json +32 -3
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import clsx from "clsx";
|
|
10
10
|
import { IconChevronDown } from "@tabler/icons-react";
|
|
11
11
|
import { useResizeObserver } from "../../../hooks/useResizeObserver";
|
|
12
|
+
import { Button } from "../button/button";
|
|
12
13
|
import s from "./accordion.module.css";
|
|
13
14
|
|
|
14
15
|
export type AccordionVariant = "bordered" | "minimal";
|
|
@@ -17,12 +18,23 @@ export type AccordionMode = "single" | "multiple";
|
|
|
17
18
|
|
|
18
19
|
export type AccordionSize = "sm" | "md" | "lg";
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* How an item opens. icon: the whole heading, with a chevron in it. button:
|
|
23
|
+
* the heading too, with a bordered chevron button at its end, as a table row
|
|
24
|
+
* ends in its menu button.
|
|
25
|
+
*/
|
|
26
|
+
export type AccordionToggle = "icon" | "button";
|
|
27
|
+
|
|
20
28
|
export type AccordionItem = {
|
|
21
29
|
id: string;
|
|
22
30
|
title: ReactNode;
|
|
23
31
|
content: ReactNode;
|
|
24
32
|
disabled?: boolean;
|
|
25
33
|
defaultOpen?: boolean;
|
|
34
|
+
/** Controls beside the heading, outside the button that opens it: a row's menu. */
|
|
35
|
+
actions?: ReactNode;
|
|
36
|
+
/** This item's own class, beside `itemClassName`: one marked apart from the rest. */
|
|
37
|
+
className?: string;
|
|
26
38
|
};
|
|
27
39
|
|
|
28
40
|
type AccordionBaseProps = {
|
|
@@ -35,6 +47,7 @@ type AccordionBaseProps = {
|
|
|
35
47
|
size?: AccordionSize;
|
|
36
48
|
icon?: ReactNode;
|
|
37
49
|
iconRotation?: number;
|
|
50
|
+
toggle?: AccordionToggle;
|
|
38
51
|
};
|
|
39
52
|
|
|
40
53
|
type AccordionSingleProps = {
|
|
@@ -80,6 +93,7 @@ type ItemViewProps = {
|
|
|
80
93
|
size: AccordionSize;
|
|
81
94
|
variant: AccordionVariant;
|
|
82
95
|
icon?: ReactNode;
|
|
96
|
+
toggle: AccordionToggle;
|
|
83
97
|
itemClassName?: string;
|
|
84
98
|
triggerClassName?: string;
|
|
85
99
|
contentClassName?: string;
|
|
@@ -114,6 +128,7 @@ const AccordionItemView = ({
|
|
|
114
128
|
size,
|
|
115
129
|
variant,
|
|
116
130
|
icon,
|
|
131
|
+
toggle,
|
|
117
132
|
itemClassName,
|
|
118
133
|
triggerClassName,
|
|
119
134
|
contentClassName,
|
|
@@ -138,6 +153,13 @@ const AccordionItemView = ({
|
|
|
138
153
|
|
|
139
154
|
useResizeObserver(contentRef, updateHeight, { enabled: isOpen });
|
|
140
155
|
|
|
156
|
+
const chevron = icon ?? (
|
|
157
|
+
<IconChevronDown size={size === "sm" ? 16 : size === "lg" ? 20 : 18} />
|
|
158
|
+
);
|
|
159
|
+
// With actions or a toggle button, the heading is a row: the button that
|
|
160
|
+
// opens the item, then the controls beside it, outside that button.
|
|
161
|
+
const row = toggle === "button" || item.actions !== undefined;
|
|
162
|
+
|
|
141
163
|
return (
|
|
142
164
|
<section
|
|
143
165
|
className={clsx(
|
|
@@ -146,42 +168,90 @@ const AccordionItemView = ({
|
|
|
146
168
|
isOpen && s.item_open,
|
|
147
169
|
item.disabled && s.item_disabled,
|
|
148
170
|
itemClassName,
|
|
171
|
+
item.className,
|
|
149
172
|
)}
|
|
150
173
|
data-state={isOpen ? "open" : "closed"}
|
|
151
174
|
>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
s.trigger,
|
|
161
|
-
s[`trigger_${size}`],
|
|
162
|
-
s[`trigger_${variant}`],
|
|
163
|
-
isOpen && s.trigger_open,
|
|
164
|
-
triggerClassName,
|
|
165
|
-
)}
|
|
166
|
-
>
|
|
167
|
-
<span className={clsx(s.title, s[`title_${size}`])}>{item.title}</span>
|
|
168
|
-
<span
|
|
169
|
-
className={clsx(s.icon, isOpen && s.icon_open)}
|
|
170
|
-
aria-hidden="true"
|
|
175
|
+
{row ? (
|
|
176
|
+
<div
|
|
177
|
+
className={clsx(
|
|
178
|
+
s.header,
|
|
179
|
+
s[`trigger_${size}`],
|
|
180
|
+
s[`header_${variant}`],
|
|
181
|
+
isOpen && s.trigger_open,
|
|
182
|
+
)}
|
|
171
183
|
>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
<button
|
|
185
|
+
id={triggerId}
|
|
186
|
+
type="button"
|
|
187
|
+
disabled={item.disabled}
|
|
188
|
+
aria-expanded={isOpen}
|
|
189
|
+
aria-controls={panelId}
|
|
190
|
+
onClick={onToggle}
|
|
191
|
+
className={clsx(s.trigger, s.header_trigger, triggerClassName)}
|
|
192
|
+
>
|
|
193
|
+
<span className={clsx(s.title, s[`title_${size}`])}>{item.title}</span>
|
|
194
|
+
{toggle === "icon" ? (
|
|
195
|
+
<span
|
|
196
|
+
className={clsx(s.icon, isOpen && s.icon_open)}
|
|
197
|
+
aria-hidden="true"
|
|
198
|
+
>
|
|
199
|
+
{chevron}
|
|
200
|
+
</span>
|
|
201
|
+
) : null}
|
|
202
|
+
</button>
|
|
203
|
+
{item.actions !== undefined ? (
|
|
204
|
+
<div className={s.actions}>{item.actions}</div>
|
|
205
|
+
) : null}
|
|
206
|
+
{toggle === "button" ? (
|
|
207
|
+
<Button
|
|
208
|
+
type="button"
|
|
209
|
+
variant="bordered"
|
|
210
|
+
size="sm"
|
|
211
|
+
layout="box"
|
|
212
|
+
aria-label={isOpen ? "Collapse section" : "Expand section"}
|
|
213
|
+
aria-expanded={isOpen}
|
|
214
|
+
aria-controls={panelId}
|
|
215
|
+
disabled={item.disabled}
|
|
216
|
+
onClick={onToggle}
|
|
217
|
+
className={clsx(s.toggle, isOpen && s.toggle_open)}
|
|
218
|
+
>
|
|
219
|
+
{chevron}
|
|
220
|
+
</Button>
|
|
221
|
+
) : null}
|
|
222
|
+
</div>
|
|
223
|
+
) : (
|
|
224
|
+
<button
|
|
225
|
+
id={triggerId}
|
|
226
|
+
type="button"
|
|
227
|
+
disabled={item.disabled}
|
|
228
|
+
aria-expanded={isOpen}
|
|
229
|
+
aria-controls={panelId}
|
|
230
|
+
onClick={onToggle}
|
|
231
|
+
className={clsx(
|
|
232
|
+
s.trigger,
|
|
233
|
+
s[`trigger_${size}`],
|
|
234
|
+
s[`trigger_${variant}`],
|
|
235
|
+
isOpen && s.trigger_open,
|
|
236
|
+
triggerClassName,
|
|
176
237
|
)}
|
|
177
|
-
|
|
178
|
-
|
|
238
|
+
>
|
|
239
|
+
<span className={clsx(s.title, s[`title_${size}`])}>{item.title}</span>
|
|
240
|
+
<span
|
|
241
|
+
className={clsx(s.icon, isOpen && s.icon_open)}
|
|
242
|
+
aria-hidden="true"
|
|
243
|
+
>
|
|
244
|
+
{chevron}
|
|
245
|
+
</span>
|
|
246
|
+
</button>
|
|
247
|
+
)}
|
|
179
248
|
|
|
180
249
|
<div
|
|
181
250
|
id={panelId}
|
|
182
251
|
role="region"
|
|
183
252
|
aria-labelledby={triggerId}
|
|
184
253
|
aria-hidden={!isOpen}
|
|
254
|
+
inert={!isOpen}
|
|
185
255
|
className={clsx(s.panel, isOpen && s.panel_open)}
|
|
186
256
|
style={{ height: `${panelHeight}px` }}
|
|
187
257
|
>
|
|
@@ -212,6 +282,7 @@ export const Accordion = ({
|
|
|
212
282
|
size = "md",
|
|
213
283
|
icon,
|
|
214
284
|
iconRotation = 180,
|
|
285
|
+
toggle = "icon",
|
|
215
286
|
...variantProps
|
|
216
287
|
}: AccordionProps) => {
|
|
217
288
|
const uid = useId();
|
|
@@ -317,6 +388,7 @@ export const Accordion = ({
|
|
|
317
388
|
size={size}
|
|
318
389
|
variant={variant}
|
|
319
390
|
icon={icon}
|
|
391
|
+
toggle={toggle}
|
|
320
392
|
itemClassName={itemClassName}
|
|
321
393
|
triggerClassName={triggerClassName}
|
|
322
394
|
contentClassName={contentClassName}
|
|
@@ -17,10 +17,10 @@ const COLORS = [
|
|
|
17
17
|
{ bg: "oklch(0.92 0.06 85)", fg: "oklch(0.4 0.15 85)" },
|
|
18
18
|
{ bg: "oklch(0.92 0.05 25)", fg: "oklch(0.4 0.15 25)" },
|
|
19
19
|
{ bg: "oklch(0.92 0.05 330)", fg: "oklch(0.4 0.15 330)" },
|
|
20
|
-
];
|
|
20
|
+
] as const;
|
|
21
21
|
|
|
22
22
|
export function Avatar({ name, size = "md", className }: AvatarProps) {
|
|
23
|
-
const color = COLORS[hashName(name) % COLORS.length];
|
|
23
|
+
const color = COLORS[hashName(name) % COLORS.length] ?? COLORS[0];
|
|
24
24
|
const initials = getInitials(name) || "?";
|
|
25
25
|
return (
|
|
26
26
|
<div
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
--breadcrumbs-color-current: var(--color-foreground);
|
|
10
10
|
--breadcrumbs-color-separator: var(--color-muted-foreground);
|
|
11
11
|
--breadcrumbs-font-size: var(--font-size-sm);
|
|
12
|
-
--breadcrumbs-gap: var(--spacing-
|
|
13
|
-
--breadcrumbs-padding: var(--spacing-2) var(--spacing-3);
|
|
12
|
+
--breadcrumbs-gap: var(--spacing-5);
|
|
14
13
|
|
|
15
14
|
display: block;
|
|
16
15
|
width: 100%;
|
|
@@ -22,8 +21,7 @@
|
|
|
22
21
|
|
|
23
22
|
.size_sm {
|
|
24
23
|
--breadcrumbs-font-size: var(--font-size-xs);
|
|
25
|
-
--breadcrumbs-gap: var(--spacing-
|
|
26
|
-
--breadcrumbs-padding: var(--spacing-1) var(--spacing-2);
|
|
24
|
+
--breadcrumbs-gap: var(--spacing-4);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
/* ─── List ───────────────────────────────────────────────────────────────── */
|
|
@@ -45,15 +43,18 @@
|
|
|
45
43
|
min-width: 0;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
/* ─── Crumb (link / button / span)
|
|
46
|
+
/* ─── Crumb (link / button / span) ───────────────────────────────────────────
|
|
47
|
+
A text link, not a button: no padding or hover fill, so every crumb sits on
|
|
48
|
+
the same edge as the content under it. Hover darkens the text.
|
|
49
|
+
─────────────────────────────────────────────────────────────────────────── */
|
|
49
50
|
|
|
50
51
|
.crumb {
|
|
51
52
|
display: inline-flex;
|
|
52
53
|
align-items: center;
|
|
53
54
|
gap: var(--spacing-2);
|
|
54
55
|
min-width: 0;
|
|
55
|
-
padding:
|
|
56
|
-
border-radius: var(--radius-
|
|
56
|
+
padding: 0;
|
|
57
|
+
border-radius: var(--radius-sm);
|
|
57
58
|
color: inherit;
|
|
58
59
|
background-color: transparent;
|
|
59
60
|
font: inherit;
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
white-space: nowrap;
|
|
63
64
|
cursor: default;
|
|
64
65
|
transition: var(--transition-fast);
|
|
65
|
-
transition-property:
|
|
66
|
+
transition-property: color;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
.crumb_link {
|
|
@@ -71,7 +72,6 @@
|
|
|
71
72
|
|
|
72
73
|
&:hover {
|
|
73
74
|
color: var(--breadcrumbs-color-hover);
|
|
74
|
-
background-color: var(--color-muted);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
&:focus-visible {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.base {
|
|
1
|
+
.base {
|
|
2
2
|
--button-fg: var(--color-foreground);
|
|
3
3
|
--button-placeholder: var(--button-fg);
|
|
4
4
|
--button-icon: var(--button-fg);
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
min-height: var(--spacing-11);
|
|
12
12
|
border-radius: var(--radius-md);
|
|
13
13
|
display: flex;
|
|
14
|
-
|
|
14
|
+
/* Never narrower than its label, unless the host sets the variable (a
|
|
15
|
+
full-width field trigger sets 0, so its label truncates). */
|
|
16
|
+
min-width: var(--button-min-width, fit-content);
|
|
15
17
|
align-items: center;
|
|
16
18
|
justify-content: space-between;
|
|
17
19
|
gap: var(--spacing-4);
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
outline: none;
|
|
25
27
|
border: none;
|
|
26
28
|
|
|
27
|
-
line-height: 1.125;
|
|
29
|
+
line-height: 1.125;
|
|
28
30
|
|
|
29
31
|
svg {
|
|
30
32
|
cursor: pointer;
|
|
@@ -36,12 +38,16 @@
|
|
|
36
38
|
font-size: var(--font-size-xs);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
/* An icon button: as wide as it is high, its own size and never its
|
|
42
|
+
content's (the base keeps a button at least as wide as its content), so
|
|
43
|
+
a round one stays round; the icon sits in its middle. */
|
|
44
|
+
&.box {
|
|
40
45
|
width: var(--spacing-11);
|
|
46
|
+
min-width: 0;
|
|
41
47
|
min-height: unset;
|
|
42
48
|
justify-content: center;
|
|
43
49
|
height: var(--spacing-11);
|
|
44
|
-
padding:
|
|
50
|
+
padding: 0;
|
|
45
51
|
|
|
46
52
|
&.sm {
|
|
47
53
|
width: var(--spacing-10);
|
|
@@ -183,6 +189,38 @@
|
|
|
183
189
|
}
|
|
184
190
|
}
|
|
185
191
|
|
|
192
|
+
/* Same pattern - danger is the soft red a Badge's error tone uses, for an
|
|
193
|
+
action that cannot be taken back: deleting, purging, removing. It reads in
|
|
194
|
+
both themes from the error tokens alone. */
|
|
195
|
+
:is(.danger, .tone_danger) {
|
|
196
|
+
--button-fg: var(--color-error-foreground);
|
|
197
|
+
--button-placeholder: var(--color-error-foreground);
|
|
198
|
+
--button-icon: var(--color-error-foreground);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.danger {
|
|
202
|
+
background-color: var(--color-error-subtle);
|
|
203
|
+
border: 1px solid
|
|
204
|
+
color-mix(in oklch, var(--color-error-foreground) 24%, var(--color-border));
|
|
205
|
+
color: var(--button-fg);
|
|
206
|
+
box-shadow: var(--shadow-sm);
|
|
207
|
+
|
|
208
|
+
svg {
|
|
209
|
+
color: var(--button-icon);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
&:hover:not(.disabled),
|
|
213
|
+
&:active:not(.disabled),
|
|
214
|
+
&.input_focus:focus,
|
|
215
|
+
&.active {
|
|
216
|
+
background-color: color-mix(
|
|
217
|
+
in oklch,
|
|
218
|
+
var(--color-error-foreground) 14%,
|
|
219
|
+
var(--color-error-subtle)
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
186
224
|
/* Same pattern - ghost is transparent background; tone alias lets composing
|
|
187
225
|
components inherit these vars without applying the full ghost layout rules. */
|
|
188
226
|
:is(.ghost, .tone_ghost) {
|
|
@@ -12,7 +12,8 @@ export type ButtonVariant =
|
|
|
12
12
|
| "secondary"
|
|
13
13
|
| "bordered"
|
|
14
14
|
| "border_b"
|
|
15
|
-
| "ghost"
|
|
15
|
+
| "ghost"
|
|
16
|
+
| "danger";
|
|
16
17
|
export type ButtonSize = "default" | "sm";
|
|
17
18
|
export type ButtonLayout = "default" | "box";
|
|
18
19
|
export type AsProp = "button" | "div" | "span" | "link";
|
|
@@ -181,6 +181,12 @@
|
|
|
181
181
|
scrollbar-gutter: auto;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/* The columns fit (`columnsFit` in data-table.tsx): nothing to scroll to
|
|
185
|
+
sideways, so a sub-pixel overhang can't raise a horizontal scrollbar. */
|
|
186
|
+
.table_body_wrap[data-fits="true"] {
|
|
187
|
+
overflow-x: hidden;
|
|
188
|
+
}
|
|
189
|
+
|
|
184
190
|
/* ─────────────────────────────────────────────
|
|
185
191
|
Shared <table> - header wrap + body wrap.
|
|
186
192
|
table-layout:fixed + identical widths = perfect column alignment.
|
|
@@ -209,6 +215,16 @@
|
|
|
209
215
|
tbody tr {
|
|
210
216
|
transition: background var(--transition-fast);
|
|
211
217
|
|
|
218
|
+
/* The last row's line is a shadow, not a border. A shadow never adds to
|
|
219
|
+
the body's scroll height, and the body clips it at its own edge: with
|
|
220
|
+
space left under the rows it closes them, and where the rows reach the
|
|
221
|
+
body's border it is cut off instead of drawing a second line there.
|
|
222
|
+
Placed before the drop target, which wins on a last row. */
|
|
223
|
+
&:last-child td {
|
|
224
|
+
border-bottom: none;
|
|
225
|
+
box-shadow: 0 var(--border-1) 0 var(--color-border);
|
|
226
|
+
}
|
|
227
|
+
|
|
212
228
|
&:hover td {
|
|
213
229
|
background: var(--color-secondary);
|
|
214
230
|
}
|
|
@@ -589,55 +605,59 @@
|
|
|
589
605
|
flex-shrink: 0;
|
|
590
606
|
margin-top: auto;
|
|
591
607
|
width: 100%;
|
|
608
|
+
min-width: 0;
|
|
592
609
|
display: flex;
|
|
610
|
+
/* The count and the controls share a row while both fit; past that the
|
|
611
|
+
controls take a row of their own below. Nothing scrolls or clips. */
|
|
612
|
+
flex-wrap: wrap;
|
|
593
613
|
align-items: center;
|
|
594
614
|
justify-content: space-between;
|
|
595
|
-
gap: var(--spacing-8);
|
|
596
|
-
|
|
597
|
-
/* When the count + controls can't fit, the WHOLE bar scrolls horizontally as
|
|
598
|
-
one strip rather than wrapping or clipping. The outlet hides X overflow, so
|
|
599
|
-
letting a cluster shrink would just clip the nav buttons - scroll the bar
|
|
600
|
-
instead. `space-between` still spreads them when there IS room. The vertical
|
|
601
|
-
padding gives the buttons' focus rings room so `overflow-x: auto` (which also
|
|
602
|
-
clips the Y axis) doesn't crop them. */
|
|
603
|
-
overflow-x: auto;
|
|
604
|
-
scrollbar-width: thin;
|
|
605
|
-
scrollbar-color: var(--color-border) transparent;
|
|
606
|
-
padding-block: var(--spacing-2);
|
|
607
|
-
|
|
608
|
-
> * {
|
|
609
|
-
flex-shrink: 0;
|
|
610
|
-
}
|
|
615
|
+
column-gap: var(--spacing-8);
|
|
616
|
+
row-gap: var(--spacing-4);
|
|
611
617
|
}
|
|
612
618
|
|
|
613
619
|
@media (--sm) {
|
|
614
620
|
.pagination_bar {
|
|
615
|
-
gap: var(--spacing-5);
|
|
621
|
+
column-gap: var(--spacing-5);
|
|
616
622
|
}
|
|
617
623
|
}
|
|
618
624
|
|
|
619
|
-
.pagination_bar_loading {
|
|
620
|
-
flex-wrap: wrap;
|
|
621
|
-
row-gap: var(--spacing-4);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
625
|
.loading_select_skeleton {
|
|
625
626
|
border-radius: var(--radius-sm);
|
|
626
627
|
flex-shrink: 0;
|
|
627
628
|
}
|
|
628
629
|
|
|
630
|
+
/* The count, then what a page adds beside it, like the actions on ticked
|
|
631
|
+
rows. The block sizes from nothing and never below its count, so the
|
|
632
|
+
controls wrap only once the count alone and they cannot share a row; on
|
|
633
|
+
their row it takes what the controls leave, and its extra scrolls there. */
|
|
629
634
|
.pagination_left {
|
|
630
635
|
display: flex;
|
|
631
636
|
align-items: center;
|
|
632
637
|
gap: var(--spacing-8);
|
|
633
|
-
|
|
638
|
+
flex: 1 1 0;
|
|
639
|
+
min-width: min-content;
|
|
634
640
|
}
|
|
635
641
|
|
|
636
642
|
.pagination_left_loading {
|
|
637
643
|
overflow: hidden;
|
|
638
644
|
}
|
|
639
645
|
|
|
646
|
+
/* The extra slot, a ScrollRail: it takes what the count leaves on the row
|
|
647
|
+
and scrolls across on its own, with no scrollbar, so the bar never changes
|
|
648
|
+
height. Its items claim no width of the row (inline-size containment):
|
|
649
|
+
however many there are, the controls keep their place beside the count. */
|
|
650
|
+
.pagination_extra {
|
|
651
|
+
flex: 1 1 0;
|
|
652
|
+
contain: inline-size;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.pagination_extra_items {
|
|
656
|
+
gap: var(--spacing-4);
|
|
657
|
+
}
|
|
658
|
+
|
|
640
659
|
.pagination_count {
|
|
660
|
+
flex-shrink: 0;
|
|
641
661
|
font-size: var(--font-size-sm);
|
|
642
662
|
color: var(--color-muted-foreground);
|
|
643
663
|
white-space: nowrap;
|
|
@@ -650,14 +670,37 @@
|
|
|
650
670
|
|
|
651
671
|
.pagination_controls {
|
|
652
672
|
display: flex;
|
|
673
|
+
flex-wrap: wrap;
|
|
653
674
|
align-items: center;
|
|
654
|
-
|
|
675
|
+
justify-content: flex-end;
|
|
676
|
+
column-gap: var(--spacing-8);
|
|
677
|
+
row-gap: var(--spacing-4);
|
|
655
678
|
min-width: 0;
|
|
656
679
|
}
|
|
657
680
|
|
|
681
|
+
/* A phone gives the controls a row of their own: the page size at its start,
|
|
682
|
+
the pages at its end. The size keeps its label for screen readers only, and
|
|
683
|
+
the first/last buttons go: prev/next and the page number say as much. */
|
|
658
684
|
@media (--sm) {
|
|
659
685
|
.pagination_controls {
|
|
660
|
-
|
|
686
|
+
flex-basis: 100%;
|
|
687
|
+
column-gap: var(--spacing-5);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.page_size_wrap {
|
|
691
|
+
margin-inline-end: auto;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.page_size_label {
|
|
695
|
+
position: absolute;
|
|
696
|
+
width: 1px;
|
|
697
|
+
height: 1px;
|
|
698
|
+
overflow: hidden;
|
|
699
|
+
clip-path: inset(50%);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.page_nav_edge {
|
|
703
|
+
display: none;
|
|
661
704
|
}
|
|
662
705
|
}
|
|
663
706
|
|