@nebutra/ui 2.0.0 → 3.0.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.
Files changed (43) hide show
  1. package/dist/{canonical-Bf4czR2u.d.ts → canonical-DEhT4VfS.d.ts} +71 -120
  2. package/dist/card-DunB1gKE.d.ts +105 -0
  3. package/dist/components/index.d.ts +33 -4
  4. package/dist/components/index.js +225 -89
  5. package/dist/components/index.js.map +1 -1
  6. package/dist/editorial/index.d.ts +2 -2
  7. package/dist/editorial/index.js +75 -1
  8. package/dist/editorial/index.js.map +1 -1
  9. package/dist/empty-state-bmfHCOuN.d.ts +86 -0
  10. package/dist/hooks/index.d.ts +18 -1
  11. package/dist/hooks/index.js +120 -5
  12. package/dist/hooks/index.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/layout/index.d.ts +17 -58
  15. package/dist/layout/index.js +366 -126
  16. package/dist/layout/index.js.map +1 -1
  17. package/dist/layouts/index.d.ts +1 -1
  18. package/dist/layouts/index.js +89 -15
  19. package/dist/layouts/index.js.map +1 -1
  20. package/dist/patterns/index.d.ts +38 -44
  21. package/dist/patterns/index.js +470 -278
  22. package/dist/patterns/index.js.map +1 -1
  23. package/dist/primitives/canonical.d.ts +3 -1
  24. package/dist/primitives/canonical.js +495 -231
  25. package/dist/primitives/canonical.js.map +1 -1
  26. package/dist/primitives/index.d.ts +90 -62
  27. package/dist/primitives/index.js +1129 -1185
  28. package/dist/primitives/index.js.map +1 -1
  29. package/dist/tailwind.preset.d.ts +32 -32
  30. package/dist/tailwind.preset.js +24 -20
  31. package/dist/tailwind.preset.js.map +1 -1
  32. package/dist/theme/index.d.ts +1 -110
  33. package/dist/typography/fonts.css +8 -16
  34. package/dist/typography/index.d.ts +29 -45
  35. package/dist/typography/index.js +26 -14
  36. package/dist/typography/index.js.map +1 -1
  37. package/dist/utils/index.d.ts +18 -12
  38. package/dist/utils/index.js +94 -18
  39. package/dist/utils/index.js.map +1 -1
  40. package/package.json +6 -10
  41. package/src/typography/fonts.css +8 -16
  42. package/dist/styles/brand-override.css +0 -61
  43. package/src/styles/brand-override.css +0 -61
@@ -87,6 +87,23 @@ declare function useDebouncedValue<T>(value: T, delay?: number): T;
87
87
  */
88
88
  declare function useMediaQuery(query: string): boolean;
89
89
 
90
+ /**
91
+ * Whether a pending indicator should be on screen right now.
92
+ *
93
+ * A spinner that flashes for 80ms reads as a glitch, and one that appears and
94
+ * disappears inside 200ms reads as jank. So: stay hidden until the wait is
95
+ * noticeable (`showDelayMs`), and once shown, stay long enough to be read
96
+ * (`minVisibleMs`) even if the work finished in between. Defaults come from the
97
+ * interaction contract (Vercel Web Interface Guidelines).
98
+ *
99
+ * const visible = usePendingVisible(isPending);
100
+ * return visible ? <Spinner /> : null;
101
+ */
102
+ declare function usePendingVisible(pending: boolean, { showDelayMs, minVisibleMs, }?: {
103
+ showDelayMs?: number;
104
+ minVisibleMs?: number;
105
+ }): boolean;
106
+
90
107
  /**
91
108
  * Returns true if the user has enabled prefers-reduced-motion.
92
109
  * Use this to disable animations, skip transitions, or simplify motion.
@@ -150,4 +167,4 @@ interface DwellState {
150
167
  */
151
168
  declare function useScrollDwell(ref: RefObject<HTMLElement | null>, config?: DwellConfig): DwellState;
152
169
 
153
- export { type DwellConfig, DwellHint, type DwellHintProps, type DwellState, useDebouncedRefresh, useDebouncedValue, useMediaQuery, useReducedMotion, useScrollDwell };
170
+ export { type DwellConfig, DwellHint, type DwellHintProps, type DwellState, useDebouncedRefresh, useDebouncedValue, useMediaQuery, usePendingVisible, useReducedMotion, useScrollDwell };
@@ -1,13 +1,87 @@
1
1
  "use client";
2
- import * as React from 'react';
2
+ import * as React2 from 'react';
3
3
  import { useRef, useEffect, useCallback, useState } from 'react';
4
4
  import { useReducedMotion, AnimatePresence, motion } from 'framer-motion';
5
5
  import { clsx } from 'clsx';
6
- import { twMerge } from 'tailwind-merge';
6
+ import { extendTailwindMerge } from 'tailwind-merge';
7
7
  import { jsx, jsxs } from 'react/jsx-runtime';
8
8
  import { useDebounceCallback, useDebounceValue, useMediaQuery as useMediaQuery$1 } from 'usehooks-ts';
9
9
 
10
10
  // src/hooks/DwellHint.tsx
11
+
12
+ // src/utils/tw-merge-theme.generated.ts
13
+ var TW_MERGE_THEME = {
14
+ breakpoint: ["3xl", "shell", "tight", "xs"],
15
+ container: ["content", "text", "wide"],
16
+ ease: ["brand", "in", "in-out", "out", "spring"],
17
+ font: ["cn", "display", "heading", "mono", "sans"],
18
+ leading: ["display", "heading"],
19
+ radius: ["2xl", "3xl", "button", "card", "full", "lg", "md", "none", "panel", "sm", "xl", "xs"],
20
+ shadow: [
21
+ "2xl",
22
+ "ambient-glow",
23
+ "ambient-lg",
24
+ "ambient-md",
25
+ "ambient-sm",
26
+ "brand",
27
+ "brand-lg",
28
+ "glass-lg",
29
+ "glass-md",
30
+ "glass-sm",
31
+ "glow-accent",
32
+ "glow-accent-lg",
33
+ "glow-accent-sm",
34
+ "glow-primary",
35
+ "lg",
36
+ "md",
37
+ "sheen",
38
+ "sm",
39
+ "xl",
40
+ "xs"
41
+ ],
42
+ text: [
43
+ "2xl",
44
+ "2xs",
45
+ "3xl",
46
+ "4xl",
47
+ "5xl",
48
+ "6xl",
49
+ "7xl",
50
+ "8xl",
51
+ "9xl",
52
+ "base",
53
+ "body",
54
+ "display",
55
+ "label",
56
+ "lg",
57
+ "meta",
58
+ "sm",
59
+ "ui",
60
+ "xl",
61
+ "xs"
62
+ ],
63
+ tracking: ["display", "heading", "label", "tight"]
64
+ };
65
+ var TW_MERGE_CLASS_GROUPS = {
66
+ duration: [{ duration: ["cinematic", "flow", "micro", "reveal"] }]
67
+ };
68
+
69
+ // src/utils/cn.ts
70
+ var twMerge = extendTailwindMerge({
71
+ extend: {
72
+ theme: Object.fromEntries(
73
+ Object.entries(TW_MERGE_THEME).map(([key, values]) => [key, [...values]])
74
+ ),
75
+ classGroups: Object.fromEntries(
76
+ Object.entries(TW_MERGE_CLASS_GROUPS).map(([key, groups]) => [
77
+ key,
78
+ groups.map(
79
+ (group) => Object.fromEntries(Object.entries(group).map(([k, v]) => [k, [...v]]))
80
+ )
81
+ ])
82
+ )
83
+ }
84
+ });
11
85
  function cn(...inputs) {
12
86
  return twMerge(clsx(inputs));
13
87
  }
@@ -16,7 +90,7 @@ var POSITION_CLASSES = {
16
90
  bottom: "bottom-0 translate-y-full mt-2",
17
91
  center: "top-1/2 -translate-y-1/2"
18
92
  };
19
- var DwellHint = React.forwardRef(
93
+ var DwellHint = React2.forwardRef(
20
94
  ({ show, message, icon, position = "bottom", className, ...props }, ref) => {
21
95
  const shouldReduceMotion = useReducedMotion();
22
96
  const resolvedPosition = position ?? "bottom";
@@ -48,7 +122,7 @@ var DwellHint = React.forwardRef(
48
122
  "flex items-center gap-2 px-4 py-2.5",
49
123
  "bg-background/95 backdrop-blur-md",
50
124
  "border border-border/50 rounded-[var(--radius-lg)]",
51
- "shadow-lg shadow-black/5",
125
+ "shadow-lg shadow-foreground/5",
52
126
  "text-sm text-muted-foreground"
53
127
  ),
54
128
  children: [
@@ -97,6 +171,47 @@ function useMediaQuery(query) {
97
171
  return useMediaQuery$1(query, { initializeWithValue: false });
98
172
  }
99
173
 
174
+ // src/tokens/components/interaction.ts
175
+ var interaction = {
176
+ /**
177
+ * Pending states. A spinner that flashes for 80ms reads as a glitch; one
178
+ * that appears and vanishes in 200ms reads as jank. Show only after the
179
+ * wait is noticeable, then keep it long enough to be read.
180
+ * Vercel Web Interface Guidelines: show-delay ~150–300ms, minimum visible
181
+ * ~300–500ms.
182
+ */
183
+ pending: { showDelayMs: 200, minVisibleMs: 400 }};
184
+
185
+ // src/hooks/use-pending-visible.ts
186
+ function usePendingVisible(pending, {
187
+ showDelayMs = interaction.pending.showDelayMs,
188
+ minVisibleMs = interaction.pending.minVisibleMs
189
+ } = {}) {
190
+ const [visible, setVisible] = React2.useState(false);
191
+ const shownAt = React2.useRef(null);
192
+ React2.useEffect(() => {
193
+ if (pending) {
194
+ if (visible) return;
195
+ const timer2 = setTimeout(() => {
196
+ shownAt.current = Date.now();
197
+ setVisible(true);
198
+ }, showDelayMs);
199
+ return () => clearTimeout(timer2);
200
+ }
201
+ if (!visible) return;
202
+ const elapsed = Date.now() - (shownAt.current ?? 0);
203
+ const timer = setTimeout(
204
+ () => {
205
+ shownAt.current = null;
206
+ setVisible(false);
207
+ },
208
+ Math.max(0, minVisibleMs - elapsed)
209
+ );
210
+ return () => clearTimeout(timer);
211
+ }, [pending, visible, showDelayMs, minVisibleMs]);
212
+ return visible;
213
+ }
214
+
100
215
  // src/hooks/use-reduced-motion.ts
101
216
  function useReducedMotion2() {
102
217
  return useReducedMotion() ?? false;
@@ -199,6 +314,6 @@ function useScrollDwell(ref, config = {}) {
199
314
  };
200
315
  }
201
316
 
202
- export { DwellHint, useDebouncedRefresh, useDebouncedValue, useMediaQuery, useReducedMotion2 as useReducedMotion, useScrollDwell };
317
+ export { DwellHint, useDebouncedRefresh, useDebouncedValue, useMediaQuery, usePendingVisible, useReducedMotion2 as useReducedMotion, useScrollDwell };
203
318
  //# sourceMappingURL=index.js.map
204
319
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/cn.ts","../../src/hooks/DwellHint.tsx","../../src/hooks/use-debounced-refresh.ts","../../src/hooks/use-media-query.ts","../../src/hooks/use-reduced-motion.ts","../../src/hooks/useScrollDwell.ts"],"names":["useMediaQueryBase","useReducedMotion","useRef","useCallback","useEffect"],"mappings":"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","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 * as React from \"react\";\nimport {\n AnimatePresence,\n type HTMLMotionProps,\n motion,\n useReducedMotion,\n} from \"../shared/animation/motion\";\nimport { cn } from \"../utils/cn\";\n\nexport type DwellHintPosition = \"top\" | \"bottom\" | \"center\";\n\nexport interface DwellHintProps\n extends Omit<\n HTMLMotionProps<\"div\">,\n \"children\" | \"initial\" | \"animate\" | \"exit\" | \"transition\" | \"position\"\n > {\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?: DwellHintPosition;\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 */\nconst POSITION_CLASSES: Record<DwellHintPosition, string> = {\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\nexport const DwellHint = React.forwardRef<HTMLDivElement, DwellHintProps>(\n ({ show, message, icon, position = \"bottom\", className, ...props }, ref) => {\n const shouldReduceMotion = useReducedMotion();\n // Narrow after destructure: HTMLMotionProps intersections can widen\n // `position` to `any` under tsup DTS emit, which then fails TS7053.\n const resolvedPosition: DwellHintPosition = position ?? \"bottom\";\n\n return (\n <AnimatePresence initial={!shouldReduceMotion}>\n {show && (\n <motion.div\n ref={ref}\n className={cn(\n \"absolute left-1/2 -translate-x-1/2 z-10\",\n POSITION_CLASSES[resolvedPosition],\n className,\n )}\n initial={\n shouldReduceMotion\n ? { opacity: 1 }\n : {\n opacity: 0,\n y: resolvedPosition === \"top\" ? -10 : 10,\n scale: 0.95,\n }\n }\n animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0, scale: 1 }}\n exit={\n shouldReduceMotion\n ? { opacity: 0 }\n : { opacity: 0, y: resolvedPosition === \"top\" ? -5 : 5, scale: 0.98 }\n }\n transition={\n shouldReduceMotion\n ? { duration: 0 }\n : {\n duration: 0.4,\n ease: [0.34, 1.56, 0.64, 1],\n }\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","/**\n * Debounced Refresh Hook\n *\n * Wraps usehooks-ts (already a workspace dependency) so the rest of the\n * monorepo gets a single canonical debounced hook surface:\n * - cancellable\n * - configurable delay\n * - leading / trailing modes\n */\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { useDebounceCallback, useDebounceValue } from \"usehooks-ts\";\n\ntype DebouncedRefreshResult = {\n schedule: () => void;\n cancel: () => void;\n isPending: () => boolean;\n flush: () => void;\n};\n\n/**\n * 防抖刷新 Hook\n *\n * 用于限制频繁触发的刷新操作\n *\n * @param callback - 要执行的回调函数\n * @param delay - 延迟时间 (毫秒)\n * @returns 控制对象 { schedule, cancel, isPending, flush }\n *\n * @example\n * const { schedule, cancel } = useDebouncedRefresh(() => {\n * refetch();\n * }, 400);\n *\n * // 在数据变化时调用\n * useEffect(() => {\n * schedule();\n * }, [data]);\n */\nexport function useDebouncedRefresh(callback: () => void, delay = 400): DebouncedRefreshResult {\n const callbackRef = useRef(callback);\n\n // 保持回调引用最新\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n // 使用 usehooks-ts 的 useDebounceCallback\n const debounced = useDebounceCallback(\n () => {\n callbackRef.current();\n },\n delay,\n { leading: false, trailing: true },\n );\n\n const schedule = useCallback(() => {\n debounced();\n }, [debounced]);\n\n const cancel = useCallback(() => {\n debounced.cancel();\n }, [debounced]);\n\n const isPending = useCallback(() => {\n return debounced.isPending();\n }, [debounced]);\n\n const flush = useCallback(() => {\n debounced.flush();\n }, [debounced]);\n\n // 组件卸载时取消\n useEffect(() => cancel, [cancel]);\n\n return { schedule, cancel, isPending, flush };\n}\n\n/**\n * 防抖值 Hook\n *\n * Thin wrapper over usehooks-ts useDebounceValue so consumers can import\n * the canonical hook from @nebutra/ui/hooks alongside useDebouncedRefresh.\n *\n * @param value - 要防抖的值\n * @param delay - 延迟时间 (毫秒)\n * @returns 防抖后的值\n *\n * @example\n * const [searchTerm, setSearchTerm] = useState(\"\");\n * const debouncedSearch = useDebouncedValue(searchTerm, 300);\n *\n * useEffect(() => {\n * if (debouncedSearch) {\n * performSearch(debouncedSearch);\n * }\n * }, [debouncedSearch]);\n */\nexport function useDebouncedValue<T>(value: T, delay = 500): T {\n const [debouncedValue] = useDebounceValue(value, delay);\n return debouncedValue;\n}\n","import { useMediaQuery as useMediaQueryBase } from \"usehooks-ts\";\n\n/**\n * Hook to detect media query matches.\n *\n * Delegates to usehooks-ts `useMediaQuery` with `initializeWithValue: false`\n * so the initial render always returns `false` — SSR-safe, matching the\n * previous hand-rolled behaviour (no `window` access during SSR/hydration).\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 return useMediaQueryBase(query, { initializeWithValue: false });\n}\n","\"use client\";\n\nimport { useReducedMotion as useSharedReducedMotion } from \"../shared/animation/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 useSharedReducedMotion() ?? 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"]}
1
+ {"version":3,"sources":["../../src/utils/tw-merge-theme.generated.ts","../../src/utils/cn.ts","../../src/hooks/DwellHint.tsx","../../src/hooks/use-debounced-refresh.ts","../../src/hooks/use-media-query.ts","../../src/tokens/components/interaction.ts","../../src/hooks/use-pending-visible.ts","../../src/hooks/use-reduced-motion.ts","../../src/hooks/useScrollDwell.ts"],"names":["React","useMediaQueryBase","timer","useReducedMotion","useState","useRef","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["// GENERATED by packages/design/ui/scripts/gen-tw-merge-theme.mjs — do not edit.\n// Source: the @theme blocks in packages/design/tokens/{styles,recipe}.css.\n\nexport const TW_MERGE_THEME = {\n breakpoint: [\"3xl\", \"shell\", \"tight\", \"xs\"],\n container: [\"content\", \"text\", \"wide\"],\n ease: [\"brand\", \"in\", \"in-out\", \"out\", \"spring\"],\n font: [\"cn\", \"display\", \"heading\", \"mono\", \"sans\"],\n leading: [\"display\", \"heading\"],\n radius: [\"2xl\", \"3xl\", \"button\", \"card\", \"full\", \"lg\", \"md\", \"none\", \"panel\", \"sm\", \"xl\", \"xs\"],\n shadow: [\n \"2xl\",\n \"ambient-glow\",\n \"ambient-lg\",\n \"ambient-md\",\n \"ambient-sm\",\n \"brand\",\n \"brand-lg\",\n \"glass-lg\",\n \"glass-md\",\n \"glass-sm\",\n \"glow-accent\",\n \"glow-accent-lg\",\n \"glow-accent-sm\",\n \"glow-primary\",\n \"lg\",\n \"md\",\n \"sheen\",\n \"sm\",\n \"xl\",\n \"xs\",\n ],\n text: [\n \"2xl\",\n \"2xs\",\n \"3xl\",\n \"4xl\",\n \"5xl\",\n \"6xl\",\n \"7xl\",\n \"8xl\",\n \"9xl\",\n \"base\",\n \"body\",\n \"display\",\n \"label\",\n \"lg\",\n \"meta\",\n \"sm\",\n \"ui\",\n \"xl\",\n \"xs\",\n ],\n tracking: [\"display\", \"heading\", \"label\", \"tight\"],\n} as const;\n\nexport const TW_MERGE_CLASS_GROUPS = {\n duration: [{ duration: [\"cinematic\", \"flow\", \"micro\", \"reveal\"] }],\n} as const;\n","import { type ClassValue, clsx } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\nimport { TW_MERGE_CLASS_GROUPS, TW_MERGE_THEME } from \"./tw-merge-theme.generated\";\n\n/**\n * tailwind-merge only knows Tailwind's default scale. Every utility the design\n * tokens add (text-label, shadow-ambient-md, rounded-card, ease-brand,\n * max-w-wide, …) was otherwise an unknown class: `cn(\"text-label\n * text-destructive-strong\")` returned \"text-destructive-strong\" and the size fell away, and\n * `cn(\"rounded-card\", \"rounded-lg\")` kept both so the winner depended on\n * stylesheet order. The theme below is generated from the tokens' @theme blocks\n * (scripts/gen-tw-merge-theme.mjs), so a new token utility is known here the\n * moment it exists; a test fails if the generated file goes stale.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: Object.fromEntries(\n Object.entries(TW_MERGE_THEME).map(([key, values]) => [key, [...values]]),\n ),\n classGroups: Object.fromEntries(\n Object.entries(TW_MERGE_CLASS_GROUPS).map(([key, groups]) => [\n key,\n groups.map((group) =>\n Object.fromEntries(Object.entries(group).map(([k, v]) => [k, [...v]])),\n ),\n ]),\n ),\n },\n});\n\n/** Kept for callers and tests that enumerate the design-system text steps. */\nexport const DESIGN_SYSTEM_TEXT_SIZES = TW_MERGE_THEME.text;\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n AnimatePresence,\n type HTMLMotionProps,\n motion,\n useReducedMotion,\n} from \"../shared/animation/motion\";\nimport { cn } from \"../utils/cn\";\n\nexport type DwellHintPosition = \"top\" | \"bottom\" | \"center\";\n\nexport interface DwellHintProps\n extends Omit<\n HTMLMotionProps<\"div\">,\n \"children\" | \"initial\" | \"animate\" | \"exit\" | \"transition\" | \"position\"\n > {\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?: DwellHintPosition;\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 */\nconst POSITION_CLASSES: Record<DwellHintPosition, string> = {\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\nexport const DwellHint = React.forwardRef<HTMLDivElement, DwellHintProps>(\n ({ show, message, icon, position = \"bottom\", className, ...props }, ref) => {\n const shouldReduceMotion = useReducedMotion();\n // Narrow after destructure: HTMLMotionProps intersections can widen\n // `position` to `any` under tsup DTS emit, which then fails TS7053.\n const resolvedPosition: DwellHintPosition = position ?? \"bottom\";\n\n return (\n <AnimatePresence initial={!shouldReduceMotion}>\n {show && (\n <motion.div\n ref={ref}\n className={cn(\n \"absolute left-1/2 -translate-x-1/2 z-10\",\n POSITION_CLASSES[resolvedPosition],\n className,\n )}\n initial={\n shouldReduceMotion\n ? { opacity: 1 }\n : {\n opacity: 0,\n y: resolvedPosition === \"top\" ? -10 : 10,\n scale: 0.95,\n }\n }\n animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0, scale: 1 }}\n exit={\n shouldReduceMotion\n ? { opacity: 0 }\n : { opacity: 0, y: resolvedPosition === \"top\" ? -5 : 5, scale: 0.98 }\n }\n transition={\n shouldReduceMotion\n ? { duration: 0 }\n : {\n duration: 0.4,\n ease: [0.34, 1.56, 0.64, 1],\n }\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-foreground/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","/**\n * Debounced Refresh Hook\n *\n * Wraps usehooks-ts (already a workspace dependency) so the rest of the\n * monorepo gets a single canonical debounced hook surface:\n * - cancellable\n * - configurable delay\n * - leading / trailing modes\n */\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { useDebounceCallback, useDebounceValue } from \"usehooks-ts\";\n\ntype DebouncedRefreshResult = {\n schedule: () => void;\n cancel: () => void;\n isPending: () => boolean;\n flush: () => void;\n};\n\n/**\n * 防抖刷新 Hook\n *\n * 用于限制频繁触发的刷新操作\n *\n * @param callback - 要执行的回调函数\n * @param delay - 延迟时间 (毫秒)\n * @returns 控制对象 { schedule, cancel, isPending, flush }\n *\n * @example\n * const { schedule, cancel } = useDebouncedRefresh(() => {\n * refetch();\n * }, 400);\n *\n * // 在数据变化时调用\n * useEffect(() => {\n * schedule();\n * }, [data]);\n */\nexport function useDebouncedRefresh(callback: () => void, delay = 400): DebouncedRefreshResult {\n const callbackRef = useRef(callback);\n\n // 保持回调引用最新\n useEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n // 使用 usehooks-ts 的 useDebounceCallback\n const debounced = useDebounceCallback(\n () => {\n callbackRef.current();\n },\n delay,\n { leading: false, trailing: true },\n );\n\n const schedule = useCallback(() => {\n debounced();\n }, [debounced]);\n\n const cancel = useCallback(() => {\n debounced.cancel();\n }, [debounced]);\n\n const isPending = useCallback(() => {\n return debounced.isPending();\n }, [debounced]);\n\n const flush = useCallback(() => {\n debounced.flush();\n }, [debounced]);\n\n // 组件卸载时取消\n useEffect(() => cancel, [cancel]);\n\n return { schedule, cancel, isPending, flush };\n}\n\n/**\n * 防抖值 Hook\n *\n * Thin wrapper over usehooks-ts useDebounceValue so consumers can import\n * the canonical hook from @nebutra/ui/hooks alongside useDebouncedRefresh.\n *\n * @param value - 要防抖的值\n * @param delay - 延迟时间 (毫秒)\n * @returns 防抖后的值\n *\n * @example\n * const [searchTerm, setSearchTerm] = useState(\"\");\n * const debouncedSearch = useDebouncedValue(searchTerm, 300);\n *\n * useEffect(() => {\n * if (debouncedSearch) {\n * performSearch(debouncedSearch);\n * }\n * }, [debouncedSearch]);\n */\nexport function useDebouncedValue<T>(value: T, delay = 500): T {\n const [debouncedValue] = useDebounceValue(value, delay);\n return debouncedValue;\n}\n","import { useMediaQuery as useMediaQueryBase } from \"usehooks-ts\";\n\n/**\n * Hook to detect media query matches.\n *\n * Delegates to usehooks-ts `useMediaQuery` with `initializeWithValue: false`\n * so the initial render always returns `false` — SSR-safe, matching the\n * previous hand-rolled behaviour (no `window` access during SSR/hydration).\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 return useMediaQueryBase(query, { initializeWithValue: false });\n}\n","/**\n * Interaction contract — the timing and feel every product surface shares.\n *\n * One place for the numbers that make an interface feel deliberate rather than\n * merely animated. Each value is taken from a shipped, measured source rather\n * than tuned by eye; the source is named beside it so a change has to argue\n * with the evidence. Components read these; apps never restate them.\n *\n * Sources: sonner, vaul and Radix Tooltip source code; Vercel Web Interface\n * Guidelines; Emil Kowalski's animation guidance (author of sonner and vaul).\n */\n\nexport const interaction = {\n /**\n * Tooltips. The first one waits so a sweeping pointer does not strobe;\n * once one is open, its neighbours open instantly if the pointer arrives\n * within the skip window. Radix Tooltip defaults: delayDuration 700,\n * skipDelayDuration 300. Vercel/Kowalski: \"delay the first tooltip only\".\n */\n tooltip: { delayMs: 700, skipWindowMs: 300 },\n\n /**\n * Pending states. A spinner that flashes for 80ms reads as a glitch; one\n * that appears and vanishes in 200ms reads as jank. Show only after the\n * wait is noticeable, then keep it long enough to be read.\n * Vercel Web Interface Guidelines: show-delay ~150–300ms, minimum visible\n * ~300–500ms.\n */\n pending: { showDelayMs: 200, minVisibleMs: 400 },\n\n /**\n * Press feedback. A slight scale-down on :active, fast. Kowalski:\n * scale(0.97) at ~150ms. Never animate keyboard-initiated activation.\n */\n press: { scale: 0.97, durationMs: 150 },\n\n /**\n * Entering overlays start close to their final size, never from zero.\n * Kowalski: \"never enter from scale(0)\"; 0.9–0.95 reads as arrival.\n */\n enter: { fromScale: 0.96 },\n\n /**\n * Enter/exit asymmetry. Things arrive with a considered ease-out and leave\n * faster: the user already decided they are done. sonner: enter 300ms,\n * exit 200ms.\n */\n toast: { enterMs: 300, exitMs: 200, lifetimeMs: 4000, stackGapPx: 14 },\n\n /**\n * Sheets and drawers. vaul uses one curve for every drawer transition:\n * cubic-bezier(0.32, 0.72, 0, 1) over 500ms.\n */\n drawer: { ease: [0.32, 0.72, 0, 1] as const, durationMs: 500 },\n\n /**\n * Mutation latency budget. Past this, show optimistic state or progress.\n * Vercel Web Interface Guidelines: keep mutations under 500ms.\n */\n mutationBudgetMs: 500,\n} as const;\n\nexport type Interaction = typeof interaction;\n","\"use client\";\n\nimport * as React from \"react\";\nimport { interaction } from \"../tokens/components/interaction\";\n\n/**\n * Whether a pending indicator should be on screen right now.\n *\n * A spinner that flashes for 80ms reads as a glitch, and one that appears and\n * disappears inside 200ms reads as jank. So: stay hidden until the wait is\n * noticeable (`showDelayMs`), and once shown, stay long enough to be read\n * (`minVisibleMs`) even if the work finished in between. Defaults come from the\n * interaction contract (Vercel Web Interface Guidelines).\n *\n * const visible = usePendingVisible(isPending);\n * return visible ? <Spinner /> : null;\n */\nexport function usePendingVisible(\n pending: boolean,\n {\n showDelayMs = interaction.pending.showDelayMs,\n minVisibleMs = interaction.pending.minVisibleMs,\n }: { showDelayMs?: number; minVisibleMs?: number } = {},\n): boolean {\n const [visible, setVisible] = React.useState(false);\n const shownAt = React.useRef<number | null>(null);\n\n React.useEffect(() => {\n if (pending) {\n if (visible) return;\n const timer = setTimeout(() => {\n shownAt.current = Date.now();\n setVisible(true);\n }, showDelayMs);\n return () => clearTimeout(timer);\n }\n if (!visible) return;\n const elapsed = Date.now() - (shownAt.current ?? 0);\n const timer = setTimeout(\n () => {\n shownAt.current = null;\n setVisible(false);\n },\n Math.max(0, minVisibleMs - elapsed),\n );\n return () => clearTimeout(timer);\n }, [pending, visible, showDelayMs, minVisibleMs]);\n\n return visible;\n}\n","\"use client\";\n\nimport { useReducedMotion as useSharedReducedMotion } from \"../shared/animation/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 useSharedReducedMotion() ?? 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"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { NebutraThemeProvider, NebutraThemeProviderProps, NebutraTokens, ThemeMode } from './theme/index.js';
1
+ export { NebutraThemeProvider, NebutraThemeProviderProps, ThemeMode } from './theme/index.js';
2
2
  export { Anthropic, Claude, Gemini, Google, OpenAI } from '@lobehub/icons';
3
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
4
  import 'react/jsx-runtime';
@@ -1,7 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { ReactNode } from 'react';
4
+ export { C as Card, a as CardProps } from '../card-DunB1gKE.js';
4
5
  import { ThemeMode } from '../theme/index.js';
6
+ export { E as EmptyState, a as EmptyStateProps } from '../empty-state-bmfHCOuN.js';
7
+ import 'class-variance-authority/types';
8
+ import 'class-variance-authority';
5
9
  import '@lobehub/icons';
6
10
  import '@nebutra/icons';
7
11
 
@@ -32,6 +36,12 @@ interface AppShellProps {
32
36
  contentClassName?: string;
33
37
  /** Extra classes for the outer wrapper. */
34
38
  className?: string;
39
+ /** Accessible name for the primary sidebar landmark. Pass a translated string. */
40
+ primaryNavLabel?: string;
41
+ /** Accessible name for the overlay-collapse button. Pass a translated string. */
42
+ collapseNavigationLabel?: string;
43
+ /** Accessible name for the mobile hamburger button. Pass a translated string. */
44
+ openNavigationMenuLabel?: string;
35
45
  }
36
46
  /**
37
47
  * AppShell — top-level dashboard chrome: sidebar + sticky header + scrollable main.
@@ -56,29 +66,7 @@ interface AppShellProps {
56
66
  * </AppShell>
57
67
  * ```
58
68
  */
59
- declare function AppShell({ sidebar, header, children, sidebarWidth, sidebarCollapsedWidth, collapsed, defaultCollapsed, onCollapsedChange, headerHeight, contentClassName, className, overlay, }: AppShellProps): react_jsx_runtime.JSX.Element;
60
-
61
- interface CardProps {
62
- children: React__default.ReactNode;
63
- className?: string;
64
- /** Whether the card has a hover/press effect */
65
- isInteractive?: boolean;
66
- }
67
- /**
68
- * Card — surface container with border, background, and shadow.
69
- *
70
- * @status stable
71
- * @planned apps/web dashboard — settings panels, billing summary, team member tiles.
72
- * See governance/registry.ts for full allocation record.
73
- *
74
- * @example
75
- * ```tsx
76
- * <Card>
77
- * <p>Card content</p>
78
- * </Card>
79
- * ```
80
- */
81
- declare function Card({ children, isInteractive, className }: CardProps): react_jsx_runtime.JSX.Element;
69
+ declare function AppShell({ sidebar, header, children, sidebarWidth, sidebarCollapsedWidth, collapsed, defaultCollapsed, onCollapsedChange, headerHeight, contentClassName, className, overlay, primaryNavLabel, collapseNavigationLabel, openNavigationMenuLabel, }: AppShellProps): react_jsx_runtime.JSX.Element;
82
70
 
83
71
  type ContainerSize = "sm" | "md" | "lg" | "xl" | "full";
84
72
  interface ContainerProps {
@@ -110,40 +98,6 @@ interface DesignSystemProviderProps {
110
98
  }
111
99
  declare function DesignSystemProvider({ children, defaultMode, storageKey, enableSystemPreference, }: DesignSystemProviderProps): react_jsx_runtime.JSX.Element;
112
100
 
113
- type Tone = "default" | "branded" | "subtle";
114
- type Size = "sm" | "md" | "lg";
115
- interface EmptyStateProps {
116
- /** Primary message. */
117
- title: string;
118
- /** Supporting description. */
119
- description?: string;
120
- /** Visual anchor. When omitted and `tone="branded"`, a default `<BrandMark>` is rendered. */
121
- mascot?: ReactNode;
122
- /** Inline icon shown above the title. Ignored when `mascot` is provided. */
123
- icon?: ReactNode;
124
- /** Primary call-to-action node. */
125
- action?: ReactNode;
126
- /** Secondary call-to-action node. */
127
- secondaryAction?: ReactNode;
128
- /**
129
- * Visual tone.
130
- * - `default`: neutral bordered state
131
- * - `branded`: BrandMark anchor for first-touch panels
132
- * - `subtle`: quieter inline state
133
- */
134
- tone?: Tone;
135
- /** Size variant. */
136
- size?: Size;
137
- /** Extra classes for the root element. */
138
- className?: string;
139
- }
140
- /**
141
- * EmptyState — layout-compatible wrapper around the canonical compound
142
- * primitive. New composition work should prefer `EmptyState.Root` from
143
- * `@nebutra/ui/primitives`; product layout code can keep this simpler API.
144
- */
145
- declare function EmptyState({ title, description, mascot, icon, action, secondaryAction, tone, size, className, }: EmptyStateProps): react_jsx_runtime.JSX.Element;
146
-
147
101
  interface ErrorStateProps {
148
102
  /** Error title */
149
103
  title?: string;
@@ -240,6 +194,11 @@ interface LoadingStateProps {
240
194
  /**
241
195
  * LoadingState — centred spinner for async content loading.
242
196
  *
197
+ * Mounted means pending, so the spinner itself waits
198
+ * interaction.pending.showDelayMs before appearing: a fast load never flashes
199
+ * one. The block keeps its height while it waits, so nothing shifts when it
200
+ * does appear.
201
+ *
243
202
  * @status stable
244
203
  * @planned apps/web dashboard — React Suspense fallback boundaries for async data routes.
245
204
  *
@@ -297,4 +256,4 @@ interface SectionProps {
297
256
  */
298
257
  declare function Section({ children, label, className }: SectionProps): react_jsx_runtime.JSX.Element;
299
258
 
300
- export { AppShell, type AppShellProps, Card, type CardProps, Container, type ContainerProps, type ContainerSize, DesignSystemProvider, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, FullPageStatus, type FullPageStatusAction, type FullPageStatusMeta, type FullPageStatusProps, LoadingState, type LoadingStateProps, PageHeader, type PageHeaderProps, Section, type SectionProps };
259
+ export { AppShell, type AppShellProps, Container, type ContainerProps, type ContainerSize, DesignSystemProvider, ErrorState, type ErrorStateProps, FullPageStatus, type FullPageStatusAction, type FullPageStatusMeta, type FullPageStatusProps, LoadingState, type LoadingStateProps, PageHeader, type PageHeaderProps, Section, type SectionProps };