aplance-ui 1.3.0 → 1.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/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-context.ts +33 -29
- package/registry/components/primitives/popover/popover.module.css +4 -2
- package/registry/components/primitives/popover/popover.tsx +150 -37
- 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/copy-button/copy-button.tsx +5 -0
- package/registry/components/ui/data-table/data-table.module.css +786 -710
- package/registry/components/ui/data-table/data-table.tsx +1238 -1211
- package/registry/components/ui/data-table/filter-modal.tsx +86 -0
- 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 +19 -1
- package/registry/components/ui/data-table/useDataTable.tsx +2 -2
- package/registry/components/ui/data-table/utils.ts +30 -4
- 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 +118 -76
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +552 -484
- package/registry/components/ui/input-fields/date-picker/date-picker.tsx +936 -933
- package/registry/components/ui/input-fields/date-picker/types.ts +2 -0
- package/registry/components/ui/input-fields/emoji-picker/emoji-picker.tsx +8 -0
- 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/scroll-rail/scroll-rail.module.css +104 -0
- package/registry/components/ui/scroll-rail/scroll-rail.tsx +159 -0
- package/registry/components/ui/tabs/tabs.module.css +8 -12
- package/registry/components/ui/tabs/tabs.tsx +94 -50
- package/registry/components/ui/tooltip/tooltip.module.css +8 -3
- package/registry/components/ui/tooltip/tooltip.tsx +16 -3
- package/registry/hooks/useMediaQuery.ts +38 -0
- package/registry/lib/string.ts +3 -3
- package/registry/registry.json +54 -4
|
@@ -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";
|
|
@@ -18,6 +18,9 @@ export type CopyButtonProps = {
|
|
|
18
18
|
layout?: ButtonLayout;
|
|
19
19
|
preview?: CopyPreview;
|
|
20
20
|
previewMaxLength?: number;
|
|
21
|
+
/** Called with the value after a successful copy (e.g. to toast, or clear a
|
|
22
|
+
* one-time reveal). Not called if the copy fails. */
|
|
23
|
+
onCopy?: (value: string) => void;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
const maskToken = (raw: string): string => {
|
|
@@ -44,6 +47,7 @@ export function CopyButton({
|
|
|
44
47
|
layout,
|
|
45
48
|
preview = "none",
|
|
46
49
|
previewMaxLength = 24,
|
|
50
|
+
onCopy,
|
|
47
51
|
}: CopyButtonProps) {
|
|
48
52
|
const [copied, setCopied] = useState(false);
|
|
49
53
|
const timeoutRef = useRef<number | null>(null);
|
|
@@ -82,6 +86,7 @@ export function CopyButton({
|
|
|
82
86
|
const success = await copyWithFallback(value);
|
|
83
87
|
if (!success) return;
|
|
84
88
|
|
|
89
|
+
onCopy?.(value);
|
|
85
90
|
setCopied(true);
|
|
86
91
|
if (timeoutRef.current !== null) {
|
|
87
92
|
window.clearTimeout(timeoutRef.current);
|