@allshift/ui 0.2.0 → 0.3.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/dist/index.cjs +599 -2
- package/dist/index.d.cts +195 -11
- package/dist/index.d.ts +195 -11
- package/dist/index.js +596 -1
- package/package.json +12 -3
- package/styles/shift-canvas.css +67 -0
package/dist/index.js
CHANGED
|
@@ -203,9 +203,604 @@ function ScrollGenerateCard({
|
|
|
203
203
|
phase === "idle" && /* @__PURE__ */ jsx4("div", { style: { visibility: "hidden" }, children: children("idle") })
|
|
204
204
|
] });
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
// src/canvas/LiveCard.tsx
|
|
208
|
+
import {
|
|
209
|
+
motion as motion5,
|
|
210
|
+
useMotionValue as useMotionValue3,
|
|
211
|
+
useTransform as useTransform2,
|
|
212
|
+
useSpring as useSpring3,
|
|
213
|
+
useMotionTemplate as useMotionTemplate2
|
|
214
|
+
} from "framer-motion";
|
|
215
|
+
|
|
216
|
+
// src/canvas/useGenerate.ts
|
|
217
|
+
import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
218
|
+
import { useInView as useInView2 } from "framer-motion";
|
|
219
|
+
function useGenerate(opts = {}) {
|
|
220
|
+
const {
|
|
221
|
+
enterDelay = 0,
|
|
222
|
+
skeletonMs = 420,
|
|
223
|
+
trigger = "view",
|
|
224
|
+
margin = "-60px 0px",
|
|
225
|
+
repeat = false
|
|
226
|
+
} = opts;
|
|
227
|
+
const ref = useRef3(null);
|
|
228
|
+
const inView = useInView2(ref, { once: !repeat, margin });
|
|
229
|
+
const [phase, setPhase] = useState2("idle");
|
|
230
|
+
useEffect2(() => {
|
|
231
|
+
const shouldRun = trigger === "mount" || inView;
|
|
232
|
+
if (!shouldRun) return;
|
|
233
|
+
if (!repeat && phase !== "idle") return;
|
|
234
|
+
let live;
|
|
235
|
+
const enter = setTimeout(() => {
|
|
236
|
+
setPhase("skeleton");
|
|
237
|
+
live = setTimeout(() => setPhase("live"), skeletonMs);
|
|
238
|
+
}, enterDelay);
|
|
239
|
+
return () => {
|
|
240
|
+
clearTimeout(enter);
|
|
241
|
+
clearTimeout(live);
|
|
242
|
+
};
|
|
243
|
+
}, [inView, trigger]);
|
|
244
|
+
return { ref, phase, isLive: phase === "live" };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/canvas/accents.ts
|
|
248
|
+
var ACCENT_RGB = {
|
|
249
|
+
teal: "0,229,240",
|
|
250
|
+
blue: "59,139,255",
|
|
251
|
+
purple: "168,124,255",
|
|
252
|
+
magenta: "232,48,248",
|
|
253
|
+
green: "0,224,128",
|
|
254
|
+
orange: "255,110,48",
|
|
255
|
+
neutral: "255,255,255"
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/canvas/LiveCard.tsx
|
|
259
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
260
|
+
function resolveRgb(accent) {
|
|
261
|
+
if (accent === "brand") return { rgb: "var(--shift-accent-rgb, 91,140,255)", isBrand: true };
|
|
262
|
+
return { rgb: ACCENT_RGB[accent], isBrand: false };
|
|
263
|
+
}
|
|
264
|
+
var SKEL_WIDTHS = [72, 88, 60, 80, 66, 78, 54, 84];
|
|
265
|
+
function Skeleton({ rows, minHeight }) {
|
|
266
|
+
return /* @__PURE__ */ jsxs3(
|
|
267
|
+
motion5.div,
|
|
268
|
+
{
|
|
269
|
+
initial: { opacity: 0, y: 16 },
|
|
270
|
+
animate: { opacity: 1, y: 0 },
|
|
271
|
+
transition: { duration: 0.3 },
|
|
272
|
+
className: "ash-surface relative rounded-2xl border border-white/[0.07] p-5 overflow-hidden",
|
|
273
|
+
style: { minHeight },
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsx5("div", { className: "h-5 w-28 rounded-full bg-white/[0.07] animate-pulse mb-5" }),
|
|
276
|
+
Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 mb-3", children: [
|
|
277
|
+
/* @__PURE__ */ jsx5("div", { className: "w-7 h-7 rounded-xl bg-white/5 animate-pulse shrink-0" }),
|
|
278
|
+
/* @__PURE__ */ jsx5(
|
|
279
|
+
"div",
|
|
280
|
+
{
|
|
281
|
+
className: "h-2.5 rounded-full bg-white/5 animate-pulse",
|
|
282
|
+
style: { width: `${SKEL_WIDTHS[i % SKEL_WIDTHS.length]}%` }
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
] }, i)),
|
|
286
|
+
/* @__PURE__ */ jsx5(
|
|
287
|
+
motion5.div,
|
|
288
|
+
{
|
|
289
|
+
className: "absolute inset-0 pointer-events-none",
|
|
290
|
+
style: { background: "linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.05) 50%, transparent 100%)" },
|
|
291
|
+
animate: { x: ["-100%", "200%"] },
|
|
292
|
+
transition: { duration: 1.25, ease: "easeInOut", repeat: Infinity }
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
function LiveCard({
|
|
300
|
+
children,
|
|
301
|
+
accent = "neutral",
|
|
302
|
+
tilt = true,
|
|
303
|
+
shine = true,
|
|
304
|
+
generate = true,
|
|
305
|
+
index = 0,
|
|
306
|
+
trigger = "view",
|
|
307
|
+
skeletonRows = 4,
|
|
308
|
+
topLine = true,
|
|
309
|
+
className = "",
|
|
310
|
+
minHeight = 180,
|
|
311
|
+
onClick
|
|
312
|
+
}) {
|
|
313
|
+
const { rgb } = resolveRgb(accent);
|
|
314
|
+
const { ref, phase } = useGenerate({
|
|
315
|
+
enterDelay: generate ? index * 75 : 0,
|
|
316
|
+
trigger
|
|
317
|
+
});
|
|
318
|
+
const effectivePhase = generate ? phase : "live";
|
|
319
|
+
const mx = useMotionValue3(0.5);
|
|
320
|
+
const my = useMotionValue3(0.5);
|
|
321
|
+
const rawRY = useTransform2(mx, [0, 1], [-7, 7]);
|
|
322
|
+
const rawRX = useTransform2(my, [0, 1], [4, -4]);
|
|
323
|
+
const rotateY = useSpring3(rawRY, { stiffness: 300, damping: 28 });
|
|
324
|
+
const rotateX = useSpring3(rawRX, { stiffness: 300, damping: 28 });
|
|
325
|
+
const shineX = useMotionValue3(50);
|
|
326
|
+
const shineY = useMotionValue3(50);
|
|
327
|
+
const shineBg = useMotionTemplate2`radial-gradient(circle at ${shineX}% ${shineY}%, rgba(${rgb},0.10) 0%, rgba(${rgb},0.035) 42%, transparent 66%)`;
|
|
328
|
+
function onMouseMove(e) {
|
|
329
|
+
if (!tilt && !shine) return;
|
|
330
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
331
|
+
const nx = (e.clientX - rect.left) / rect.width;
|
|
332
|
+
const ny = (e.clientY - rect.top) / rect.height;
|
|
333
|
+
mx.set(nx);
|
|
334
|
+
my.set(ny);
|
|
335
|
+
shineX.set(nx * 100);
|
|
336
|
+
shineY.set(ny * 100);
|
|
337
|
+
}
|
|
338
|
+
function onMouseLeave() {
|
|
339
|
+
mx.set(0.5);
|
|
340
|
+
my.set(0.5);
|
|
341
|
+
shineX.set(50);
|
|
342
|
+
shineY.set(50);
|
|
343
|
+
}
|
|
344
|
+
if (effectivePhase === "idle") {
|
|
345
|
+
return /* @__PURE__ */ jsx5("div", { ref, className: `rounded-2xl border border-white/[0.04] ${className}`, style: { minHeight } });
|
|
346
|
+
}
|
|
347
|
+
if (effectivePhase === "skeleton") {
|
|
348
|
+
return /* @__PURE__ */ jsx5("div", { ref, className, children: /* @__PURE__ */ jsx5(Skeleton, { rows: skeletonRows, minHeight }) });
|
|
349
|
+
}
|
|
350
|
+
return /* @__PURE__ */ jsx5(
|
|
351
|
+
"div",
|
|
352
|
+
{
|
|
353
|
+
ref,
|
|
354
|
+
style: { perspective: tilt ? "1000px" : void 0 },
|
|
355
|
+
onMouseMove,
|
|
356
|
+
onMouseLeave,
|
|
357
|
+
className,
|
|
358
|
+
children: /* @__PURE__ */ jsxs3(
|
|
359
|
+
motion5.div,
|
|
360
|
+
{
|
|
361
|
+
style: {
|
|
362
|
+
rotateX: tilt ? rotateX : void 0,
|
|
363
|
+
rotateY: tilt ? rotateY : void 0,
|
|
364
|
+
background: shine ? shineBg : void 0,
|
|
365
|
+
transformStyle: tilt ? "preserve-3d" : void 0
|
|
366
|
+
},
|
|
367
|
+
initial: generate ? { opacity: 0, y: 12 } : false,
|
|
368
|
+
animate: { opacity: 1, y: 0 },
|
|
369
|
+
transition: { duration: 0.3 },
|
|
370
|
+
whileHover: tilt ? { scale: 1.012, y: -3 } : void 0,
|
|
371
|
+
onClick,
|
|
372
|
+
className: `ash-surface group relative rounded-2xl border border-white/[0.08] p-5 overflow-hidden transition-colors duration-300 ${onClick ? "cursor-pointer" : ""}`,
|
|
373
|
+
children: [
|
|
374
|
+
topLine && /* @__PURE__ */ jsx5(
|
|
375
|
+
motion5.div,
|
|
376
|
+
{
|
|
377
|
+
className: "absolute top-0 left-0 right-0 h-px",
|
|
378
|
+
style: { background: `linear-gradient(90deg, transparent, rgba(${rgb},0.9), transparent)`, transformOrigin: "left" },
|
|
379
|
+
initial: { scaleX: 0, opacity: 0 },
|
|
380
|
+
animate: { scaleX: 1, opacity: 0.7 },
|
|
381
|
+
transition: { duration: 0.5, ease: "easeOut" }
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
children,
|
|
385
|
+
/* @__PURE__ */ jsx5(
|
|
386
|
+
"div",
|
|
387
|
+
{
|
|
388
|
+
className: "absolute bottom-0 right-0 w-32 h-32 rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none",
|
|
389
|
+
style: { background: `rgba(${rgb},0.18)` }
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/canvas/GenerativeView.tsx
|
|
400
|
+
import { Children, isValidElement } from "react";
|
|
401
|
+
import { motion as motion6 } from "framer-motion";
|
|
402
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
403
|
+
function GenerativeView({
|
|
404
|
+
children,
|
|
405
|
+
stagger = 0.08,
|
|
406
|
+
delay = 0,
|
|
407
|
+
className = ""
|
|
408
|
+
}) {
|
|
409
|
+
const items = Children.toArray(children);
|
|
410
|
+
return /* @__PURE__ */ jsx6(
|
|
411
|
+
motion6.div,
|
|
412
|
+
{
|
|
413
|
+
className,
|
|
414
|
+
initial: "hidden",
|
|
415
|
+
animate: "show",
|
|
416
|
+
variants: {
|
|
417
|
+
hidden: {},
|
|
418
|
+
show: { transition: { staggerChildren: stagger, delayChildren: delay } }
|
|
419
|
+
},
|
|
420
|
+
children: items.map(
|
|
421
|
+
(child, i) => isValidElement(child) ? /* @__PURE__ */ jsx6(
|
|
422
|
+
motion6.div,
|
|
423
|
+
{
|
|
424
|
+
variants: {
|
|
425
|
+
hidden: { opacity: 0, y: 16, filter: "blur(6px)" },
|
|
426
|
+
show: {
|
|
427
|
+
opacity: 1,
|
|
428
|
+
y: 0,
|
|
429
|
+
filter: "blur(0px)",
|
|
430
|
+
transition: { type: "spring", stiffness: 260, damping: 28 }
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
children: child
|
|
434
|
+
},
|
|
435
|
+
child.key ?? i
|
|
436
|
+
) : child
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/canvas/LiveStat.tsx
|
|
443
|
+
import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
|
|
444
|
+
import { motion as motion7 } from "framer-motion";
|
|
445
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
446
|
+
function rgbFor(accent) {
|
|
447
|
+
if (accent === "brand") return "var(--shift-accent-rgb, 91,140,255)";
|
|
448
|
+
return ACCENT_RGB[accent];
|
|
449
|
+
}
|
|
450
|
+
function formatCompact(n, prefix, suffix) {
|
|
451
|
+
let body;
|
|
452
|
+
if (Math.abs(n) >= 1e6) body = `${(n / 1e6).toFixed(1)}M`;
|
|
453
|
+
else if (Math.abs(n) >= 1e3) body = `${(n / 1e3).toFixed(0)}K`;
|
|
454
|
+
else body = n.toLocaleString();
|
|
455
|
+
return `${prefix}${body}${suffix}`;
|
|
456
|
+
}
|
|
457
|
+
function LiveStat({
|
|
458
|
+
label,
|
|
459
|
+
value,
|
|
460
|
+
display,
|
|
461
|
+
prefix = "",
|
|
462
|
+
suffix = "",
|
|
463
|
+
sub,
|
|
464
|
+
icon: Icon,
|
|
465
|
+
accent = "neutral",
|
|
466
|
+
duration = 1200,
|
|
467
|
+
index = 0
|
|
468
|
+
}) {
|
|
469
|
+
const rgb = rgbFor(accent);
|
|
470
|
+
const [n, setN] = useState3(0);
|
|
471
|
+
const raf = useRef4(0);
|
|
472
|
+
useEffect3(() => {
|
|
473
|
+
if (value == null || display != null) return;
|
|
474
|
+
const start = performance.now();
|
|
475
|
+
const step = (now) => {
|
|
476
|
+
const pct = Math.min((now - start) / duration, 1);
|
|
477
|
+
const eased = 1 - Math.pow(1 - pct, 3);
|
|
478
|
+
setN(value * eased);
|
|
479
|
+
if (pct < 1) raf.current = requestAnimationFrame(step);
|
|
480
|
+
};
|
|
481
|
+
raf.current = requestAnimationFrame(step);
|
|
482
|
+
return () => cancelAnimationFrame(raf.current);
|
|
483
|
+
}, [value, display, duration]);
|
|
484
|
+
const shown = display ?? (value != null ? formatCompact(n, prefix, suffix) : "\u2014");
|
|
485
|
+
return /* @__PURE__ */ jsxs4(
|
|
486
|
+
motion7.div,
|
|
487
|
+
{
|
|
488
|
+
initial: { opacity: 0, y: 16, scale: 0.96 },
|
|
489
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
490
|
+
transition: { type: "spring", stiffness: 280, damping: 26, delay: index * 0.06 },
|
|
491
|
+
className: "ash-surface relative rounded-2xl border border-white/[0.08] p-4 overflow-hidden",
|
|
492
|
+
children: [
|
|
493
|
+
/* @__PURE__ */ jsx7("div", { className: "absolute top-0 left-0 right-0 h-px", style: { background: `rgba(${rgb},0.6)` } }),
|
|
494
|
+
Icon && /* @__PURE__ */ jsx7("div", { className: "w-8 h-8 rounded-xl flex items-center justify-center mb-3", style: { background: `rgba(${rgb},0.15)` }, children: /* @__PURE__ */ jsx7(Icon, { size: 14, style: { color: `rgb(${rgb})` } }) }),
|
|
495
|
+
/* @__PURE__ */ jsx7("div", { className: "text-2xl font-black mb-1", style: { color: `rgb(${rgb})`, filter: `drop-shadow(0 0 10px rgba(${rgb},0.45))` }, children: shown }),
|
|
496
|
+
/* @__PURE__ */ jsx7("div", { className: "text-[11px] font-bold text-white/70", children: label }),
|
|
497
|
+
sub && /* @__PURE__ */ jsx7("div", { className: "text-[10px] text-white/30 mt-0.5", children: sub })
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/canvas/CanvasTransition.tsx
|
|
504
|
+
import { usePathname } from "next/navigation";
|
|
505
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
506
|
+
import { motion as motion8, AnimatePresence } from "framer-motion";
|
|
507
|
+
import { Sparkles } from "lucide-react";
|
|
508
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
509
|
+
function CanvasTransition({
|
|
510
|
+
children,
|
|
511
|
+
labels = {},
|
|
512
|
+
seenKey = "ash-seen-views",
|
|
513
|
+
assemblerName = "Shift",
|
|
514
|
+
alwaysGenerate = false
|
|
515
|
+
}) {
|
|
516
|
+
const pathname = usePathname();
|
|
517
|
+
const [mounted, setMounted] = useState4(false);
|
|
518
|
+
const [showSheen, setShowSheen] = useState4(false);
|
|
519
|
+
const [seen] = useState4(() => /* @__PURE__ */ new Set());
|
|
520
|
+
function viewLabel(path) {
|
|
521
|
+
const seg = path.split("/").filter(Boolean);
|
|
522
|
+
const key = seg.find((s) => labels[s]) ?? seg[0] ?? "dashboard";
|
|
523
|
+
return labels[key] ?? key.replace(/-/g, " ");
|
|
524
|
+
}
|
|
525
|
+
useEffect4(() => {
|
|
526
|
+
try {
|
|
527
|
+
const raw = sessionStorage.getItem(seenKey);
|
|
528
|
+
if (raw) JSON.parse(raw).forEach((p) => seen.add(p));
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
setMounted(true);
|
|
532
|
+
}, []);
|
|
533
|
+
const firstVisit = mounted && (alwaysGenerate || !seen.has(pathname));
|
|
534
|
+
useEffect4(() => {
|
|
535
|
+
if (!mounted) return;
|
|
536
|
+
const wasFirst = alwaysGenerate || !seen.has(pathname);
|
|
537
|
+
seen.add(pathname);
|
|
538
|
+
try {
|
|
539
|
+
sessionStorage.setItem(seenKey, JSON.stringify([...seen]));
|
|
540
|
+
} catch {
|
|
541
|
+
}
|
|
542
|
+
if (wasFirst) {
|
|
543
|
+
setShowSheen(true);
|
|
544
|
+
const t = setTimeout(() => setShowSheen(false), 900);
|
|
545
|
+
return () => clearTimeout(t);
|
|
546
|
+
}
|
|
547
|
+
setShowSheen(false);
|
|
548
|
+
}, [pathname, mounted]);
|
|
549
|
+
return /* @__PURE__ */ jsx8(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxs5(
|
|
550
|
+
motion8.div,
|
|
551
|
+
{
|
|
552
|
+
initial: firstVisit ? { opacity: 0, y: 14, filter: "blur(7px)" } : { opacity: 0 },
|
|
553
|
+
animate: { opacity: 1, y: 0, filter: "blur(0px)" },
|
|
554
|
+
exit: { opacity: 0, filter: "blur(4px)", transition: { duration: 0.16 } },
|
|
555
|
+
transition: firstVisit ? { type: "spring", stiffness: 240, damping: 28 } : { duration: 0.16, ease: "easeOut" },
|
|
556
|
+
className: "relative min-h-full",
|
|
557
|
+
children: [
|
|
558
|
+
/* @__PURE__ */ jsx8(AnimatePresence, { children: showSheen && /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
559
|
+
/* @__PURE__ */ jsx8(
|
|
560
|
+
motion8.div,
|
|
561
|
+
{
|
|
562
|
+
initial: { scaleX: 0, opacity: 0.8 },
|
|
563
|
+
animate: { scaleX: 1, opacity: 0 },
|
|
564
|
+
exit: { opacity: 0 },
|
|
565
|
+
transition: { duration: 0.9, ease: "easeOut" },
|
|
566
|
+
style: {
|
|
567
|
+
transformOrigin: "left",
|
|
568
|
+
background: "linear-gradient(90deg, transparent, var(--shift-accent), transparent)"
|
|
569
|
+
},
|
|
570
|
+
className: "pointer-events-none fixed top-0 left-0 right-0 h-0.5 z-[60]"
|
|
571
|
+
}
|
|
572
|
+
),
|
|
573
|
+
/* @__PURE__ */ jsxs5(
|
|
574
|
+
motion8.div,
|
|
575
|
+
{
|
|
576
|
+
initial: { opacity: 0, y: -6 },
|
|
577
|
+
animate: { opacity: 1, y: 0 },
|
|
578
|
+
exit: { opacity: 0, y: -6 },
|
|
579
|
+
transition: { duration: 0.25 },
|
|
580
|
+
style: {
|
|
581
|
+
borderColor: "color-mix(in srgb, var(--shift-accent) 25%, transparent)",
|
|
582
|
+
boxShadow: "0 0 24px color-mix(in srgb, var(--shift-accent) 18%, transparent)"
|
|
583
|
+
},
|
|
584
|
+
className: "ash-surface pointer-events-none fixed top-3 left-1/2 -translate-x-1/2 z-[60] flex items-center gap-1.5 rounded-full border px-3 py-1",
|
|
585
|
+
children: [
|
|
586
|
+
/* @__PURE__ */ jsx8(Sparkles, { size: 10, style: { color: "var(--shift-accent)" }, className: "animate-pulse" }),
|
|
587
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-[10px] font-bold", style: { color: "var(--shift-accent)" }, children: [
|
|
588
|
+
assemblerName,
|
|
589
|
+
" is assembling ",
|
|
590
|
+
viewLabel(pathname),
|
|
591
|
+
"\u2026"
|
|
592
|
+
] })
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
)
|
|
596
|
+
] }) }),
|
|
597
|
+
children
|
|
598
|
+
]
|
|
599
|
+
},
|
|
600
|
+
pathname
|
|
601
|
+
) });
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/canvas/CanvasLauncher.tsx
|
|
605
|
+
import { useEffect as useEffect5, useMemo, useState as useState5 } from "react";
|
|
606
|
+
import { useRouter, usePathname as usePathname2 } from "next/navigation";
|
|
607
|
+
import { motion as motion9, AnimatePresence as AnimatePresence2 } from "framer-motion";
|
|
608
|
+
import { LayoutGrid, X, Search, Sparkles as Sparkles2 } from "lucide-react";
|
|
609
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
610
|
+
function CanvasLauncher({
|
|
611
|
+
groups,
|
|
612
|
+
title = "Everything Shift can build",
|
|
613
|
+
subtitle = "Pick a resource \u2014 I'll bring it to the canvas",
|
|
614
|
+
buttonLabel = "Everything",
|
|
615
|
+
hotkey = "j",
|
|
616
|
+
openEvent = "shift:open-launcher",
|
|
617
|
+
onNavigate
|
|
618
|
+
}) {
|
|
619
|
+
const router = useRouter();
|
|
620
|
+
const pathname = usePathname2();
|
|
621
|
+
const [open, setOpen] = useState5(false);
|
|
622
|
+
const [query, setQuery] = useState5("");
|
|
623
|
+
useEffect5(() => {
|
|
624
|
+
function onKey(e) {
|
|
625
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === hotkey) {
|
|
626
|
+
e.preventDefault();
|
|
627
|
+
setOpen((v) => !v);
|
|
628
|
+
}
|
|
629
|
+
if (e.key === "Escape") setOpen(false);
|
|
630
|
+
}
|
|
631
|
+
document.addEventListener("keydown", onKey);
|
|
632
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
633
|
+
}, [hotkey]);
|
|
634
|
+
useEffect5(() => {
|
|
635
|
+
function onOpen() {
|
|
636
|
+
setOpen(true);
|
|
637
|
+
}
|
|
638
|
+
window.addEventListener(openEvent, onOpen);
|
|
639
|
+
return () => window.removeEventListener(openEvent, onOpen);
|
|
640
|
+
}, [openEvent]);
|
|
641
|
+
const filtered = useMemo(() => {
|
|
642
|
+
if (!query.trim()) return groups;
|
|
643
|
+
const q = query.toLowerCase();
|
|
644
|
+
return groups.map((g) => ({
|
|
645
|
+
...g,
|
|
646
|
+
items: g.items.filter((i) => i.label.toLowerCase().includes(q) || i.desc.toLowerCase().includes(q))
|
|
647
|
+
})).filter((g) => g.items.length > 0);
|
|
648
|
+
}, [query, groups]);
|
|
649
|
+
function go(href) {
|
|
650
|
+
setOpen(false);
|
|
651
|
+
setQuery("");
|
|
652
|
+
if (href === pathname) return;
|
|
653
|
+
if (onNavigate) onNavigate(href);
|
|
654
|
+
else router.push(href);
|
|
655
|
+
}
|
|
656
|
+
let flatIndex = 0;
|
|
657
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
658
|
+
/* @__PURE__ */ jsxs6(
|
|
659
|
+
"button",
|
|
660
|
+
{
|
|
661
|
+
type: "button",
|
|
662
|
+
onClick: () => setOpen(true),
|
|
663
|
+
"aria-label": "Open menu",
|
|
664
|
+
title: `Everything (\u2318${hotkey.toUpperCase()})`,
|
|
665
|
+
style: { borderColor: "color-mix(in srgb, var(--shift-accent) 20%, transparent)", color: "var(--shift-accent)" },
|
|
666
|
+
className: "ash-surface flex items-center gap-2 px-2.5 py-1.5 rounded-xl border transition-all hover:brightness-110",
|
|
667
|
+
children: [
|
|
668
|
+
/* @__PURE__ */ jsx9(LayoutGrid, { size: 15 }),
|
|
669
|
+
/* @__PURE__ */ jsx9("span", { className: "hidden lg:inline text-xs font-bold", children: buttonLabel })
|
|
670
|
+
]
|
|
671
|
+
}
|
|
672
|
+
),
|
|
673
|
+
/* @__PURE__ */ jsx9(AnimatePresence2, { children: open && /* @__PURE__ */ jsxs6(
|
|
674
|
+
motion9.div,
|
|
675
|
+
{
|
|
676
|
+
initial: { opacity: 0 },
|
|
677
|
+
animate: { opacity: 1 },
|
|
678
|
+
exit: { opacity: 0 },
|
|
679
|
+
transition: { duration: 0.2 },
|
|
680
|
+
className: "fixed inset-0 z-[8000] flex flex-col",
|
|
681
|
+
style: { background: "rgba(2,4,10,0.86)", backdropFilter: "blur(20px)", WebkitBackdropFilter: "blur(20px)" },
|
|
682
|
+
onClick: () => setOpen(false),
|
|
683
|
+
children: [
|
|
684
|
+
/* @__PURE__ */ jsx9("div", { className: "shrink-0 px-6 pt-6 pb-4", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs6("div", { className: "max-w-6xl mx-auto", children: [
|
|
685
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between mb-4", children: [
|
|
686
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
|
|
687
|
+
/* @__PURE__ */ jsx9(
|
|
688
|
+
"div",
|
|
689
|
+
{
|
|
690
|
+
className: "w-7 h-7 rounded-full flex items-center justify-center",
|
|
691
|
+
style: { background: "linear-gradient(135deg, var(--shift-accent), var(--shift-accent-2))", boxShadow: "0 0 14px color-mix(in srgb, var(--shift-accent) 50%, transparent)" },
|
|
692
|
+
children: /* @__PURE__ */ jsx9(Sparkles2, { size: 13, className: "text-white" })
|
|
693
|
+
}
|
|
694
|
+
),
|
|
695
|
+
/* @__PURE__ */ jsxs6("div", { children: [
|
|
696
|
+
/* @__PURE__ */ jsx9("div", { className: "text-sm font-black text-white leading-none", children: title }),
|
|
697
|
+
/* @__PURE__ */ jsx9("div", { className: "text-[11px] text-white/40 mt-1", children: subtitle })
|
|
698
|
+
] })
|
|
699
|
+
] }),
|
|
700
|
+
/* @__PURE__ */ jsx9("button", { type: "button", onClick: () => setOpen(false), className: "w-8 h-8 rounded-xl flex items-center justify-center text-white/40 hover:text-white hover:bg-white/[0.08] transition-all", children: /* @__PURE__ */ jsx9(X, { size: 16 }) })
|
|
701
|
+
] }),
|
|
702
|
+
/* @__PURE__ */ jsxs6("div", { className: "ash-surface flex items-center gap-2.5 rounded-2xl border border-white/10 px-4 py-3", children: [
|
|
703
|
+
/* @__PURE__ */ jsx9(Search, { size: 15, className: "text-white/30" }),
|
|
704
|
+
/* @__PURE__ */ jsx9(
|
|
705
|
+
"input",
|
|
706
|
+
{
|
|
707
|
+
autoFocus: true,
|
|
708
|
+
value: query,
|
|
709
|
+
onChange: (e) => setQuery(e.target.value),
|
|
710
|
+
placeholder: "Search resources\u2026",
|
|
711
|
+
className: "flex-1 bg-transparent text-sm text-white placeholder:text-white/25 outline-none"
|
|
712
|
+
}
|
|
713
|
+
),
|
|
714
|
+
/* @__PURE__ */ jsx9("kbd", { className: "text-[10px] text-white/25 border border-white/10 rounded px-1.5 py-0.5", children: "esc" })
|
|
715
|
+
] })
|
|
716
|
+
] }) }),
|
|
717
|
+
/* @__PURE__ */ jsx9("div", { className: "flex-1 overflow-y-auto px-6 pb-10", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs6("div", { className: "max-w-6xl mx-auto flex flex-col gap-7", children: [
|
|
718
|
+
filtered.map((group) => {
|
|
719
|
+
const rgb = ACCENT_RGB[group.accent];
|
|
720
|
+
return /* @__PURE__ */ jsxs6("div", { children: [
|
|
721
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 mb-3", style: { color: `rgb(${rgb})` }, children: [
|
|
722
|
+
/* @__PURE__ */ jsx9("div", { className: "w-1.5 h-1.5 rounded-full", style: { background: `rgb(${rgb})` } }),
|
|
723
|
+
/* @__PURE__ */ jsx9("span", { className: "text-[11px] font-black uppercase tracking-widest", children: group.label })
|
|
724
|
+
] }),
|
|
725
|
+
/* @__PURE__ */ jsx9("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3", children: group.items.map((item) => {
|
|
726
|
+
const Icon = item.icon;
|
|
727
|
+
const isActive = pathname === item.href;
|
|
728
|
+
const idx = flatIndex++;
|
|
729
|
+
return /* @__PURE__ */ jsxs6(
|
|
730
|
+
motion9.button,
|
|
731
|
+
{
|
|
732
|
+
type: "button",
|
|
733
|
+
onClick: () => go(item.href),
|
|
734
|
+
initial: { opacity: 0, y: 14, filter: "blur(5px)" },
|
|
735
|
+
animate: { opacity: 1, y: 0, filter: "blur(0px)" },
|
|
736
|
+
transition: { duration: 0.28, delay: Math.min(idx * 0.018, 0.5), ease: "easeOut" },
|
|
737
|
+
whileHover: { y: -3, scale: 1.015 },
|
|
738
|
+
style: isActive ? { borderColor: `rgba(${rgb},0.4)` } : void 0,
|
|
739
|
+
className: `ash-surface group relative text-left rounded-2xl border p-4 overflow-hidden transition-colors ${isActive ? "" : "border-white/[0.08] hover:border-white/[0.15]"}`,
|
|
740
|
+
children: [
|
|
741
|
+
/* @__PURE__ */ jsx9("div", { className: "absolute top-0 left-0 right-0 h-px opacity-60", style: { background: `rgba(${rgb},0.7)` } }),
|
|
742
|
+
/* @__PURE__ */ jsx9("div", { className: "w-9 h-9 rounded-xl flex items-center justify-center mb-3", style: { background: `rgba(${rgb},0.12)` }, children: /* @__PURE__ */ jsx9(Icon, { size: 16, style: { color: `rgb(${rgb})` } }) }),
|
|
743
|
+
/* @__PURE__ */ jsx9("div", { className: "text-sm font-bold text-white mb-0.5", children: item.label }),
|
|
744
|
+
/* @__PURE__ */ jsx9("div", { className: "text-[11px] text-white/40 leading-snug", children: item.desc }),
|
|
745
|
+
isActive && /* @__PURE__ */ jsx9("span", { className: "absolute top-3 right-3 text-[9px] font-bold", style: { color: `rgb(${rgb})` }, children: "on canvas" }),
|
|
746
|
+
/* @__PURE__ */ jsx9("div", { className: "absolute bottom-0 right-0 w-24 h-24 rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none", style: { background: `rgba(${rgb},0.18)` } })
|
|
747
|
+
]
|
|
748
|
+
},
|
|
749
|
+
item.href
|
|
750
|
+
);
|
|
751
|
+
}) })
|
|
752
|
+
] }, group.label);
|
|
753
|
+
}),
|
|
754
|
+
filtered.length === 0 && /* @__PURE__ */ jsxs6("div", { className: "text-center text-white/30 text-sm py-20", children: [
|
|
755
|
+
"No resources match \u201C",
|
|
756
|
+
query,
|
|
757
|
+
"\u201D"
|
|
758
|
+
] })
|
|
759
|
+
] }) })
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
) })
|
|
763
|
+
] });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/canvas/PageTransition.tsx
|
|
767
|
+
import { motion as motion10 } from "framer-motion";
|
|
768
|
+
import { usePathname as usePathname3 } from "next/navigation";
|
|
769
|
+
import { useRef as useRef5 } from "react";
|
|
770
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
771
|
+
function PageTransition({ children, className }) {
|
|
772
|
+
const pathname = usePathname3();
|
|
773
|
+
const depthRef = useRef5(0);
|
|
774
|
+
const dirRef = useRef5(0);
|
|
775
|
+
const depth = pathname.split("/").filter(Boolean).length;
|
|
776
|
+
if (depth !== depthRef.current) {
|
|
777
|
+
dirRef.current = dirRef.current === 0 ? 0 : depth >= depthRef.current ? 1 : -1;
|
|
778
|
+
depthRef.current = depth;
|
|
779
|
+
}
|
|
780
|
+
const dir = dirRef.current;
|
|
781
|
+
return /* @__PURE__ */ jsx10(
|
|
782
|
+
motion10.div,
|
|
783
|
+
{
|
|
784
|
+
initial: { opacity: 0, x: dir * 22, filter: "blur(3px)" },
|
|
785
|
+
animate: { opacity: 1, x: 0, filter: "blur(0px)" },
|
|
786
|
+
transition: { type: "spring", stiffness: 400, damping: 34 },
|
|
787
|
+
className: className ?? "flex flex-col flex-1 min-h-0",
|
|
788
|
+
children
|
|
789
|
+
},
|
|
790
|
+
pathname
|
|
791
|
+
);
|
|
792
|
+
}
|
|
206
793
|
export {
|
|
794
|
+
ACCENT_RGB,
|
|
795
|
+
CanvasLauncher,
|
|
796
|
+
CanvasTransition,
|
|
797
|
+
GenerativeView,
|
|
207
798
|
HolographicShine,
|
|
799
|
+
LiveCard,
|
|
800
|
+
LiveStat,
|
|
801
|
+
PageTransition,
|
|
208
802
|
ScrollGenerateCard,
|
|
209
803
|
ShiftCard,
|
|
210
|
-
TiltCard
|
|
804
|
+
TiltCard,
|
|
805
|
+
useGenerate
|
|
211
806
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allshift/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shift family shared FEEL layer — glass morphism, 3D tilt physics, holographic shine, scroll-triggered generation. Brand-neutral by design.",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Shift family shared FEEL layer — glass morphism, 3D tilt physics, holographic shine, scroll-triggered generation, and the living-canvas engine (LiveCard, LiveStat, CanvasTransition, CanvasLauncher). Brand-neutral by design.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
7
7
|
"type": "module",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"require": "./dist/index.cjs"
|
|
22
22
|
},
|
|
23
23
|
"./styles.css": "./styles/shift-ui.css",
|
|
24
|
+
"./canvas.css": "./styles/shift-canvas.css",
|
|
24
25
|
"./package.json": "./package.json"
|
|
25
26
|
},
|
|
26
27
|
"scripts": {
|
|
@@ -30,11 +31,19 @@
|
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"framer-motion": ">=10",
|
|
33
|
-
"react": ">=18"
|
|
34
|
+
"react": ">=18",
|
|
35
|
+
"lucide-react": ">=0.300.0",
|
|
36
|
+
"next": ">=14"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"lucide-react": { "optional": true },
|
|
40
|
+
"next": { "optional": true }
|
|
34
41
|
},
|
|
35
42
|
"devDependencies": {
|
|
36
43
|
"@types/react": "^19.2.17",
|
|
37
44
|
"framer-motion": "^12.40.0",
|
|
45
|
+
"lucide-react": "^1.14.0",
|
|
46
|
+
"next": "^16.2.7",
|
|
38
47
|
"react": "^19.2.7",
|
|
39
48
|
"tsup": "^8.5.1",
|
|
40
49
|
"typescript": "^6.0.3"
|