@nexub/react 7.1.0 → 7.3.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/accessibility.json +1 -1
- package/dist/components/Accordion/Accordion.js +1 -1
- package/dist/components/AlertDialog/AlertDialog.d.ts +7 -1
- package/dist/components/AlertDialog/AlertDialog.js +8 -1
- package/dist/components/Avatar/Avatar.js +3 -1
- package/dist/components/Breadcrumb/Breadcrumb.js +3 -2
- package/dist/components/Card/Card.js +3 -1
- package/dist/components/Dialog/Dialog.d.ts +11 -1
- package/dist/components/Dialog/Dialog.js +8 -1
- package/dist/components/FileUpload/FileUpload.d.ts +8 -1
- package/dist/components/FileUpload/FileUpload.js +5 -3
- package/dist/components/MediaCard/MediaCard.js +4 -1
- package/dist/components/Motion/Collapse.d.ts +5 -4
- package/dist/components/Motion/Collapse.js +23 -53
- package/dist/components/Motion/motion.types.d.ts +2 -0
- package/dist/components/NavigationMenu/NavigationMenu.js +3 -1
- package/dist/components/NexubProvider/NexubProvider.d.ts +7 -1
- package/dist/components/NexubProvider/NexubProvider.js +3 -1
- package/dist/components/PasswordInput/PasswordInput.js +6 -1
- package/dist/components/Progress/Progress.js +1 -1
- package/dist/components/Tabs/Tabs.js +13 -3
- package/dist/components/ThemeProvider/ThemeProvider.js +2 -1
- package/dist/hooks/useClassScrollLock.d.ts +13 -0
- package/dist/hooks/useClassScrollLock.js +12 -0
- package/dist/hooks/useSlidingIndicator.d.ts +1 -1
- package/dist/hooks/useSlidingIndicator.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/styles.css +1 -1
- package/dist/utils/safeHref.d.ts +12 -0
- package/dist/utils/safeHref.js +24 -0
- package/dist/utils/viewTransition.d.ts +4 -2
- package/dist/utils/viewTransition.js +10 -2
- package/package.json +11 -6
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "7.
|
|
2
|
+
"version": "7.3.0",
|
|
3
3
|
"standard": "WCAG 2.2 Level AA",
|
|
4
4
|
"method": {
|
|
5
5
|
"automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark, and on open popups (menus, listboxes, dialogs), on every pull request; a new violation fails the build.",
|
|
@@ -33,7 +33,7 @@ const Trigger = React.forwardRef(
|
|
|
33
33
|
...props,
|
|
34
34
|
children: [
|
|
35
35
|
children,
|
|
36
|
-
/* @__PURE__ */ jsx(ChevronDown, { size: "sm", className: "shrink-0 transition-transform duration-
|
|
36
|
+
/* @__PURE__ */ jsx(ChevronDown, { size: "sm", className: "shrink-0 transition-transform duration-spatial-fast ease-spatial-fast", "aria-hidden": "true" })
|
|
37
37
|
]
|
|
38
38
|
}
|
|
39
39
|
) })
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as RadixAlertDialog from '@radix-ui/react-alert-dialog';
|
|
3
|
+
import { type ScrollLock } from '../../hooks/useClassScrollLock.js';
|
|
4
|
+
export interface AlertDialogRootProps extends RadixAlertDialog.AlertDialogProps {
|
|
5
|
+
/** See Dialog.Root `scrollLock`. @default 'style' (becomes 'class' in 8.0) */
|
|
6
|
+
scrollLock?: ScrollLock;
|
|
7
|
+
}
|
|
8
|
+
declare function Root({ scrollLock, ...props }: AlertDialogRootProps): React.JSX.Element;
|
|
3
9
|
export interface AlertDialogConfirmProps {
|
|
4
10
|
/** What the user must type, e.g. the resource name. Matching ignores outer spaces. */
|
|
5
11
|
text: string;
|
|
@@ -16,7 +22,7 @@ declare namespace Confirm {
|
|
|
16
22
|
var displayName: string;
|
|
17
23
|
}
|
|
18
24
|
export declare const AlertDialog: {
|
|
19
|
-
Root:
|
|
25
|
+
Root: typeof Root;
|
|
20
26
|
Trigger: React.ForwardRefExoticComponent<RadixAlertDialog.AlertDialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
21
27
|
Portal: React.FC<RadixAlertDialog.AlertDialogPortalProps>;
|
|
22
28
|
Overlay: React.ForwardRefExoticComponent<RadixAlertDialog.AlertDialogOverlayProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -7,12 +7,19 @@ import { overlaySurface, scrim } from "../../utils/surface.js";
|
|
|
7
7
|
import { buttonVariants } from "../Button/Button.js";
|
|
8
8
|
import { Input } from "../Input/Input.js";
|
|
9
9
|
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
10
|
+
import { ClassLockOverlay, ScrollLockContext } from "../../hooks/useClassScrollLock.js";
|
|
10
11
|
const ConfirmContext = React.createContext(null);
|
|
11
|
-
const
|
|
12
|
+
const ALERT_LOCK = { style: { scrollLock: "style", modal: true }, class: { scrollLock: "class", modal: true } };
|
|
13
|
+
function Root({ scrollLock = "style", ...props }) {
|
|
14
|
+
return /* @__PURE__ */ jsx(ScrollLockContext.Provider, { value: ALERT_LOCK[scrollLock], children: /* @__PURE__ */ jsx(RadixAlertDialog.Root, { ...props }) });
|
|
15
|
+
}
|
|
12
16
|
const Trigger = RadixAlertDialog.Trigger;
|
|
13
17
|
const Portal = RadixAlertDialog.Portal;
|
|
14
18
|
const Overlay = React.forwardRef(
|
|
15
19
|
function AlertDialogOverlay({ className, ...props }, ref) {
|
|
20
|
+
const { scrollLock } = React.useContext(ScrollLockContext);
|
|
21
|
+
if (scrollLock === "class")
|
|
22
|
+
return /* @__PURE__ */ jsx(ClassLockOverlay, { ref, className: cn("fixed inset-0 z-[var(--z-modal)] nx-motion", scrim, className) });
|
|
16
23
|
return /* @__PURE__ */ jsx(
|
|
17
24
|
RadixAlertDialog.Overlay,
|
|
18
25
|
{
|
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { safeSrc } from "../../utils/safeHref.js";
|
|
6
7
|
const avatarVariants = cva(
|
|
7
8
|
"relative inline-flex items-center justify-center shrink-0 rounded-full overflow-hidden bg-[var(--color-surface-container-high)] text-[var(--color-on-surface-muted)] font-medium select-none",
|
|
8
9
|
{
|
|
@@ -38,13 +39,14 @@ const Avatar = React.forwardRef(
|
|
|
38
39
|
({
|
|
39
40
|
className,
|
|
40
41
|
size = "md",
|
|
41
|
-
src,
|
|
42
|
+
src: srcProp,
|
|
42
43
|
name,
|
|
43
44
|
fallback,
|
|
44
45
|
badge,
|
|
45
46
|
badgeColor,
|
|
46
47
|
...props
|
|
47
48
|
}, ref) => {
|
|
49
|
+
const src = safeSrc(srcProp);
|
|
48
50
|
const [failedSrc, setFailedSrc] = React.useState();
|
|
49
51
|
const showImage = Boolean(src) && failedSrc !== src;
|
|
50
52
|
const initials = name ? getInitials(name) : null;
|
|
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { ChevronRight, MoreHorizontal } from "@nexub/icons";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { safeHref } from "../../utils/safeHref.js";
|
|
6
7
|
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
7
8
|
import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
|
|
8
9
|
const Root = React.forwardRef(
|
|
@@ -84,7 +85,7 @@ const Trail = React.forwardRef(({ items, maxItems = 4, ...props }, ref) => {
|
|
|
84
85
|
const collapsed = items.length > keep;
|
|
85
86
|
const hidden = collapsed ? items.slice(1, items.length - (keep - 1)) : [];
|
|
86
87
|
const shown = collapsed ? [items[0], ...items.slice(items.length - (keep - 1))] : items;
|
|
87
|
-
const crumb = (item, last) => last ? /* @__PURE__ */ jsx(Page, { children: item.label }) : /* @__PURE__ */ jsx(Link, { href: item.href, children: item.label });
|
|
88
|
+
const crumb = (item, last) => last ? /* @__PURE__ */ jsx(Page, { children: item.label }) : /* @__PURE__ */ jsx(Link, { href: safeHref(item.href), children: item.label });
|
|
88
89
|
return /* @__PURE__ */ jsx(Root, { ref, ...props, children: /* @__PURE__ */ jsx(List, { children: shown.map((item, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
89
90
|
i > 0 && /* @__PURE__ */ jsx(Separator, {}),
|
|
90
91
|
/* @__PURE__ */ jsx(Item, { children: crumb(item, i === shown.length - 1) }),
|
|
@@ -99,7 +100,7 @@ const Trail = React.forwardRef(({ items, maxItems = 4, ...props }, ref) => {
|
|
|
99
100
|
children: /* @__PURE__ */ jsx(MoreHorizontal, { size: "sm", "aria-hidden": "true" })
|
|
100
101
|
}
|
|
101
102
|
),
|
|
102
|
-
/* @__PURE__ */ jsx(DropdownMenu.Content, { align: "start", children: hidden.map((h, j) => /* @__PURE__ */ jsx(DropdownMenu.Item, { asChild: true, children: /* @__PURE__ */ jsx("a", { href: h.href, children: h.label }) }, j)) })
|
|
103
|
+
/* @__PURE__ */ jsx(DropdownMenu.Content, { align: "start", children: hidden.map((h, j) => /* @__PURE__ */ jsx(DropdownMenu.Item, { asChild: true, children: /* @__PURE__ */ jsx("a", { href: safeHref(h.href), children: h.label }) }, j)) })
|
|
103
104
|
] }) })
|
|
104
105
|
] })
|
|
105
106
|
] }, i)) }) });
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
5
|
import { cva } from "class-variance-authority";
|
|
6
6
|
import { cn } from "../../utils/cn.js";
|
|
7
|
+
import { safeHref } from "../../utils/safeHref.js";
|
|
7
8
|
import { glassSurfaceClass } from "../../utils/glass.js";
|
|
8
9
|
import { usePointerSheen } from "../../hooks/usePointerSheen.js";
|
|
9
10
|
const cardVariants = cva(
|
|
@@ -72,7 +73,7 @@ const Card = React.forwardRef(
|
|
|
72
73
|
);
|
|
73
74
|
Card.displayName = "Card";
|
|
74
75
|
const CardLink = React.forwardRef(
|
|
75
|
-
({ className, asChild = false, ...props }, ref) => {
|
|
76
|
+
({ className, asChild = false, href, ...props }, ref) => {
|
|
76
77
|
const Comp = asChild ? Slot : "a";
|
|
77
78
|
return /* @__PURE__ */ jsx(
|
|
78
79
|
Comp,
|
|
@@ -83,6 +84,7 @@ const CardLink = React.forwardRef(
|
|
|
83
84
|
"text-inherit no-underline outline-none after:absolute after:inset-0 after:rounded-[inherit] after:content-['']",
|
|
84
85
|
className
|
|
85
86
|
),
|
|
87
|
+
href: safeHref(href),
|
|
86
88
|
...props
|
|
87
89
|
}
|
|
88
90
|
);
|
|
@@ -2,12 +2,22 @@ import * as React from 'react';
|
|
|
2
2
|
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
3
3
|
import type { Size } from '../../utils/control.js';
|
|
4
4
|
import { type SurfaceValue } from '../../utils/glass.js';
|
|
5
|
+
import { type ScrollLock } from '../../hooks/useClassScrollLock.js';
|
|
6
|
+
export interface DialogRootProps extends RadixDialog.DialogProps {
|
|
7
|
+
/**
|
|
8
|
+
* `'class'` locks page scroll with a class on <html> instead of the <style>
|
|
9
|
+
* Radix injects, so the dialog works under a strict `style-src` with no nonce.
|
|
10
|
+
* @default 'style' (becomes 'class' in 8.0)
|
|
11
|
+
*/
|
|
12
|
+
scrollLock?: ScrollLock;
|
|
13
|
+
}
|
|
14
|
+
declare function Root({ scrollLock, modal, ...props }: DialogRootProps): React.JSX.Element;
|
|
5
15
|
interface ContentProps extends RadixDialog.DialogContentProps {
|
|
6
16
|
size?: Size | 'xl' | 'full';
|
|
7
17
|
surface?: SurfaceValue;
|
|
8
18
|
}
|
|
9
19
|
export declare const Dialog: {
|
|
10
|
-
Root:
|
|
20
|
+
Root: typeof Root;
|
|
11
21
|
Trigger: React.ForwardRefExoticComponent<RadixDialog.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
12
22
|
Portal: React.FC<RadixDialog.DialogPortalProps>;
|
|
13
23
|
Overlay: React.ForwardRefExoticComponent<RadixDialog.DialogOverlayProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -5,12 +5,19 @@ import * as RadixDialog from "@radix-ui/react-dialog";
|
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
6
|
import { overlaySurface, scrim } from "../../utils/surface.js";
|
|
7
7
|
import { glassSurfaceClass } from "../../utils/glass.js";
|
|
8
|
-
|
|
8
|
+
import { ClassLockOverlay, ScrollLockContext } from "../../hooks/useClassScrollLock.js";
|
|
9
|
+
function Root({ scrollLock = "style", modal = true, ...props }) {
|
|
10
|
+
const lock = React.useMemo(() => ({ scrollLock, modal }), [scrollLock, modal]);
|
|
11
|
+
return /* @__PURE__ */ jsx(ScrollLockContext.Provider, { value: lock, children: /* @__PURE__ */ jsx(RadixDialog.Root, { modal, ...props }) });
|
|
12
|
+
}
|
|
9
13
|
const Trigger = RadixDialog.Trigger;
|
|
10
14
|
const Portal = RadixDialog.Portal;
|
|
11
15
|
const Close = RadixDialog.Close;
|
|
12
16
|
const Overlay = React.forwardRef(
|
|
13
17
|
function DialogOverlay({ className, ...props }, ref) {
|
|
18
|
+
const { scrollLock, modal } = React.useContext(ScrollLockContext);
|
|
19
|
+
const cls = cn("fixed inset-0 z-[var(--z-modal)] nx-motion", scrim, className);
|
|
20
|
+
if (scrollLock === "class") return modal ? /* @__PURE__ */ jsx(ClassLockOverlay, { ref, className: cls }) : null;
|
|
14
21
|
return /* @__PURE__ */ jsx(
|
|
15
22
|
RadixDialog.Overlay,
|
|
16
23
|
{
|
|
@@ -9,7 +9,9 @@ export declare function formatFileSize(bytes: number, locale: string): string;
|
|
|
9
9
|
export declare function fileAccepted(file: File, accept?: string[]): boolean;
|
|
10
10
|
export interface FileRejection {
|
|
11
11
|
file: File;
|
|
12
|
-
reason: 'type' | 'size' | 'count';
|
|
12
|
+
reason: 'type' | 'size' | 'count' | 'custom';
|
|
13
|
+
/** The message returned by `validate` (reason `'custom'`). */
|
|
14
|
+
message?: string;
|
|
13
15
|
}
|
|
14
16
|
export interface FileUploadRootProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
17
|
accept?: string[];
|
|
@@ -19,6 +21,11 @@ export interface FileUploadRootProps extends React.HTMLAttributes<HTMLDivElement
|
|
|
19
21
|
onFilesChange?: (files: File[]) => void;
|
|
20
22
|
/** Files turned away by `accept`, `maxSize` or `maxFiles`, from the picker or a drop. */
|
|
21
23
|
onReject?: (rejections: FileRejection[]) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Your own check, run after `accept` and `maxSize`: return a message to reject
|
|
26
|
+
* the file. Client-side checks are a convenience; validate again on the server.
|
|
27
|
+
*/
|
|
28
|
+
validate?: (file: File) => string | null | undefined;
|
|
22
29
|
}
|
|
23
30
|
export interface FileUploadItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
|
24
31
|
index: number;
|
|
@@ -27,17 +27,19 @@ function useFileUpload() {
|
|
|
27
27
|
return ctx;
|
|
28
28
|
}
|
|
29
29
|
const Root = React.forwardRef(
|
|
30
|
-
({ className, accept, maxFiles, maxSize, onFilesChange, onReject, children, ...props }, ref) => {
|
|
30
|
+
({ className, accept, maxFiles, maxSize, onFilesChange, onReject, validate, children, ...props }, ref) => {
|
|
31
31
|
const [files, setFiles] = React.useState([]);
|
|
32
32
|
const [rejections, setRejections] = React.useState([]);
|
|
33
33
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
34
34
|
const addFiles = React.useCallback(
|
|
35
35
|
(newFiles) => {
|
|
36
36
|
const rejected = [];
|
|
37
|
+
let custom;
|
|
37
38
|
const next = [...files];
|
|
38
39
|
for (const file of newFiles) {
|
|
39
40
|
if (!fileAccepted(file, accept)) rejected.push({ file, reason: "type" });
|
|
40
41
|
else if (maxSize !== void 0 && file.size > maxSize) rejected.push({ file, reason: "size" });
|
|
42
|
+
else if (custom = validate?.(file) || null) rejected.push({ file, reason: "custom", message: custom });
|
|
41
43
|
else if (maxFiles !== void 0 && next.length >= maxFiles) rejected.push({ file, reason: "count" });
|
|
42
44
|
else next.push(file);
|
|
43
45
|
}
|
|
@@ -48,7 +50,7 @@ const Root = React.forwardRef(
|
|
|
48
50
|
}
|
|
49
51
|
if (rejected.length) onReject?.(rejected);
|
|
50
52
|
},
|
|
51
|
-
[files, accept, maxFiles, maxSize, onFilesChange, onReject]
|
|
53
|
+
[files, accept, maxFiles, maxSize, onFilesChange, onReject, validate]
|
|
52
54
|
);
|
|
53
55
|
const removeFile = React.useCallback(
|
|
54
56
|
(index) => {
|
|
@@ -126,7 +128,7 @@ const Dropzone = React.forwardRef(
|
|
|
126
128
|
}
|
|
127
129
|
)
|
|
128
130
|
] }),
|
|
129
|
-
/* @__PURE__ */ jsx("div", { id: errorId, role: "alert", className: "empty:hidden px-4 pb-3 text-start text-sm text-[var(--color-error)]", children: rejections.length > 0 && /* @__PURE__ */ jsx("ul", { children: rejections.map(({ file, reason }, i) => /* @__PURE__ */ jsx("li", { children: reason === "type" ? messages.fileTypeRejected(file.name) : reason === "size" ? messages.fileTooLarge(file.name, formatFileSize(maxSize ?? 0, locale)) : messages.tooManyFiles(maxFiles ?? 0) }, `${file.name}-${i}`)) }) })
|
|
131
|
+
/* @__PURE__ */ jsx("div", { id: errorId, role: "alert", className: "empty:hidden px-4 pb-3 text-start text-sm text-[var(--color-error)]", children: rejections.length > 0 && /* @__PURE__ */ jsx("ul", { children: rejections.map(({ file, reason, message }, i) => /* @__PURE__ */ jsx("li", { children: reason === "custom" ? message : reason === "type" ? messages.fileTypeRejected(file.name) : reason === "size" ? messages.fileTooLarge(file.name, formatFileSize(maxSize ?? 0, locale)) : messages.tooManyFiles(maxFiles ?? 0) }, `${file.name}-${i}`)) }) })
|
|
130
132
|
]
|
|
131
133
|
}
|
|
132
134
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../../utils/cn.js";
|
|
4
|
+
import { safeHref, safeSrc } from "../../utils/safeHref.js";
|
|
4
5
|
import { glassSurfaceClass } from "../../utils/glass.js";
|
|
5
6
|
const ratioClass = {
|
|
6
7
|
"16/9": "aspect-video",
|
|
@@ -9,7 +10,9 @@ const ratioClass = {
|
|
|
9
10
|
"3/4": "aspect-[3/4]"
|
|
10
11
|
};
|
|
11
12
|
const MediaCard = React.forwardRef(
|
|
12
|
-
({ src, alt, ratio = "16/9", title, description, eyebrow, href, className, children, ...props }, ref) => {
|
|
13
|
+
({ src: srcProp, alt, ratio = "16/9", title, description, eyebrow, href: hrefProp, className, children, ...props }, ref) => {
|
|
14
|
+
const src = safeSrc(srcProp);
|
|
15
|
+
const href = safeHref(hrefProp);
|
|
13
16
|
const hasCaption = title != null || description != null || eyebrow != null || children != null;
|
|
14
17
|
return /* @__PURE__ */ jsxs(
|
|
15
18
|
"div",
|
|
@@ -4,6 +4,7 @@ export interface CollapseProps extends Omit<MotionBaseProps, 'easing'>, React.HT
|
|
|
4
4
|
/**
|
|
5
5
|
* Legacy: set it to use `--duration-*` + `--ease-{value}` instead of the
|
|
6
6
|
* spatial spring for the height transition.
|
|
7
|
+
* @deprecated since 7.3, removed in 8.0 (see MotionBaseProps.easing).
|
|
7
8
|
*/
|
|
8
9
|
easing?: MotionBaseProps['easing'];
|
|
9
10
|
/**
|
|
@@ -15,10 +16,10 @@ export interface CollapseProps extends Omit<MotionBaseProps, 'easing'>, React.HT
|
|
|
15
16
|
as?: React.ElementType;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
|
-
* Collapse —
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* Collapse — reveals / hides content on the spatial spring by animating a
|
|
20
|
+
* one-row grid from `0fr` to `1fr`, so the browser sizes it: no height is
|
|
21
|
+
* measured in JS and content that grows while open just grows. Reduced motion
|
|
22
|
+
* snaps instead of animating.
|
|
22
23
|
*
|
|
23
24
|
* ```tsx
|
|
24
25
|
* <Collapse in={isOpen}>
|
|
@@ -13,63 +13,30 @@ const Collapse = React.forwardRef(function Collapse2({
|
|
|
13
13
|
as: Tag = "div",
|
|
14
14
|
...rest
|
|
15
15
|
}, ref) {
|
|
16
|
-
const innerRef = React.useRef(null);
|
|
17
16
|
const reduced = usePrefersReducedMotion();
|
|
18
17
|
const legacy = easing !== void 0;
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const [overflow, setOverflow] = React.useState(
|
|
22
|
-
visible ? "visible" : "hidden"
|
|
23
|
-
);
|
|
18
|
+
const instant = duration === "instant" || reduced;
|
|
19
|
+
const settleMs = instant ? 0 : legacy ? DURATION_MS[duration] : SPRING_MS.spatial[SPRING_SPEED[duration]];
|
|
24
20
|
const [mounted, setMounted] = React.useState(unmountOnExit ? visible : true);
|
|
21
|
+
const [expanded, setExpanded] = React.useState(visible);
|
|
22
|
+
const [settled, setSettled] = React.useState(true);
|
|
25
23
|
const prevVisible = React.useRef(visible);
|
|
26
24
|
React.useEffect(() => {
|
|
27
|
-
|
|
25
|
+
if (prevVisible.current === visible) return;
|
|
28
26
|
prevVisible.current = visible;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
setMounted(true);
|
|
34
|
-
raf1 = requestAnimationFrame(() => {
|
|
35
|
-
const el = innerRef.current;
|
|
36
|
-
if (el) {
|
|
37
|
-
setHeight(el.scrollHeight);
|
|
38
|
-
setOverflow("hidden");
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
} else {
|
|
42
|
-
const el = innerRef.current;
|
|
43
|
-
if (el) {
|
|
44
|
-
setHeight(el.scrollHeight);
|
|
45
|
-
setOverflow("hidden");
|
|
46
|
-
raf1 = requestAnimationFrame(() => {
|
|
47
|
-
raf2 = requestAnimationFrame(() => {
|
|
48
|
-
setHeight(typeof collapsedHeight === "number" ? collapsedHeight : 0);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
27
|
+
setSettled(false);
|
|
28
|
+
if (!visible) {
|
|
29
|
+
setExpanded(false);
|
|
30
|
+
return;
|
|
52
31
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}, [visible, collapsedHeight]);
|
|
32
|
+
setMounted(true);
|
|
33
|
+
const raf = requestAnimationFrame(() => setExpanded(true));
|
|
34
|
+
return () => cancelAnimationFrame(raf);
|
|
35
|
+
}, [visible]);
|
|
58
36
|
const finish = React.useCallback(() => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
setOverflow("visible");
|
|
62
|
-
} else if (unmountOnExit) {
|
|
63
|
-
setMounted(false);
|
|
64
|
-
}
|
|
37
|
+
setSettled(true);
|
|
38
|
+
if (!visible && unmountOnExit) setMounted(false);
|
|
65
39
|
}, [visible, unmountOnExit]);
|
|
66
|
-
const handleTransitionEnd = React.useCallback(
|
|
67
|
-
(e) => {
|
|
68
|
-
if (e.target !== e.currentTarget || e.propertyName !== "max-height") return;
|
|
69
|
-
finish();
|
|
70
|
-
},
|
|
71
|
-
[finish]
|
|
72
|
-
);
|
|
73
40
|
const firstRender = React.useRef(true);
|
|
74
41
|
React.useEffect(() => {
|
|
75
42
|
if (firstRender.current) {
|
|
@@ -80,20 +47,23 @@ const Collapse = React.forwardRef(function Collapse2({
|
|
|
80
47
|
return () => clearTimeout(t);
|
|
81
48
|
}, [visible, settleMs, finish]);
|
|
82
49
|
if (!mounted) return null;
|
|
83
|
-
const
|
|
50
|
+
const overflow = expanded && settled ? "visible" : "hidden";
|
|
84
51
|
return /* @__PURE__ */ jsx(
|
|
85
52
|
Tag,
|
|
86
53
|
{
|
|
87
54
|
ref,
|
|
88
55
|
style: {
|
|
89
|
-
|
|
56
|
+
display: "grid",
|
|
57
|
+
gridTemplateRows: expanded ? "1fr" : "0fr",
|
|
90
58
|
overflow,
|
|
91
|
-
transition:
|
|
59
|
+
transition: instant ? "none" : legacy ? `grid-template-rows var(--duration-${duration}) var(--ease-${easing})` : spring("grid-template-rows", "spatial", SPRING_SPEED[duration]),
|
|
92
60
|
...style
|
|
93
61
|
},
|
|
94
|
-
onTransitionEnd:
|
|
62
|
+
onTransitionEnd: (e) => {
|
|
63
|
+
if (e.target === e.currentTarget && e.propertyName === "grid-template-rows") finish();
|
|
64
|
+
},
|
|
95
65
|
...rest,
|
|
96
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
66
|
+
children: /* @__PURE__ */ jsx("div", { style: { minHeight: collapsedHeight, overflow }, children })
|
|
97
67
|
}
|
|
98
68
|
);
|
|
99
69
|
});
|
|
@@ -12,6 +12,8 @@ export interface MotionBaseProps {
|
|
|
12
12
|
/**
|
|
13
13
|
* Legacy: set it to use the old `--duration-*` + `--ease-{value}` tokens
|
|
14
14
|
* instead of springs. Omit it to ride the spring tokens.
|
|
15
|
+
* @deprecated since 7.3: the `--duration-*` / `--ease-*` tokens (tokens/motion.json) are
|
|
16
|
+
* retired in favour of the spring tokens. Removed in 8.0.
|
|
15
17
|
*/
|
|
16
18
|
easing?: MotionEasing;
|
|
17
19
|
/**
|
|
@@ -50,7 +50,7 @@ const Trigger = React.forwardRef(
|
|
|
50
50
|
ChevronDown,
|
|
51
51
|
{
|
|
52
52
|
size: "xs",
|
|
53
|
-
className: "relative ms-1 transition duration-
|
|
53
|
+
className: "relative ms-1 transition-transform duration-spatial-fast ease-spatial-fast group-data-[state=open]:rotate-180",
|
|
54
54
|
"aria-hidden": "true"
|
|
55
55
|
}
|
|
56
56
|
)
|
|
@@ -96,6 +96,8 @@ const Viewport = React.forwardRef(
|
|
|
96
96
|
"nx-motion nx-pop origin-top relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden",
|
|
97
97
|
floatingSurface,
|
|
98
98
|
"md:w-[var(--radix-navigation-menu-viewport-width)]",
|
|
99
|
+
// Resize between items on the spatial spring instead of snapping.
|
|
100
|
+
"transition-[width,height] duration-spatial-default ease-spatial-default motion-reduce:transition-none",
|
|
99
101
|
className
|
|
100
102
|
),
|
|
101
103
|
...props
|
|
@@ -14,6 +14,12 @@ export interface NexubProviderProps {
|
|
|
14
14
|
dateLocale?: Locale;
|
|
15
15
|
/** Overrides for the default English strings. */
|
|
16
16
|
messages?: Partial<NexubMessages>;
|
|
17
|
+
/**
|
|
18
|
+
* CSP nonce for the `<style>` tags Radix overlays inject at runtime (scroll
|
|
19
|
+
* lock, scrollbar gap). Pass the same per-request nonce as your
|
|
20
|
+
* `style-src-elem` so they work without `'unsafe-inline'`.
|
|
21
|
+
*/
|
|
22
|
+
nonce?: string;
|
|
17
23
|
children?: React.ReactNode;
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
@@ -21,7 +27,7 @@ export interface NexubProviderProps {
|
|
|
21
27
|
* It does not set `dir`/`lang` on <html> — the app does that (as it renders
|
|
22
28
|
* <html>), so the page and CSS `rtl:` variants agree with this provider.
|
|
23
29
|
*/
|
|
24
|
-
export declare function NexubProvider({ locale, dir, dateLocale, messages, children }: NexubProviderProps): React.JSX.Element;
|
|
30
|
+
export declare function NexubProvider({ locale, dir, dateLocale, messages, nonce, children }: NexubProviderProps): React.JSX.Element;
|
|
25
31
|
/** The active strings: defaults merged with the nearest NexubProvider's overrides. */
|
|
26
32
|
export declare function useNexubMessages(): NexubMessages;
|
|
27
33
|
/** The active locale (default 'en'), direction and optional date-fns Locale. */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { DirectionProvider } from "@radix-ui/react-direction";
|
|
5
|
+
import { setNonce } from "get-nonce";
|
|
5
6
|
import { defaultMessages, directionOf } from "@nexub/core";
|
|
6
7
|
const NexubContext = React.createContext({
|
|
7
8
|
locale: void 0,
|
|
@@ -9,7 +10,8 @@ const NexubContext = React.createContext({
|
|
|
9
10
|
dateLocale: void 0,
|
|
10
11
|
messages: defaultMessages
|
|
11
12
|
});
|
|
12
|
-
function NexubProvider({ locale = "en", dir, dateLocale, messages, children }) {
|
|
13
|
+
function NexubProvider({ locale = "en", dir, dateLocale, messages, nonce, children }) {
|
|
14
|
+
if (nonce) setNonce(nonce);
|
|
13
15
|
const resolvedDir = dir ?? directionOf(locale);
|
|
14
16
|
const value = React.useMemo(
|
|
15
17
|
() => ({ locale, dir: resolvedDir, dateLocale, messages: { ...defaultMessages, ...messages } }),
|
|
@@ -14,12 +14,16 @@ const PasswordInput = React.forwardRef(
|
|
|
14
14
|
onChange: onVisibleChange
|
|
15
15
|
});
|
|
16
16
|
const messages = useNexubMessages();
|
|
17
|
+
const autoId = React.useId();
|
|
18
|
+
const inputId = props.id ?? autoId;
|
|
17
19
|
const toggleButton = /* @__PURE__ */ jsx(
|
|
18
20
|
"button",
|
|
19
21
|
{
|
|
20
22
|
type: "button",
|
|
21
23
|
onClick: () => setShowPassword(!showPassword),
|
|
22
|
-
"aria-label":
|
|
24
|
+
"aria-label": messages.showPassword,
|
|
25
|
+
"aria-pressed": showPassword,
|
|
26
|
+
"aria-controls": inputId,
|
|
23
27
|
className: cn(
|
|
24
28
|
"shrink-0 rounded-full text-[var(--color-on-surface-muted)] hover:text-[var(--color-on-surface)] transition-colors"
|
|
25
29
|
),
|
|
@@ -30,6 +34,7 @@ const PasswordInput = React.forwardRef(
|
|
|
30
34
|
Input,
|
|
31
35
|
{
|
|
32
36
|
ref,
|
|
37
|
+
id: inputId,
|
|
33
38
|
autoComplete: "current-password",
|
|
34
39
|
...props,
|
|
35
40
|
type: showPassword ? "text" : "password",
|
|
@@ -20,7 +20,7 @@ const progressTrackVariants = cva(
|
|
|
20
20
|
}
|
|
21
21
|
);
|
|
22
22
|
const progressFillVariants = cva(
|
|
23
|
-
"h-full rounded-full transition-all duration-
|
|
23
|
+
"h-full rounded-full transition-all duration-spatial-default ease-spatial-default",
|
|
24
24
|
{
|
|
25
25
|
variants: {
|
|
26
26
|
tone: {
|
|
@@ -6,7 +6,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
6
6
|
import { useMergedRefs } from "../../hooks/useMergedRefs.js";
|
|
7
7
|
import { useSlidingIndicator } from "../../hooks/useSlidingIndicator.js";
|
|
8
8
|
const Root = React.forwardRef(
|
|
9
|
-
({ className, ...props }, ref) => /* @__PURE__ */ jsx(RadixTabs.Root, { ref, className: cn("flex flex-col gap-3", className), ...props })
|
|
9
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx(RadixTabs.Root, { ref, className: cn("flex flex-col gap-3 data-[orientation=vertical]:flex-row", className), ...props })
|
|
10
10
|
);
|
|
11
11
|
Root.displayName = "Tabs.Root";
|
|
12
12
|
const List = React.forwardRef(
|
|
@@ -18,8 +18,12 @@ const List = React.forwardRef(
|
|
|
18
18
|
{
|
|
19
19
|
ref: useMergedRefs(ref, forwardedRef),
|
|
20
20
|
className: cn(
|
|
21
|
-
"relative flex w-full items-stretch justify-start overflow-x-auto",
|
|
21
|
+
"group/tabs relative flex w-full items-stretch justify-start overflow-x-auto",
|
|
22
22
|
"border-b border-[var(--color-outline-variant)]",
|
|
23
|
+
// Vertical: a column with the rule on the inline-end side.
|
|
24
|
+
"data-[orientation=vertical]:w-auto data-[orientation=vertical]:shrink-0 data-[orientation=vertical]:flex-col",
|
|
25
|
+
"data-[orientation=vertical]:overflow-x-visible data-[orientation=vertical]:overflow-y-auto",
|
|
26
|
+
"data-[orientation=vertical]:border-b-0 data-[orientation=vertical]:border-e",
|
|
23
27
|
className
|
|
24
28
|
),
|
|
25
29
|
...props,
|
|
@@ -32,7 +36,10 @@ const List = React.forwardRef(
|
|
|
32
36
|
className: cn(
|
|
33
37
|
"pointer-events-none absolute bottom-0 left-0 h-[3px] rounded-t-full bg-[var(--color-primary)]",
|
|
34
38
|
"w-[var(--nx-ind-w,0px)] translate-x-[var(--nx-ind-x,0px)] opacity-[var(--nx-ind-o,0)]",
|
|
35
|
-
"
|
|
39
|
+
"group-data-[orientation=vertical]/tabs:top-0 group-data-[orientation=vertical]/tabs:bottom-auto group-data-[orientation=vertical]/tabs:left-auto group-data-[orientation=vertical]/tabs:end-0",
|
|
40
|
+
"group-data-[orientation=vertical]/tabs:h-[var(--nx-ind-h,0px)] group-data-[orientation=vertical]/tabs:w-[3px] group-data-[orientation=vertical]/tabs:rounded-none group-data-[orientation=vertical]/tabs:rounded-s-full",
|
|
41
|
+
"group-data-[orientation=vertical]/tabs:translate-x-0 group-data-[orientation=vertical]/tabs:translate-y-[var(--nx-ind-y,0px)]",
|
|
42
|
+
"[[data-nx-indicator=ready]>&]:transition-[transform,width,height] [[data-nx-indicator=ready]>&]:duration-spatial-default [[data-nx-indicator=ready]>&]:ease-spatial-default"
|
|
36
43
|
)
|
|
37
44
|
}
|
|
38
45
|
)
|
|
@@ -52,6 +59,7 @@ const Trigger = React.forwardRef(
|
|
|
52
59
|
"nx-state-layer relative inline-flex h-control-md items-center justify-center whitespace-nowrap rounded-t-[var(--shape-field)] px-4 text-label-lg select-none",
|
|
53
60
|
"text-[var(--color-on-surface-variant)] transition-colors duration-effects-fast ease-effects-fast",
|
|
54
61
|
"data-[state=active]:text-[var(--color-primary)]",
|
|
62
|
+
"group-data-[orientation=vertical]/tabs:justify-start group-data-[orientation=vertical]/tabs:rounded-none group-data-[orientation=vertical]/tabs:rounded-s-[var(--shape-field)]",
|
|
55
63
|
"disabled:pointer-events-none disabled:text-[var(--color-text-disabled)]",
|
|
56
64
|
className
|
|
57
65
|
),
|
|
@@ -66,6 +74,8 @@ const Content = React.forwardRef(
|
|
|
66
74
|
{
|
|
67
75
|
ref,
|
|
68
76
|
className: cn(
|
|
77
|
+
// Crossfade in on the effects spring (@starting-style, @nexub/themes/base).
|
|
78
|
+
"nx-tab-panel",
|
|
69
79
|
className,
|
|
70
80
|
// Radix hides inactive panels with the `hidden` attribute, which any
|
|
71
81
|
// display class (`flex`, `md:grid`…) overrides. !important always wins.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
5
|
+
const PRODUCTS = ["leap", "bookub", "payub", "founderub"];
|
|
5
6
|
const GLASS_MODES = ["clear", "balanced", "tinted", "off"];
|
|
6
7
|
const ThemeContext = React.createContext(void 0);
|
|
7
8
|
ThemeContext.displayName = "ThemeContext";
|
|
@@ -165,7 +166,7 @@ function useTheme() {
|
|
|
165
166
|
const jsString = (s) => JSON.stringify(s).replace(/</g, "\\u003c");
|
|
166
167
|
function themeScriptSource(storageKey, productStorageKey, glassStorageKey = "nexub-glass", contrastStorageKey) {
|
|
167
168
|
const contrast = contrastStorageKey ? `var c=localStorage.getItem(${jsString(contrastStorageKey)});if(['standard','medium','high'].indexOf(c)>-1)d.setAttribute('data-contrast',c);` : "";
|
|
168
|
-
const product = productStorageKey ? `var b=localStorage.getItem(${jsString(productStorageKey)});if(b
|
|
169
|
+
const product = productStorageKey ? `var b=localStorage.getItem(${jsString(productStorageKey)});if(${JSON.stringify(PRODUCTS)}.indexOf(b)>-1)d.setAttribute('data-product',b);` : "";
|
|
169
170
|
const glass = `var g=localStorage.getItem(${jsString(glassStorageKey)});if(${JSON.stringify(GLASS_MODES)}.indexOf(g)>-1)d.setAttribute('data-glass',g);`;
|
|
170
171
|
return `try{var d=document.documentElement,t=localStorage.getItem(${jsString(storageKey)});if(t==='light'||t==='dark')d.setAttribute('data-theme',t);${product}${glass}${contrast}}catch(e){}`;
|
|
171
172
|
}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
1
2
|
/**
|
|
2
3
|
* CSP-safe page scroll lock: toggles `nexub-scroll-lock` (from @nexub/themes/base)
|
|
3
4
|
* on <html> instead of injecting a <style> tag. On while mounted; counted, so
|
|
4
5
|
* nested overlays (a Sheet and a CommandPalette) share one lock.
|
|
5
6
|
*/
|
|
6
7
|
export declare function useClassScrollLock(): void;
|
|
8
|
+
export type ScrollLock = 'style' | 'class';
|
|
9
|
+
/**
|
|
10
|
+
* Which scroll lock a modal Root asked for. `'style'` (7.x default) is Radix's
|
|
11
|
+
* react-remove-scroll, which injects a <style> (needs a CSP nonce, see
|
|
12
|
+
* NexubProvider `nonce`); `'class'` is the class lock above, no <style> at all.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ScrollLockContext: React.Context<{
|
|
15
|
+
scrollLock: ScrollLock;
|
|
16
|
+
modal: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
/** Plain overlay for scrollLock="class": Radix's RemoveScroll lives in its Overlay. */
|
|
19
|
+
export declare const ClassLockOverlay: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -13,6 +13,18 @@ function useClassScrollLock() {
|
|
|
13
13
|
};
|
|
14
14
|
}, []);
|
|
15
15
|
}
|
|
16
|
+
const ScrollLockContext = React.createContext({
|
|
17
|
+
scrollLock: "style",
|
|
18
|
+
modal: true
|
|
19
|
+
});
|
|
20
|
+
const ClassLockOverlay = React.forwardRef(
|
|
21
|
+
function ClassLockOverlay2(props, ref) {
|
|
22
|
+
useClassScrollLock();
|
|
23
|
+
return React.createElement("div", { ref, "data-state": "open", ...props });
|
|
24
|
+
}
|
|
25
|
+
);
|
|
16
26
|
export {
|
|
27
|
+
ClassLockOverlay,
|
|
28
|
+
ScrollLockContext,
|
|
17
29
|
useClassScrollLock
|
|
18
30
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* One indicator that slides to the active item (SegmentedControl thumb, Tabs
|
|
4
|
-
* underline). Writes --nx-ind-x / -w / -o (opacity) on the (position: relative) root;
|
|
4
|
+
* underline). Writes --nx-ind-x / -w / -y / -h / -o (opacity) on the (position: relative) root;
|
|
5
5
|
* CSS transitions do the motion. Sets data-nx-indicator="ready" one frame after
|
|
6
6
|
* the first measure so the indicator doesn't animate in on mount.
|
|
7
7
|
*/
|