@nebutra/ui 0.1.0 → 0.2.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.
Files changed (39) hide show
  1. package/dist/components/index.d.ts +380 -0
  2. package/dist/components/index.js +2398 -0
  3. package/dist/components/index.js.map +1 -0
  4. package/dist/hooks/index.d.ts +106 -0
  5. package/dist/hooks/index.js +182 -0
  6. package/dist/hooks/index.js.map +1 -0
  7. package/dist/icons/index.d.ts +3 -0
  8. package/dist/icons/index.js +5 -0
  9. package/dist/icons/index.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.js +31 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/layout/index.d.ts +296 -0
  14. package/dist/layout/index.js +1303 -0
  15. package/dist/layout/index.js.map +1 -0
  16. package/dist/layouts/index.d.ts +161 -0
  17. package/dist/layouts/index.js +611 -0
  18. package/dist/layouts/index.js.map +1 -0
  19. package/dist/patterns/index.d.ts +363 -0
  20. package/dist/patterns/index.js +3004 -0
  21. package/dist/patterns/index.js.map +1 -0
  22. package/dist/primitives/index.d.ts +9588 -0
  23. package/dist/primitives/index.js +26779 -0
  24. package/dist/primitives/index.js.map +1 -0
  25. package/dist/styles/brand-override.css +61 -0
  26. package/dist/tailwind.preset.d.ts +251 -0
  27. package/dist/tailwind.preset.js +273 -0
  28. package/dist/tailwind.preset.js.map +1 -0
  29. package/dist/theme/index.d.ts +150 -0
  30. package/dist/theme/index.js +28 -0
  31. package/dist/theme/index.js.map +1 -0
  32. package/dist/typography/fonts.css +110 -0
  33. package/dist/typography/index.d.ts +425 -0
  34. package/dist/typography/index.js +374 -0
  35. package/dist/typography/index.js.map +1 -0
  36. package/dist/utils/index.d.ts +61 -0
  37. package/dist/utils/index.js +55 -0
  38. package/dist/utils/index.js.map +1 -0
  39. package/package.json +18 -8
@@ -0,0 +1,106 @@
1
+ import { HTMLMotionProps } from 'framer-motion';
2
+ import * as React from 'react';
3
+ import { RefObject } from 'react';
4
+
5
+ interface DwellHintProps extends Omit<HTMLMotionProps<"div">, "children" | "initial" | "animate" | "exit" | "transition"> {
6
+ /** Whether the hint is visible */
7
+ show: boolean;
8
+ /** The hint message to display */
9
+ message: string;
10
+ /** Optional icon to display before the message */
11
+ icon?: React.ReactNode;
12
+ /** Position of the hint relative to content (default: "bottom") */
13
+ position?: "top" | "bottom" | "center";
14
+ }
15
+ /**
16
+ * DwellHint - A contextual hint that appears when user dwells on content.
17
+ *
18
+ * Use with useScrollDwell hook to show helpful messages when users
19
+ * pause scrolling and appear to be reading content.
20
+ *
21
+ * @example
22
+ * const { hasTriggered } = useScrollDwell(sectionRef, { threshold: 1000 });
23
+ *
24
+ * <DwellHint
25
+ * show={hasTriggered}
26
+ * message="This is why production-ready matters."
27
+ * icon={<Lightbulb className="w-4 h-4" />}
28
+ * />
29
+ */
30
+ declare const DwellHint: React.ForwardRefExoticComponent<Omit<DwellHintProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
31
+
32
+ /**
33
+ * Hook to detect media query matches
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const isMobile = useMediaQuery("(max-width: 768px)");
38
+ * const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
39
+ * ```
40
+ */
41
+ declare function useMediaQuery(query: string): boolean;
42
+
43
+ /**
44
+ * Returns true if the user has enabled prefers-reduced-motion.
45
+ * Use this to disable animations, skip transitions, or simplify motion.
46
+ *
47
+ * Wraps framer-motion's `useReducedMotion` so callers don't need framer-motion
48
+ * as a direct import just for accessibility checks.
49
+ *
50
+ * @example
51
+ * const shouldReduce = useReducedMotion();
52
+ * const transition = shouldReduce ? { duration: 0 } : brandSpring.default;
53
+ */
54
+ declare function useReducedMotion(): boolean;
55
+
56
+ interface DwellConfig {
57
+ /** Time in ms before dwell is triggered (default: 800) */
58
+ threshold?: number;
59
+ /** Cooldown in ms between triggers (default: 5000) */
60
+ cooldown?: number;
61
+ /** Callback when dwell is detected */
62
+ onDwell?: () => void;
63
+ /** Only trigger when element is in viewport (default: true) */
64
+ requireVisible?: boolean;
65
+ }
66
+ interface DwellState {
67
+ /** Whether the user is currently dwelling */
68
+ isDwelling: boolean;
69
+ /** Progress toward trigger threshold (0-1) */
70
+ dwellProgress: number;
71
+ /** Whether dwell was triggered (resets after cooldown) */
72
+ hasTriggered: boolean;
73
+ /** Manually reset the triggered state */
74
+ reset: () => void;
75
+ }
76
+ /**
77
+ * useScrollDwell - Detects when a user pauses scrolling over an element.
78
+ *
79
+ * This hook monitors scroll activity and detects "dwell" behavior where
80
+ * the user stops scrolling for a specified duration. Useful for showing
81
+ * contextual hints, tooltips, or triggering animations when a user
82
+ * appears to be reading or studying content.
83
+ *
84
+ * @param ref - Reference to the element to monitor
85
+ * @param config - Configuration options
86
+ * @returns DwellState object with current dwell status
87
+ *
88
+ * @example
89
+ * function FeatureSection() {
90
+ * const sectionRef = useRef<HTMLDivElement>(null);
91
+ * const { isDwelling, hasTriggered } = useScrollDwell(sectionRef, {
92
+ * threshold: 1000,
93
+ * cooldown: 10000,
94
+ * onDwell: () => console.log("User is reading this section"),
95
+ * });
96
+ *
97
+ * return (
98
+ * <div ref={sectionRef}>
99
+ * {hasTriggered && <DwellHint message="Take a closer look!" />}
100
+ * </div>
101
+ * );
102
+ * }
103
+ */
104
+ declare function useScrollDwell(ref: RefObject<HTMLElement | null>, config?: DwellConfig): DwellState;
105
+
106
+ export { type DwellConfig, DwellHint, type DwellHintProps, type DwellState, useMediaQuery, useReducedMotion, useScrollDwell };
@@ -0,0 +1,182 @@
1
+ "use client";
2
+ import { AnimatePresence, motion, useReducedMotion as useReducedMotion$1 } from 'framer-motion';
3
+ import * as React2 from 'react';
4
+ import { useState, useRef, useCallback, useEffect } from 'react';
5
+ import { clsx } from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
+
9
+ // src/hooks/DwellHint.tsx
10
+ function cn(...inputs) {
11
+ return twMerge(clsx(inputs));
12
+ }
13
+ var DwellHint = React2.forwardRef(
14
+ ({ show, message, icon, position = "bottom", className, ...props }, ref) => {
15
+ const positionClasses = {
16
+ top: "top-0 -translate-y-full mb-2",
17
+ bottom: "bottom-0 translate-y-full mt-2",
18
+ center: "top-1/2 -translate-y-1/2"
19
+ };
20
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: show && /* @__PURE__ */ jsx(
21
+ motion.div,
22
+ {
23
+ ref,
24
+ className: cn(
25
+ "absolute left-1/2 -translate-x-1/2 z-10",
26
+ positionClasses[position],
27
+ className
28
+ ),
29
+ initial: {
30
+ opacity: 0,
31
+ y: position === "top" ? -10 : 10,
32
+ scale: 0.95
33
+ },
34
+ animate: { opacity: 1, y: 0, scale: 1 },
35
+ exit: { opacity: 0, y: position === "top" ? -5 : 5, scale: 0.98 },
36
+ transition: {
37
+ duration: 0.4,
38
+ ease: [0.34, 1.56, 0.64, 1]
39
+ },
40
+ ...props,
41
+ children: /* @__PURE__ */ jsxs(
42
+ "div",
43
+ {
44
+ className: cn(
45
+ "flex items-center gap-2 px-4 py-2.5",
46
+ "bg-background/95 backdrop-blur-md",
47
+ "border border-border/50 rounded-[var(--radius-lg)]",
48
+ "shadow-lg shadow-black/5",
49
+ "text-sm text-muted-foreground"
50
+ ),
51
+ children: [
52
+ icon && /* @__PURE__ */ jsx("span", { className: "text-primary/70 shrink-0", children: icon }),
53
+ /* @__PURE__ */ jsx("span", { children: message })
54
+ ]
55
+ }
56
+ )
57
+ }
58
+ ) });
59
+ }
60
+ );
61
+ DwellHint.displayName = "DwellHint";
62
+ function useMediaQuery(query) {
63
+ const [matches, setMatches] = React2.useState(false);
64
+ React2.useEffect(() => {
65
+ const media = window.matchMedia(query);
66
+ if (media.matches !== matches) {
67
+ setMatches(media.matches);
68
+ }
69
+ const listener = (event) => {
70
+ setMatches(event.matches);
71
+ };
72
+ media.addEventListener("change", listener);
73
+ return () => {
74
+ media.removeEventListener("change", listener);
75
+ };
76
+ }, [matches, query]);
77
+ return matches;
78
+ }
79
+ function useReducedMotion() {
80
+ return useReducedMotion$1() ?? false;
81
+ }
82
+ function useScrollDwell(ref, config = {}) {
83
+ const { threshold = 800, cooldown = 5e3, onDwell, requireVisible = true } = config;
84
+ const [isDwelling, setIsDwelling] = useState(false);
85
+ const [dwellProgress, setDwellProgress] = useState(0);
86
+ const [hasTriggered, setHasTriggered] = useState(false);
87
+ const dwellTimerRef = useRef(null);
88
+ const progressIntervalRef = useRef(null);
89
+ const cooldownTimerRef = useRef(null);
90
+ const lastScrollTimeRef = useRef(Date.now());
91
+ const isInCooldownRef = useRef(false);
92
+ const clearTimers = useCallback(() => {
93
+ if (dwellTimerRef.current) {
94
+ clearTimeout(dwellTimerRef.current);
95
+ dwellTimerRef.current = null;
96
+ }
97
+ if (progressIntervalRef.current) {
98
+ clearInterval(progressIntervalRef.current);
99
+ progressIntervalRef.current = null;
100
+ }
101
+ }, []);
102
+ const reset = useCallback(() => {
103
+ setHasTriggered(false);
104
+ setIsDwelling(false);
105
+ setDwellProgress(0);
106
+ isInCooldownRef.current = false;
107
+ if (cooldownTimerRef.current) {
108
+ clearTimeout(cooldownTimerRef.current);
109
+ cooldownTimerRef.current = null;
110
+ }
111
+ }, []);
112
+ const isElementVisible = useCallback(() => {
113
+ if (!ref.current) return false;
114
+ const rect = ref.current.getBoundingClientRect();
115
+ const windowHeight = window.innerHeight;
116
+ const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0);
117
+ const elementHeight = rect.height;
118
+ return visibleHeight / elementHeight > 0.5;
119
+ }, [ref]);
120
+ const startDwellTimer = useCallback(() => {
121
+ if (isInCooldownRef.current || hasTriggered) return;
122
+ if (requireVisible && !isElementVisible()) return;
123
+ clearTimers();
124
+ const startTime = Date.now();
125
+ progressIntervalRef.current = setInterval(() => {
126
+ const elapsed = Date.now() - startTime;
127
+ const progress = Math.min(elapsed / threshold, 1);
128
+ setDwellProgress(progress);
129
+ if (progress >= 1) {
130
+ if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
131
+ }
132
+ }, 50);
133
+ dwellTimerRef.current = setTimeout(() => {
134
+ setIsDwelling(true);
135
+ setHasTriggered(true);
136
+ onDwell?.();
137
+ isInCooldownRef.current = true;
138
+ cooldownTimerRef.current = setTimeout(() => {
139
+ isInCooldownRef.current = false;
140
+ setHasTriggered(false);
141
+ setDwellProgress(0);
142
+ }, cooldown);
143
+ }, threshold);
144
+ }, [threshold, cooldown, onDwell, requireVisible, hasTriggered, isElementVisible, clearTimers]);
145
+ const handleScroll = useCallback(() => {
146
+ lastScrollTimeRef.current = Date.now();
147
+ setIsDwelling(false);
148
+ setDwellProgress(0);
149
+ clearTimers();
150
+ setTimeout(() => {
151
+ const timeSinceScroll = Date.now() - lastScrollTimeRef.current;
152
+ if (timeSinceScroll >= 100) {
153
+ startDwellTimer();
154
+ }
155
+ }, 100);
156
+ }, [clearTimers, startDwellTimer]);
157
+ useEffect(() => {
158
+ window.addEventListener("scroll", handleScroll, { passive: true });
159
+ if (requireVisible && isElementVisible()) {
160
+ startDwellTimer();
161
+ } else if (!requireVisible) {
162
+ startDwellTimer();
163
+ }
164
+ return () => {
165
+ window.removeEventListener("scroll", handleScroll);
166
+ clearTimers();
167
+ if (cooldownTimerRef.current) {
168
+ clearTimeout(cooldownTimerRef.current);
169
+ }
170
+ };
171
+ }, [handleScroll, startDwellTimer, requireVisible, isElementVisible, clearTimers]);
172
+ return {
173
+ isDwelling,
174
+ dwellProgress,
175
+ hasTriggered,
176
+ reset
177
+ };
178
+ }
179
+
180
+ export { DwellHint, useMediaQuery, useReducedMotion, useScrollDwell };
181
+ //# sourceMappingURL=index.js.map
182
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/hooks/DwellHint.tsx","../../src/hooks/use-media-query.ts","../../src/hooks/use-reduced-motion.ts","../../src/hooks/useScrollDwell.ts"],"names":["React","useFramerReducedMotion","useState","useEffect"],"mappings":";;;;;;;;AAGO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AC4BO,IAAM,SAAA,GAAkBA,MAAA,CAAA,UAAA;AAAA,EAC7B,CAAC,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,QAAA,GAAW,QAAA,EAAU,SAAA,EAAW,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC1E,IAAA,MAAM,eAAA,GAAkB;AAAA,MACtB,GAAA,EAAK,8BAAA;AAAA,MACL,MAAA,EAAQ,gCAAA;AAAA,MACR,MAAA,EAAQ;AAAA,KACV;AAEA,IAAA,uBACE,GAAA,CAAC,mBACE,QAAA,EAAA,IAAA,oBACC,GAAA;AAAA,MAAC,MAAA,CAAO,GAAA;AAAA,MAAP;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,yCAAA;AAAA,UACA,gBAAgB,QAAQ,CAAA;AAAA,UACxB;AAAA,SACF;AAAA,QACA,OAAA,EAAS;AAAA,UACP,OAAA,EAAS,CAAA;AAAA,UACT,CAAA,EAAG,QAAA,KAAa,KAAA,GAAQ,GAAA,GAAM,EAAA;AAAA,UAC9B,KAAA,EAAO;AAAA,SACT;AAAA,QACA,SAAS,EAAE,OAAA,EAAS,GAAG,CAAA,EAAG,CAAA,EAAG,OAAO,CAAA,EAAE;AAAA,QACtC,IAAA,EAAM,EAAE,OAAA,EAAS,CAAA,EAAG,CAAA,EAAG,aAAa,KAAA,GAAQ,EAAA,GAAK,CAAA,EAAG,KAAA,EAAO,IAAA,EAAK;AAAA,QAChE,UAAA,EAAY;AAAA,UACV,QAAA,EAAU,GAAA;AAAA,UACV,IAAA,EAAM,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,CAAC;AAAA,SAC5B;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,kBAAA,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,EAAA;AAAA,cACT,qCAAA;AAAA,cACA,mCAAA;AAAA,cACA,oDAAA;AAAA,cACA,0BAAA;AAAA,cACA;AAAA,aACF;AAAA,YAEC,QAAA,EAAA;AAAA,cAAA,IAAA,oBAAQ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0BAAA,EAA4B,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,8BAC1D,GAAA,CAAC,UAAM,QAAA,EAAA,OAAA,EAAQ;AAAA;AAAA;AAAA;AACjB;AAAA,KACF,EAEJ,CAAA;AAAA,EAEJ;AACF;AAEA,SAAA,CAAU,WAAA,GAAc,WAAA;ACxEjB,SAAS,cAAc,KAAA,EAAwB;AACpD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAU,gBAAS,KAAK,CAAA;AAElD,EAAM,iBAAU,MAAM;AACpB,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AAGrC,IAAA,IAAI,KAAA,CAAM,YAAY,OAAA,EAAS;AAC7B,MAAA,UAAA,CAAW,MAAM,OAAO,CAAA;AAAA,IAC1B;AAGA,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAA+B;AAC/C,MAAA,UAAA,CAAW,MAAM,OAAO,CAAA;AAAA,IAC1B,CAAA;AAGA,IAAA,KAAA,CAAM,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAGzC,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAAA,IAC9C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,KAAK,CAAC,CAAA;AAEnB,EAAA,OAAO,OAAA;AACT;ACtBO,SAAS,gBAAA,GAA4B;AAC1C,EAAA,OAAOC,oBAAuB,IAAK,KAAA;AACrC;ACqCO,SAAS,cAAA,CACd,GAAA,EACA,MAAA,GAAsB,EAAC,EACX;AACZ,EAAA,MAAM,EAAE,YAAY,GAAA,EAAK,QAAA,GAAW,KAAM,OAAA,EAAS,cAAA,GAAiB,MAAK,GAAI,MAAA;AAE7E,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,SAAS,KAAK,CAAA;AAClD,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIA,SAAS,CAAC,CAAA;AACpD,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,SAAS,KAAK,CAAA;AAEtD,EAAA,MAAM,aAAA,GAAgB,OAA8B,IAAI,CAAA;AACxD,EAAA,MAAM,mBAAA,GAAsB,OAA8B,IAAI,CAAA;AAC9D,EAAA,MAAM,gBAAA,GAAmB,OAA8B,IAAI,CAAA;AAC3D,EAAA,MAAM,iBAAA,GAAoB,MAAA,CAAe,IAAA,CAAK,GAAA,EAAK,CAAA;AACnD,EAAA,MAAM,eAAA,GAAkB,OAAO,KAAK,CAAA;AAEpC,EAAA,MAAM,WAAA,GAAc,YAAY,MAAM;AACpC,IAAA,IAAI,cAAc,OAAA,EAAS;AACzB,MAAA,YAAA,CAAa,cAAc,OAAO,CAAA;AAClC,MAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AAAA,IAC1B;AACA,IAAA,IAAI,oBAAoB,OAAA,EAAS;AAC/B,MAAA,aAAA,CAAc,oBAAoB,OAAO,CAAA;AACzC,MAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAAA,IAChC;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,IAAA,aAAA,CAAc,KAAK,CAAA;AACnB,IAAA,gBAAA,CAAiB,CAAC,CAAA;AAClB,IAAA,eAAA,CAAgB,OAAA,GAAU,KAAA;AAC1B,IAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,MAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AACrC,MAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAAA,IAC7B;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,gBAAA,GAAmB,YAAY,MAAM;AACzC,IAAA,IAAI,CAAC,GAAA,CAAI,OAAA,EAAS,OAAO,KAAA;AACzB,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,qBAAA,EAAsB;AAC/C,IAAA,MAAM,eAAe,MAAA,CAAO,WAAA;AAG5B,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,MAAA,EAAQ,YAAY,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,EAAK,CAAC,CAAA;AAChF,IAAA,MAAM,gBAAgB,IAAA,CAAK,MAAA;AAE3B,IAAA,OAAO,gBAAgB,aAAA,GAAgB,GAAA;AAAA,EACzC,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AAER,EAAA,MAAM,eAAA,GAAkB,YAAY,MAAM;AACxC,IAAA,IAAI,eAAA,CAAgB,WAAW,YAAA,EAAc;AAC7C,IAAA,IAAI,cAAA,IAAkB,CAAC,gBAAA,EAAiB,EAAG;AAE3C,IAAA,WAAA,EAAY;AAGZ,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,mBAAA,CAAoB,OAAA,GAAU,YAAY,MAAM;AAC9C,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAC7B,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,WAAW,CAAC,CAAA;AAChD,MAAA,gBAAA,CAAiB,QAAQ,CAAA;AAEzB,MAAA,IAAI,YAAY,CAAA,EAAG;AACjB,QAAA,IAAI,mBAAA,CAAoB,OAAA,EAAS,aAAA,CAAc,mBAAA,CAAoB,OAAO,CAAA;AAAA,MAC5E;AAAA,IACF,GAAG,EAAE,CAAA;AAGL,IAAA,aAAA,CAAc,OAAA,GAAU,WAAW,MAAM;AACvC,MAAA,aAAA,CAAc,IAAI,CAAA;AAClB,MAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,MAAA,OAAA,IAAU;AAGV,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,MAAA,gBAAA,CAAiB,OAAA,GAAU,WAAW,MAAM;AAC1C,QAAA,eAAA,CAAgB,OAAA,GAAU,KAAA;AAC1B,QAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,QAAA,gBAAA,CAAiB,CAAC,CAAA;AAAA,MACpB,GAAG,QAAQ,CAAA;AAAA,IACb,GAAG,SAAS,CAAA;AAAA,EACd,CAAA,EAAG,CAAC,SAAA,EAAW,QAAA,EAAU,SAAS,cAAA,EAAgB,YAAA,EAAc,gBAAA,EAAkB,WAAW,CAAC,CAAA;AAE9F,EAAA,MAAM,YAAA,GAAe,YAAY,MAAM;AACrC,IAAA,iBAAA,CAAkB,OAAA,GAAU,KAAK,GAAA,EAAI;AACrC,IAAA,aAAA,CAAc,KAAK,CAAA;AACnB,IAAA,gBAAA,CAAiB,CAAC,CAAA;AAClB,IAAA,WAAA,EAAY;AAGZ,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,GAAA,EAAI,GAAI,iBAAA,CAAkB,OAAA;AACvD,MAAA,IAAI,mBAAmB,GAAA,EAAK;AAC1B,QAAA,eAAA,EAAgB;AAAA,MAClB;AAAA,IACF,GAAG,GAAG,CAAA;AAAA,EACR,CAAA,EAAG,CAAC,WAAA,EAAa,eAAe,CAAC,CAAA;AAEjC,EAAAC,UAAU,MAAM;AACd,IAAA,MAAA,CAAO,iBAAiB,QAAA,EAAU,YAAA,EAAc,EAAE,OAAA,EAAS,MAAM,CAAA;AAGjE,IAAA,IAAI,cAAA,IAAkB,kBAAiB,EAAG;AACxC,MAAA,eAAA,EAAgB;AAAA,IAClB,CAAA,MAAA,IAAW,CAAC,cAAA,EAAgB;AAC1B,MAAA,eAAA,EAAgB;AAAA,IAClB;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,YAAY,CAAA;AACjD,MAAA,WAAA,EAAY;AACZ,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAAA,MACvC;AAAA,IACF,CAAA;AAAA,EACF,GAAG,CAAC,YAAA,EAAc,iBAAiB,cAAA,EAAgB,gBAAA,EAAkB,WAAW,CAAC,CAAA;AAEjF,EAAA,OAAO;AAAA,IACL,UAAA;AAAA,IACA,aAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { AnimatePresence, type HTMLMotionProps, motion } from \"framer-motion\";\nimport * as React from \"react\";\nimport { cn } from \"../utils/cn\";\n\nexport interface DwellHintProps\n extends Omit<HTMLMotionProps<\"div\">, \"children\" | \"initial\" | \"animate\" | \"exit\" | \"transition\"> {\n /** Whether the hint is visible */\n show: boolean;\n /** The hint message to display */\n message: string;\n /** Optional icon to display before the message */\n icon?: React.ReactNode;\n /** Position of the hint relative to content (default: \"bottom\") */\n position?: \"top\" | \"bottom\" | \"center\";\n}\n\n/**\n * DwellHint - A contextual hint that appears when user dwells on content.\n *\n * Use with useScrollDwell hook to show helpful messages when users\n * pause scrolling and appear to be reading content.\n *\n * @example\n * const { hasTriggered } = useScrollDwell(sectionRef, { threshold: 1000 });\n *\n * <DwellHint\n * show={hasTriggered}\n * message=\"This is why production-ready matters.\"\n * icon={<Lightbulb className=\"w-4 h-4\" />}\n * />\n */\nexport const DwellHint = React.forwardRef<HTMLDivElement, DwellHintProps>(\n ({ show, message, icon, position = \"bottom\", className, ...props }, ref) => {\n const positionClasses = {\n top: \"top-0 -translate-y-full mb-2\",\n bottom: \"bottom-0 translate-y-full mt-2\",\n center: \"top-1/2 -translate-y-1/2\",\n };\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n className={cn(\n \"absolute left-1/2 -translate-x-1/2 z-10\",\n positionClasses[position],\n className,\n )}\n initial={{\n opacity: 0,\n y: position === \"top\" ? -10 : 10,\n scale: 0.95,\n }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: position === \"top\" ? -5 : 5, scale: 0.98 }}\n transition={{\n duration: 0.4,\n ease: [0.34, 1.56, 0.64, 1],\n }}\n {...props}\n >\n <div\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5\",\n \"bg-background/95 backdrop-blur-md\",\n \"border border-border/50 rounded-[var(--radius-lg)]\",\n \"shadow-lg shadow-black/5\",\n \"text-sm text-muted-foreground\",\n )}\n >\n {icon && <span className=\"text-primary/70 shrink-0\">{icon}</span>}\n <span>{message}</span>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n },\n);\n\nDwellHint.displayName = \"DwellHint\";\n","import * as React from \"react\";\n\n/**\n * Hook to detect media query matches\n *\n * @example\n * ```tsx\n * const isMobile = useMediaQuery(\"(max-width: 768px)\");\n * const prefersDark = useMediaQuery(\"(prefers-color-scheme: dark)\");\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(false);\n\n React.useEffect(() => {\n const media = window.matchMedia(query);\n\n // Set initial value\n if (media.matches !== matches) {\n setMatches(media.matches);\n }\n\n // Create listener\n const listener = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Add listener\n media.addEventListener(\"change\", listener);\n\n // Cleanup\n return () => {\n media.removeEventListener(\"change\", listener);\n };\n }, [matches, query]);\n\n return matches;\n}\n","\"use client\";\n\nimport { useReducedMotion as useFramerReducedMotion } from \"framer-motion\";\n\n/**\n * Returns true if the user has enabled prefers-reduced-motion.\n * Use this to disable animations, skip transitions, or simplify motion.\n *\n * Wraps framer-motion's `useReducedMotion` so callers don't need framer-motion\n * as a direct import just for accessibility checks.\n *\n * @example\n * const shouldReduce = useReducedMotion();\n * const transition = shouldReduce ? { duration: 0 } : brandSpring.default;\n */\nexport function useReducedMotion(): boolean {\n return useFramerReducedMotion() ?? false;\n}\n","\"use client\";\n\nimport { type RefObject, useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface DwellConfig {\n /** Time in ms before dwell is triggered (default: 800) */\n threshold?: number;\n /** Cooldown in ms between triggers (default: 5000) */\n cooldown?: number;\n /** Callback when dwell is detected */\n onDwell?: () => void;\n /** Only trigger when element is in viewport (default: true) */\n requireVisible?: boolean;\n}\n\nexport interface DwellState {\n /** Whether the user is currently dwelling */\n isDwelling: boolean;\n /** Progress toward trigger threshold (0-1) */\n dwellProgress: number;\n /** Whether dwell was triggered (resets after cooldown) */\n hasTriggered: boolean;\n /** Manually reset the triggered state */\n reset: () => void;\n}\n\n/**\n * useScrollDwell - Detects when a user pauses scrolling over an element.\n *\n * This hook monitors scroll activity and detects \"dwell\" behavior where\n * the user stops scrolling for a specified duration. Useful for showing\n * contextual hints, tooltips, or triggering animations when a user\n * appears to be reading or studying content.\n *\n * @param ref - Reference to the element to monitor\n * @param config - Configuration options\n * @returns DwellState object with current dwell status\n *\n * @example\n * function FeatureSection() {\n * const sectionRef = useRef<HTMLDivElement>(null);\n * const { isDwelling, hasTriggered } = useScrollDwell(sectionRef, {\n * threshold: 1000,\n * cooldown: 10000,\n * onDwell: () => console.log(\"User is reading this section\"),\n * });\n *\n * return (\n * <div ref={sectionRef}>\n * {hasTriggered && <DwellHint message=\"Take a closer look!\" />}\n * </div>\n * );\n * }\n */\nexport function useScrollDwell(\n ref: RefObject<HTMLElement | null>,\n config: DwellConfig = {},\n): DwellState {\n const { threshold = 800, cooldown = 5000, onDwell, requireVisible = true } = config;\n\n const [isDwelling, setIsDwelling] = useState(false);\n const [dwellProgress, setDwellProgress] = useState(0);\n const [hasTriggered, setHasTriggered] = useState(false);\n\n const dwellTimerRef = useRef<NodeJS.Timeout | null>(null);\n const progressIntervalRef = useRef<NodeJS.Timeout | null>(null);\n const cooldownTimerRef = useRef<NodeJS.Timeout | null>(null);\n const lastScrollTimeRef = useRef<number>(Date.now());\n const isInCooldownRef = useRef(false);\n\n const clearTimers = useCallback(() => {\n if (dwellTimerRef.current) {\n clearTimeout(dwellTimerRef.current);\n dwellTimerRef.current = null;\n }\n if (progressIntervalRef.current) {\n clearInterval(progressIntervalRef.current);\n progressIntervalRef.current = null;\n }\n }, []);\n\n const reset = useCallback(() => {\n setHasTriggered(false);\n setIsDwelling(false);\n setDwellProgress(0);\n isInCooldownRef.current = false;\n if (cooldownTimerRef.current) {\n clearTimeout(cooldownTimerRef.current);\n cooldownTimerRef.current = null;\n }\n }, []);\n\n const isElementVisible = useCallback(() => {\n if (!ref.current) return false;\n const rect = ref.current.getBoundingClientRect();\n const windowHeight = window.innerHeight;\n\n // Element is visible if at least 50% is in viewport\n const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0);\n const elementHeight = rect.height;\n\n return visibleHeight / elementHeight > 0.5;\n }, [ref]);\n\n const startDwellTimer = useCallback(() => {\n if (isInCooldownRef.current || hasTriggered) return;\n if (requireVisible && !isElementVisible()) return;\n\n clearTimers();\n\n // Start progress tracking\n const startTime = Date.now();\n progressIntervalRef.current = setInterval(() => {\n const elapsed = Date.now() - startTime;\n const progress = Math.min(elapsed / threshold, 1);\n setDwellProgress(progress);\n\n if (progress >= 1) {\n if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);\n }\n }, 50);\n\n // Set up dwell trigger\n dwellTimerRef.current = setTimeout(() => {\n setIsDwelling(true);\n setHasTriggered(true);\n onDwell?.();\n\n // Start cooldown\n isInCooldownRef.current = true;\n cooldownTimerRef.current = setTimeout(() => {\n isInCooldownRef.current = false;\n setHasTriggered(false);\n setDwellProgress(0);\n }, cooldown);\n }, threshold);\n }, [threshold, cooldown, onDwell, requireVisible, hasTriggered, isElementVisible, clearTimers]);\n\n const handleScroll = useCallback(() => {\n lastScrollTimeRef.current = Date.now();\n setIsDwelling(false);\n setDwellProgress(0);\n clearTimers();\n\n // Restart dwell detection after a short delay\n setTimeout(() => {\n const timeSinceScroll = Date.now() - lastScrollTimeRef.current;\n if (timeSinceScroll >= 100) {\n startDwellTimer();\n }\n }, 100);\n }, [clearTimers, startDwellTimer]);\n\n useEffect(() => {\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n // Start initial dwell timer if element is already visible\n if (requireVisible && isElementVisible()) {\n startDwellTimer();\n } else if (!requireVisible) {\n startDwellTimer();\n }\n\n return () => {\n window.removeEventListener(\"scroll\", handleScroll);\n clearTimers();\n if (cooldownTimerRef.current) {\n clearTimeout(cooldownTimerRef.current);\n }\n };\n }, [handleScroll, startDwellTimer, requireVisible, isElementVisible, clearTimers]);\n\n return {\n isDwelling,\n dwellProgress,\n hasTriggered,\n reset,\n };\n}\n\nexport default useScrollDwell;\n"]}
@@ -0,0 +1,3 @@
1
+ export { SiClerk as ClerkIcon, SiNextdotjs as NextjsIcon, SiPrisma as PrismaIcon, SiStripe as StripeIcon, SiSupabase as SupabaseIcon, SiTailwindcss as TailwindIcon } from '@icons-pack/react-simple-icons';
2
+ export { Anthropic, Bilibili, Claude, ClaudeCode, Cursor, DeepSeek, Flux, Gemini, Github, Google, Grok, HuggingFace, LangChain, LangGraph, MCP, Meta, Midjourney, Mistral, N8n, Ollama, OpenAI, OpenClaw, Qwen, Railway, SiliconCloud, Smithery, Vercel } from '@lobehub/icons';
3
+ export { Robot as Bot, Check, LoaderCircle as Loader2, Menu, Moon, Plus, MagnifyingGlass as Search, SettingsGear as Settings, Sparkles, Sun, Cross as X } from '@nebutra/icons';
@@ -0,0 +1,5 @@
1
+ export { SiClerk as ClerkIcon, SiNextdotjs as NextjsIcon, SiPrisma as PrismaIcon, SiStripe as StripeIcon, SiSupabase as SupabaseIcon, SiTailwindcss as TailwindIcon } from '@icons-pack/react-simple-icons';
2
+ export { Anthropic, Bilibili, Claude, ClaudeCode, Cursor, DeepSeek, Flux, Gemini, Github, Google, Grok, HuggingFace, LangChain, LangGraph, MCP, Meta, Midjourney, Mistral, N8n, Ollama, OpenAI, OpenClaw, Qwen, Railway, SiliconCloud, Smithery, Vercel } from '@lobehub/icons';
3
+ export { Robot as Bot, Check, LoaderCircle as Loader2, Menu, Moon, Plus, MagnifyingGlass as Search, SettingsGear as Settings, Sparkles, Sun, Cross as X } from '@nebutra/icons';
4
+ //# sourceMappingURL=index.js.map
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
@@ -0,0 +1,5 @@
1
+ export { NebutraThemeProvider, NebutraThemeProviderProps, NebutraTokens, ThemeMode } from './theme/index.js';
2
+ export { Anthropic, Claude, Gemini, Google, OpenAI } from '@lobehub/icons';
3
+ export { Robot as Bot, Check, LoaderCircle as Loader2, Menu, Moon, Plus, MagnifyingGlass as Search, SettingsGear as Settings, Sparkles, Sun, Cross as X } from '@nebutra/icons';
4
+ import 'react/jsx-runtime';
5
+ import 'react';
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { ThemeProvider } from '@lobehub/ui';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import '@icons-pack/react-simple-icons';
5
+ export { Anthropic, Claude, Gemini, Google, OpenAI } from '@lobehub/icons';
6
+ export { Robot as Bot, Check, LoaderCircle as Loader2, Menu, Moon, Plus, MagnifyingGlass as Search, SettingsGear as Settings, Sparkles, Sun, Cross as X } from '@nebutra/icons';
7
+
8
+ // src/theme/provider.tsx
9
+ function NebutraThemeProvider({
10
+ children,
11
+ appearance = "auto",
12
+ defaultAppearance = "light"
13
+ }) {
14
+ const resolvedAppearance = appearance === "auto" ? void 0 : appearance;
15
+ return /* @__PURE__ */ jsx(
16
+ ThemeProvider,
17
+ {
18
+ ...resolvedAppearance != null && { appearance: resolvedAppearance },
19
+ defaultAppearance,
20
+ customTheme: {
21
+ primaryColor: "blue",
22
+ neutralColor: "slate"
23
+ },
24
+ children
25
+ }
26
+ );
27
+ }
28
+
29
+ export { NebutraThemeProvider };
30
+ //# sourceMappingURL=index.js.map
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme/provider.tsx"],"names":["LobeThemeProvider"],"mappings":";;;;;;;AAoCO,SAAS,oBAAA,CAAqB;AAAA,EACnC,QAAA;AAAA,EACA,UAAA,GAAa,MAAA;AAAA,EACb,iBAAA,GAAoB;AACtB,CAAA,EAA8B;AAC5B,EAAA,MAAM,kBAAA,GAAqB,UAAA,KAAe,MAAA,GAAS,MAAA,GAAY,UAAA;AAE/D,EAAA,uBACE,GAAA;AAAA,IAACA,aAAA;AAAA,IAAA;AAAA,MACE,GAAI,kBAAA,IAAsB,IAAA,IAAQ,EAAE,YAAY,kBAAA,EAAmB;AAAA,MACpE,iBAAA;AAAA,MACA,WAAA,EAAa;AAAA,QACX,YAAA,EAAc,MAAA;AAAA,QACd,YAAA,EAAc;AAAA,OAChB;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"index.js","sourcesContent":["\"use client\";\n\n/**\n * Nebutra Theme Provider — Lobe UI integration bridge\n *\n * Wraps Lobe UI's ThemeProvider with Nebutra brand tokens.\n * This provider handles Lobe UI / Ant Design theming only.\n *\n * For app-level light/dark mode switching, use:\n * import { ThemeProvider } from \"@nebutra/tokens\";\n *\n * For CSS-variable-based tokens (Tailwind, custom components), use:\n * @import \"@nebutra/tokens/styles.css\";\n */\n\nimport { ThemeProvider as LobeThemeProvider } from \"@lobehub/ui\";\nimport type { ReactNode } from \"react\";\n\nexport interface NebutraThemeProviderProps {\n children: ReactNode;\n /** Force dark/light mode, or use system preference */\n appearance?: \"dark\" | \"light\" | \"auto\";\n /** Default theme mode when appearance is \"auto\" */\n defaultAppearance?: \"dark\" | \"light\";\n}\n\n/**\n * Theme provider that wraps Lobe UI with Nebutra design tokens\n *\n * @example\n * ```tsx\n * <NebutraThemeProvider appearance=\"auto\">\n * <App />\n * </NebutraThemeProvider>\n * ```\n */\nexport function NebutraThemeProvider({\n children,\n appearance = \"auto\",\n defaultAppearance = \"light\",\n}: NebutraThemeProviderProps) {\n const resolvedAppearance = appearance === \"auto\" ? undefined : appearance;\n\n return (\n <LobeThemeProvider\n {...(resolvedAppearance != null && { appearance: resolvedAppearance })}\n defaultAppearance={defaultAppearance}\n customTheme={{\n primaryColor: \"blue\",\n neutralColor: \"slate\",\n }}\n >\n {children}\n </LobeThemeProvider>\n );\n}\n\nexport default NebutraThemeProvider;\n"]}