apex-ui-kit 2.12.1 → 2.13.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/ReadMe.md +17 -5
- package/dist/ApexUI-Kit/templates/FloatingNavbar/FloatingNavbar.jsx +36 -11
- package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +4 -6
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +5 -8
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +158 -153
- package/dist/ApexUI-Kit/templates/ThemeSwitcher/ThemeSwitcher.jsx +24 -8
- package/dist/cli/index.js +86 -1
- package/dist/skill/apexui/SKILL.md +91 -0
- package/dist/skill/apexui/references/checklist.md +37 -0
- package/dist/skill/apexui/references/components.md +174 -0
- package/dist/skill/apexui/references/design-system.md +104 -0
- package/dist/skill/apexui/references/recipes.md +68 -0
- package/dist/skill/apexui/references/responsive-mobile.md +50 -0
- package/dist/skill/apexui/references/security.md +81 -0
- package/package.json +7 -4
- package/dist/ApexUI-Kit/templates/CardSwap3D/CardSwap3D.jsx +0 -104
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Security (front end)
|
|
2
|
+
|
|
3
|
+
No app is "100% secure", but these rules close the holes that real front ends ship with. Treat every item as required unless it clearly does not apply, and say so when you skip one.
|
|
4
|
+
|
|
5
|
+
## 1. Secrets
|
|
6
|
+
- Anything in the browser bundle is public. That includes every `VITE_*`, `NEXT_PUBLIC_*` and `REACT_APP_*` variable.
|
|
7
|
+
- Never put API keys with write or billing access, database URLs, JWT secrets, SMTP passwords or service-account files in front-end code. Call your own server (or a serverless function) and keep the secret there.
|
|
8
|
+
- Publishable keys that are designed to be public (Stripe publishable key, Firebase web config, Maps keys) are fine, but restrict them by domain in the provider's dashboard.
|
|
9
|
+
- Add `.env*` to `.gitignore` (keep a committed `.env.example` with empty values). Never print secrets in logs, errors or chat replies.
|
|
10
|
+
|
|
11
|
+
## 2. Cross-site scripting (XSS)
|
|
12
|
+
- React escapes text by default. Do not undo it: avoid `dangerouslySetInnerHTML`. If you must render HTML (CMS content, markdown), sanitise it first with DOMPurify.
|
|
13
|
+
- Never build `href`/`src` from user input without checking the protocol. Allow only `https:`, `http:`, `mailto:`, `tel:` and relative paths; block `javascript:` and `data:`.
|
|
14
|
+
- Never `eval`, `new Function` or `setTimeout("string")`.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
export function safeUrl(url) {
|
|
18
|
+
try {
|
|
19
|
+
const u = new URL(url, window.location.origin);
|
|
20
|
+
return ["http:", "https:", "mailto:", "tel:"].includes(u.protocol) ? u.href : "#";
|
|
21
|
+
} catch {
|
|
22
|
+
return "#";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 3. Links and embeds
|
|
28
|
+
- `target="_blank"` always with `rel="noopener noreferrer"`.
|
|
29
|
+
- Third-party iframes: `sandbox` with the fewest permissions, `loading="lazy"`, `referrerpolicy="strict-origin-when-cross-origin"`.
|
|
30
|
+
- Load third-party scripts only from trusted CDNs, pinned to an exact version, with Subresource Integrity (`integrity` + `crossorigin`) when the CDN supports it.
|
|
31
|
+
|
|
32
|
+
## 4. Auth on the client
|
|
33
|
+
- Keep session tokens in `httpOnly`, `Secure`, `SameSite=Lax` (or `Strict`) cookies set by the server. Do not store JWTs or refresh tokens in `localStorage` or `sessionStorage` (any XSS can read them).
|
|
34
|
+
- Hiding a button is not access control. The server must check permission on every request.
|
|
35
|
+
- Log out clears the server session; on 401 responses redirect to sign in.
|
|
36
|
+
|
|
37
|
+
## 5. Forms
|
|
38
|
+
- Validate on the client for UX (required, format, length) and again on the server for safety. Share the schema (for example Zod) when you can.
|
|
39
|
+
- Limit lengths (`maxLength`), use correct `type`/`autocomplete`, never echo raw errors from the server that reveal internals.
|
|
40
|
+
- Bot protection on public forms (sign up, contact): a honeypot field plus server-side rate limiting, or a CAPTCHA provider.
|
|
41
|
+
- File inputs: restrict `accept`, check size before upload, and let the server re-check type and size.
|
|
42
|
+
|
|
43
|
+
## 6. Security headers (set by the host)
|
|
44
|
+
Example for Vercel (`vercel.json`); adapt the CSP to the domains you actually use, then test that nothing breaks.
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"headers": [
|
|
49
|
+
{
|
|
50
|
+
"source": "/(.*)",
|
|
51
|
+
"headers": [
|
|
52
|
+
{ "key": "Content-Security-Policy", "value": "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self'; connect-src 'self' https://api.yourdomain.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" },
|
|
53
|
+
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
|
|
54
|
+
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
|
55
|
+
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
|
|
56
|
+
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" },
|
|
57
|
+
{ "key": "X-Frame-Options", "value": "DENY" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
Netlify uses `_headers`; Nginx uses `add_header`. WebGL components need `script-src 'self'` only; Google Fonts needs the two font domains above.
|
|
64
|
+
|
|
65
|
+
## 7. Dependencies and build
|
|
66
|
+
- Install packages only from npm with exact names (watch for typosquats). Prefer well-maintained packages.
|
|
67
|
+
- Run `npm audit` and fix high and critical issues; turn on Dependabot or Renovate.
|
|
68
|
+
- Commit the lockfile. Do not ship source maps with secrets in them; production builds should not expose `.env` or internal URLs.
|
|
69
|
+
|
|
70
|
+
## 8. Privacy
|
|
71
|
+
- Ask before using camera, microphone, location or notifications, and only when the user acts.
|
|
72
|
+
- Cookie and analytics consent where the law requires it (EU, UK, India DPDP). Load analytics after consent.
|
|
73
|
+
- Do not log personal data to the console or third-party error trackers; scrub emails and tokens.
|
|
74
|
+
|
|
75
|
+
## 9. Before you ship
|
|
76
|
+
- [ ] No secrets in the bundle (search the build output for `sk_`, `secret`, `password`, `mongodb+srv`, `BEGIN PRIVATE KEY`).
|
|
77
|
+
- [ ] No `dangerouslySetInnerHTML` with unsanitised input; all user URLs pass `safeUrl`.
|
|
78
|
+
- [ ] Every `target="_blank"` has `rel="noopener noreferrer"`.
|
|
79
|
+
- [ ] Tokens live in httpOnly cookies, not web storage.
|
|
80
|
+
- [ ] Security headers set and checked (for example with securityheaders.com).
|
|
81
|
+
- [ ] `npm audit` clean of high and critical issues.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apex-ui-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "116 free, animated UI components for React, styled with Tailwind CSS. Add any of them with one CLI command.",
|
|
6
6
|
"homepage": "https://apex-ui.in",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
"webgl",
|
|
27
27
|
"ai ui",
|
|
28
28
|
"apexui",
|
|
29
|
-
"apex ui kit"
|
|
29
|
+
"apex ui kit",
|
|
30
|
+
"ai agent skill",
|
|
31
|
+
"claude skill",
|
|
32
|
+
"cursor rules"
|
|
30
33
|
],
|
|
31
34
|
"type": "module",
|
|
32
35
|
"main": "dist/cli/index.js",
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
"dist"
|
|
38
41
|
],
|
|
39
42
|
"scripts": {
|
|
40
|
-
"build": "tsc && copyfiles -u 2 src/ApexUI-Kit/**/* dist/ApexUI-Kit"
|
|
43
|
+
"build": "tsc && copyfiles -u 2 src/ApexUI-Kit/**/* dist/ApexUI-Kit && copyfiles -u 1 src/skill/**/* dist"
|
|
41
44
|
},
|
|
42
45
|
"dependencies": {
|
|
43
46
|
"@fortawesome/fontawesome-svg-core": "^7.0.0",
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
-
import { motion } from "framer-motion";
|
|
3
|
-
import { Code2, Layers, Sparkles, Zap } from "lucide-react";
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
* Card Swap 3D: cards stacked in real 3D, turned toward the viewer and
|
|
7
|
-
* receding into the distance. On a timer the front card drops away and
|
|
8
|
-
* returns to the back while the rest step forward. Pauses while hovered;
|
|
9
|
-
* click to swap now. The whole stack scales down to fit narrow containers.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const DEFAULT_CARDS = [
|
|
13
|
-
{ icon: Sparkles, title: "Smooth", body: "Springs tuned to feel natural, never floaty." },
|
|
14
|
-
{ icon: Layers, title: "Composable", body: "Drop components in and style them with Tailwind." },
|
|
15
|
-
{ icon: Code2, title: "Yours", body: "Copy the source or add it with one CLI command." },
|
|
16
|
-
{ icon: Zap, title: "Fast", body: "Transforms and opacity only, 60fps on phones." },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const CardSwap3D = ({ cards = DEFAULT_CARDS, delay = 3200, distanceX = 3.6, distanceY = 3.2, depth = 70, tilt = 18, skew = 0, pauseOnHover = true, accentColor = "#a3e635", theme = "dark", width = 18, className = "" }) => {
|
|
20
|
-
// order[0] is the card in front.
|
|
21
|
-
const [order, setOrder] = useState(() => cards.map((_, i) => i));
|
|
22
|
-
const [dropping, setDropping] = useState(null);
|
|
23
|
-
const [paused, setPaused] = useState(false);
|
|
24
|
-
const [fit, setFit] = useState(1);
|
|
25
|
-
const outer = useRef(null);
|
|
26
|
-
const dark = theme !== "light";
|
|
27
|
-
const n = cards.length;
|
|
28
|
-
// Footprint of the whole stack in rem (card plus the steps behind it).
|
|
29
|
-
// (+5: the 3D turn makes the cards project a little wider than they are.)
|
|
30
|
-
const footW = width + distanceX * (n - 1) + 5;
|
|
31
|
-
const footH = width * 0.68 + distanceY * (n - 1) + 2;
|
|
32
|
-
|
|
33
|
-
const swap = () => {
|
|
34
|
-
if (dropping !== null) return;
|
|
35
|
-
setDropping(order[0]);
|
|
36
|
-
setTimeout(() => {
|
|
37
|
-
setOrder((o) => [...o.slice(1), o[0]]);
|
|
38
|
-
setDropping(null);
|
|
39
|
-
}, 520);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (paused) return undefined;
|
|
44
|
-
const id = setInterval(swap, delay);
|
|
45
|
-
return () => clearInterval(id);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Scale the stack down when its container is narrower than its footprint.
|
|
49
|
-
useLayoutEffect(() => {
|
|
50
|
-
const el = outer.current?.parentElement;
|
|
51
|
-
if (!el) return undefined;
|
|
52
|
-
const ro = new ResizeObserver(() => {
|
|
53
|
-
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
54
|
-
setFit(Math.min(1, el.clientWidth / (footW * rem)));
|
|
55
|
-
});
|
|
56
|
-
ro.observe(el);
|
|
57
|
-
return () => ro.disconnect();
|
|
58
|
-
}, [footW]);
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div ref={outer} className={`relative mx-auto ${className}`} style={{ width: `${footW * fit}rem`, height: `${footH * fit}rem` }}>
|
|
62
|
-
<div
|
|
63
|
-
className="absolute left-0 top-0 origin-top-left [perspective:1200px]"
|
|
64
|
-
style={{ width: `${footW}rem`, height: `${footH}rem`, transform: `scale(${fit})` }}
|
|
65
|
-
onPointerEnter={() => pauseOnHover && setPaused(true)}
|
|
66
|
-
onPointerLeave={() => setPaused(false)}
|
|
67
|
-
onClick={swap}
|
|
68
|
-
>
|
|
69
|
-
<div className="absolute [transform-style:preserve-3d]" style={{ left: "2.5rem", bottom: "1rem", width: `${width}rem`, height: `${width * 0.68}rem`, transform: `rotateY(${-tilt}deg) rotateX(${tilt * 0.35}deg)` }}>
|
|
70
|
-
{cards.map((card, i) => {
|
|
71
|
-
const slot = order.indexOf(i);
|
|
72
|
-
const isDropping = dropping === i;
|
|
73
|
-
const Icon = card.icon ?? Sparkles;
|
|
74
|
-
return (
|
|
75
|
-
<motion.div
|
|
76
|
-
key={i}
|
|
77
|
-
className={`absolute inset-0 cursor-pointer overflow-hidden rounded-2xl border p-6 ${dark ? "border-white/10 bg-neutral-950 text-white" : "border-black/10 bg-white text-neutral-900"}`}
|
|
78
|
-
style={{ zIndex: isDropping ? 60 : n - slot, boxShadow: "0 30px 60px -28px rgba(0,0,0,0.65)" }}
|
|
79
|
-
initial={false}
|
|
80
|
-
animate={
|
|
81
|
-
isDropping
|
|
82
|
-
? { x: "-2rem", y: "16rem", z: 120, rotateZ: -8, skewY: skew, opacity: 0 }
|
|
83
|
-
: { x: `${slot * distanceX}rem`, y: `${-slot * distanceY}rem`, z: -slot * depth, rotateZ: 0, skewY: skew, opacity: slot > 4 ? 0 : 1 - slot * 0.12 }
|
|
84
|
-
}
|
|
85
|
-
transition={isDropping ? { duration: 0.5, ease: [0.55, 0, 0.8, 0.2] } : { type: "spring", stiffness: 150, damping: 21 }}
|
|
86
|
-
>
|
|
87
|
-
<div className="flex items-center gap-2.5">
|
|
88
|
-
<span className="grid h-9 w-9 place-items-center rounded-xl text-black" style={{ background: accentColor }}>
|
|
89
|
-
<Icon className="h-[18px] w-[18px]" />
|
|
90
|
-
</span>
|
|
91
|
-
<p className="text-lg font-semibold">{card.title}</p>
|
|
92
|
-
</div>
|
|
93
|
-
<p className={`mt-3 text-[0.92rem] leading-relaxed ${dark ? "text-white/60" : "text-neutral-500"}`}>{card.body}</p>
|
|
94
|
-
<div className="pointer-events-none absolute -bottom-10 -right-10 h-32 w-32 rounded-full opacity-20 blur-2xl" style={{ background: accentColor }} />
|
|
95
|
-
</motion.div>
|
|
96
|
-
);
|
|
97
|
-
})}
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export default CardSwap3D;
|