@mohasinac/appkit 2.6.0 → 2.6.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.
|
@@ -128,10 +128,12 @@ export function createRouteHandler(options) {
|
|
|
128
128
|
return response;
|
|
129
129
|
}
|
|
130
130
|
catch (err) {
|
|
131
|
-
|
|
132
|
-
const status = typeof
|
|
133
|
-
?
|
|
134
|
-
:
|
|
131
|
+
const e = err;
|
|
132
|
+
const status = typeof e?.statusCode === "number"
|
|
133
|
+
? e.statusCode
|
|
134
|
+
: typeof e?.status === "number"
|
|
135
|
+
? e.status
|
|
136
|
+
: 500;
|
|
135
137
|
const message = err instanceof Error ? err.message : "Internal server error";
|
|
136
138
|
// 401/403 are expected for protected routes and should not be error-noisy.
|
|
137
139
|
if (status >= 500) {
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
|
+
import React, { useCallback } from "react";
|
|
3
4
|
import { twMerge } from "tailwind-merge";
|
|
4
5
|
import { Loader2 } from "lucide-react";
|
|
6
|
+
function spawnRipple(host, clientX, clientY) {
|
|
7
|
+
const rect = host.getBoundingClientRect();
|
|
8
|
+
const size = Math.max(rect.width, rect.height);
|
|
9
|
+
const ripple = document.createElement("span");
|
|
10
|
+
ripple.className = "appkit-button__ripple";
|
|
11
|
+
ripple.style.width = ripple.style.height = `${size}px`;
|
|
12
|
+
ripple.style.left = `${clientX - rect.left - size / 2}px`;
|
|
13
|
+
ripple.style.top = `${clientY - rect.top - size / 2}px`;
|
|
14
|
+
host.appendChild(ripple);
|
|
15
|
+
ripple.addEventListener("animationend", () => ripple.remove(), { once: true });
|
|
16
|
+
}
|
|
5
17
|
/**
|
|
6
18
|
* Button — versatile button with multiple variants, sizes, and loading state.
|
|
7
19
|
*
|
|
@@ -27,6 +39,13 @@ const UI_BUTTON = {
|
|
|
27
39
|
};
|
|
28
40
|
export function Button({ variant = "primary", size = "md", className = "", isLoading = false, disabled, children, asChild = false, ...props }) {
|
|
29
41
|
const classes = twMerge(UI_BUTTON.base, UI_BUTTON.variants[variant], UI_BUTTON.sizes[size], className);
|
|
42
|
+
const userOnClick = props.onClick;
|
|
43
|
+
const handleClick = useCallback((event) => {
|
|
44
|
+
if (!disabled && !isLoading) {
|
|
45
|
+
spawnRipple(event.currentTarget, event.clientX, event.clientY);
|
|
46
|
+
}
|
|
47
|
+
userOnClick?.(event);
|
|
48
|
+
}, [disabled, isLoading, userOnClick]);
|
|
30
49
|
if (asChild && React.isValidElement(children)) {
|
|
31
50
|
const child = children;
|
|
32
51
|
return React.cloneElement(child, {
|
|
@@ -35,5 +54,5 @@ export function Button({ variant = "primary", size = "md", className = "", isLoa
|
|
|
35
54
|
...(disabled ? { "aria-disabled": true } : {}),
|
|
36
55
|
});
|
|
37
56
|
}
|
|
38
|
-
return (_jsxs("button", { className: classes, disabled: disabled || isLoading, "aria-busy": isLoading || undefined, ...props, children: [isLoading && (_jsx(Loader2, { className: "appkit-button__spinner", "aria-hidden": "true" })), isLoading ? (_jsx("span", { className: "appkit-button__content appkit-button__content--loading", children: children })) : (_jsx("span", { className: "appkit-button__content", children: children }))] }));
|
|
57
|
+
return (_jsxs("button", { className: classes, disabled: disabled || isLoading, "aria-busy": isLoading || undefined, ...props, onClick: handleClick, children: [isLoading && (_jsx(Loader2, { className: "appkit-button__spinner", "aria-hidden": "true" })), isLoading ? (_jsx("span", { className: "appkit-button__content appkit-button__content--loading", children: children })) : (_jsx("span", { className: "appkit-button__content", children: children }))] }));
|
|
39
58
|
}
|
|
@@ -11,6 +11,40 @@
|
|
|
11
11
|
transition: all 0.2s ease;
|
|
12
12
|
cursor: pointer;
|
|
13
13
|
user-select: none;
|
|
14
|
+
position: relative;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
isolation: isolate;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.appkit-button__ripple {
|
|
20
|
+
position: absolute;
|
|
21
|
+
border-radius: 9999px;
|
|
22
|
+
background: currentColor;
|
|
23
|
+
opacity: 0.28;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
transform: scale(0);
|
|
26
|
+
animation: appkit-button-ripple 520ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
|
|
27
|
+
z-index: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.appkit-button__content,
|
|
31
|
+
.appkit-button__spinner {
|
|
32
|
+
position: relative;
|
|
33
|
+
z-index: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@keyframes appkit-button-ripple {
|
|
37
|
+
to {
|
|
38
|
+
transform: scale(2.4);
|
|
39
|
+
opacity: 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@media (prefers-reduced-motion: reduce) {
|
|
44
|
+
.appkit-button__ripple {
|
|
45
|
+
animation-duration: 1ms;
|
|
46
|
+
opacity: 0;
|
|
47
|
+
}
|
|
14
48
|
}
|
|
15
49
|
|
|
16
50
|
.appkit-button:focus-visible {
|