@devalok/shilp-sutra 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -0
- package/MIGRATION.md +32 -0
- package/dist/_chunks/chat.js +1 -1
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js +1 -1
- package/dist/_chunks/mention-suggestion.js.map +1 -1
- package/dist/_chunks/popover.js +1 -1
- package/dist/_chunks/popover.js.map +1 -1
- package/dist/_chunks/shared.js +1 -1
- package/dist/_chunks/shared.js.map +1 -1
- package/dist/ai/command-bar.js +1 -1
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/composed/bulk-action-bar.js +1 -1
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/command-palette.js +1 -1
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/content-card.js +1 -1
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/error-boundary.js +1 -1
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/extensions/emoji-suggestion.js +1 -1
- package/dist/composed/extensions/emoji-suggestion.js.map +1 -1
- package/dist/composed/file-preview.js +1 -1
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/rich-chat-input.js +5 -5
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +2 -2
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/shell/notification-center.js +1 -1
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/sidebar.d.ts +6 -0
- package/dist/shell/sidebar.d.ts.map +1 -1
- package/dist/shell/sidebar.js +27 -18
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/top-bar.js +1 -1
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/tokens/semantic.css +8 -2
- package/dist/ui/alert-dialog.js +1 -1
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/autocomplete.js +1 -1
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/card.d.ts.map +1 -1
- package/dist/ui/card.js +3 -3
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/color-input.js +1 -1
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/combobox.js +1 -1
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/context-menu.js +2 -2
- package/dist/ui/context-menu.js.map +1 -1
- package/dist/ui/data-table-bulk-actions.js +1 -1
- package/dist/ui/data-table-bulk-actions.js.map +1 -1
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dropdown-menu.js +2 -2
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/hover-card.js +1 -1
- package/dist/ui/hover-card.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +20 -19
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input-otp.js +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/menubar.js +3 -3
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/navigation-menu.js +1 -1
- package/dist/ui/navigation-menu.js.map +1 -1
- package/dist/ui/select.js +1 -1
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sidebar.js +1 -1
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/split-button.js +1 -1
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stat-card.d.ts +22 -2
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +101 -86
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stat-flash.d.ts +46 -0
- package/dist/ui/stat-flash.d.ts.map +1 -0
- package/dist/ui/stat-flash.js +114 -0
- package/dist/ui/stat-flash.js.map +1 -0
- package/dist/ui/toast.js +2 -2
- package/dist/ui/toast.js.map +1 -1
- package/docs/components/ui/stat-card.md +13 -3
- package/docs/components/ui/stat-flash.md +44 -0
- package/llms-full.txt +58 -4
- package/llms.txt +4 -0
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
- package/skill/references/components-full.md +58 -4
- package/skill/references/components.md +4 -0
package/dist/ui/toast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":[],"sources":["../../src/ui/toast.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertCircle,\n IconAlertTriangle,\n IconCheck,\n IconCircleCheck,\n IconCircleX,\n IconFile,\n IconInfoCircle,\n IconPhoto,\n IconRefresh,\n IconUpload,\n IconX,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { type ExternalToast,toast as sonnerToast } from 'sonner'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Progress } from './progress'\nimport { Spinner } from './spinner'\nimport { assertToasterMounted } from './toast-registry'\nimport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n\n/* ---------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------ */\n\nconst DEFAULT_DURATION = 5000\nconst UNDO_DURATION = 8000\nconst UPLOAD_COMPLETE_DELAY = 3000\n\n/* ---------------------------------------------------------------------------\n * Accent bar & icon config per toast type\n * ------------------------------------------------------------------------ */\n\nconst TOAST_TYPE_CONFIG: Record<\n ToastType,\n {\n accentClass: string\n iconClass: string\n icon: React.ForwardRefExoticComponent<any> | null\n timerBarClass: string\n }\n> = {\n message: {\n accentClass: '',\n iconClass: '',\n icon: null,\n timerBarClass: 'bg-surface-border',\n },\n success: {\n accentClass: 'bg-success-9',\n iconClass: 'text-success-11',\n icon: IconCircleCheck,\n timerBarClass: 'bg-success-9',\n },\n error: {\n accentClass: 'bg-error-9',\n iconClass: 'text-error-11',\n icon: IconCircleX,\n timerBarClass: 'bg-error-9',\n },\n warning: {\n accentClass: 'bg-warning-9',\n iconClass: 'text-warning-11',\n icon: IconAlertTriangle,\n timerBarClass: 'bg-warning-9',\n },\n info: {\n accentClass: 'bg-info-9',\n iconClass: 'text-info-11',\n icon: IconInfoCircle,\n timerBarClass: 'bg-info-9',\n },\n loading: {\n accentClass: 'bg-accent-9',\n iconClass: 'text-accent-11',\n icon: null,\n timerBarClass: 'bg-accent-9',\n },\n}\n\n/* ---------------------------------------------------------------------------\n * Timer bar — shows auto-dismiss countdown\n * ------------------------------------------------------------------------ */\n\nfunction TimerBar({\n duration,\n type,\n paused,\n}: {\n duration: number\n type: ToastType\n paused: boolean\n}) {\n const config = TOAST_TYPE_CONFIG[type]\n return (\n <div className=\"absolute bottom-0 left-0 right-0 h-[2px] overflow-hidden\">\n <div\n className={cn(\n 'h-full w-full origin-left opacity-30',\n config.timerBarClass,\n 'motion-safe:animate-timer-bar',\n )}\n style={{\n animationDuration: `${duration}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }}\n />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Base toast UI — used by all typed toasts\n * ------------------------------------------------------------------------ */\n\nfunction ToastContent({\n type,\n title,\n description,\n action,\n cancel,\n duration = DEFAULT_DURATION,\n showTimerBar = true,\n selfDismissId,\n}: {\n type: ToastType\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionOptions\n cancel?: ToastActionOptions\n duration?: number\n showTimerBar?: boolean\n /** When set, this component manages its own dismiss timer (hover-aware). */\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const config = TOAST_TYPE_CONFIG[type]\n const StatusIcon = config.icon\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(duration)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || duration === Infinity) return\n if (paused) {\n // Pause: save remaining time\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n // Start/resume timer\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, duration, paused])\n\n // Error toasts announce assertively so screen readers interrupt;\n // warnings stay polite but atomically re-read the message.\n const isUrgent = type === 'error'\n return (\n <motion.div\n layout=\"position\"\n role={isUrgent ? 'alert' : 'status'}\n aria-live={isUrgent ? 'assertive' : 'polite'}\n aria-atomic=\"true\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm border border-surface-border-strong bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n {config.accentClass && (\n <div\n className={cn('w-1 shrink-0 rounded-l-overlay-sm', config.accentClass)}\n />\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 items-start gap-ds-03 p-ds-04\">\n {/* Status icon — animates between spinner ↔ typed icon */}\n <AnimatePresence mode=\"wait\">\n {type === 'loading' ? (\n <motion.div\n key=\"loading\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n className=\"mt-0.5 shrink-0\"\n >\n <Spinner size=\"sm\" className=\"h-4 w-4\" />\n </motion.div>\n ) : StatusIcon ? (\n <motion.div\n key={type}\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n className=\"mt-0.5 shrink-0\"\n >\n <Icon icon={StatusIcon} size=\"sm\" className={config.iconClass} />\n </motion.div>\n ) : null}\n </AnimatePresence>\n\n {/* Text */}\n <div className=\"min-w-0 flex-1\">\n {title && (\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {title}\n </p>\n )}\n {description && (\n <p\n className={cn(\n 'text-ds-sm text-surface-fg-muted',\n title && 'mt-0.5',\n )}\n >\n {description}\n </p>\n )}\n\n {/* Action / cancel buttons */}\n {(action || cancel) && (\n <div className=\"mt-ds-03 flex items-center gap-ds-03\">\n {action && (\n <button\n type=\"button\"\n onClick={action.onClick}\n className=\"text-ds-sm font-medium text-accent-11 underline-offset-2 hover:underline hover:bg-surface-raised-hover rounded-control-inner px-ds-02 py-ds-01 transition-[color,background-color] duration-fast-01 ease-productive-standard focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {action.label}\n </button>\n )}\n {cancel && (\n <button\n type=\"button\"\n onClick={cancel.onClick}\n className=\"text-ds-sm text-surface-fg-muted hover:text-surface-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {cancel.label}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n\n {/* Timer bar */}\n {showTimerBar && type !== 'loading' && (\n <TimerBar duration={duration} type={type} paused={paused} />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Upload toast UI\n * ------------------------------------------------------------------------ */\n\nconst IMAGE_EXTENSIONS = new Set([\n 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif',\n])\n\nfunction isImageFile(file: UploadFile): boolean {\n if (file.previewUrl) return true\n const ext = file.name.split('.').pop()?.toLowerCase() ?? ''\n return IMAGE_EXTENSIONS.has(ext)\n}\n\nfunction getProgressColor(\n status: UploadFile['status'],\n): 'default' | 'success' | 'error' {\n switch (status) {\n case 'complete':\n return 'success'\n case 'error':\n return 'error'\n default:\n return 'default'\n }\n}\n\nfunction getProgressValue(file: UploadFile): number | undefined {\n switch (file.status) {\n case 'pending':\n return 0\n case 'uploading':\n return file.progress ?? 0\n case 'processing':\n return undefined\n case 'complete':\n return 100\n case 'error':\n return file.progress ?? 0\n }\n}\n\nfunction formatFileSize(bytes: number): string {\n if (!bytes || bytes < 0 || !Number.isFinite(bytes)) return '0 B'\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`\n}\n\nfunction UploadFileRow({\n file,\n onRetry,\n onRemove,\n}: {\n file: UploadFile\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n}) {\n const isTerminal = file.status === 'complete' || file.status === 'error'\n const isUploading =\n file.status === 'uploading' || file.status === 'processing'\n\n return (\n <motion.div\n layout\n className=\"flex items-center gap-ds-02 py-1\"\n initial={false}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, x: -20, height: 0 }}\n transition={springs.snappy}\n >\n {/* File icon — animates between states */}\n <div className=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <AnimatePresence mode=\"wait\">\n {file.status === 'complete' ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"xs\" className=\"text-success-11\" />\n </motion.div>\n ) : file.status === 'error' ? (\n <motion.div\n key=\"error\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Icon icon={IconAlertCircle} size=\"xs\" className=\"text-error-11\" />\n </motion.div>\n ) : file.status === 'processing' ? (\n <motion.div\n key=\"processing\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Spinner size=\"sm\" className=\"h-3.5 w-3.5\" />\n </motion.div>\n ) : isImageFile(file) ? (\n <motion.div\n key=\"image\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconPhoto} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"file\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconFile} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n\n {/* Filename */}\n <span className=\"min-w-0 flex-1 truncate text-ds-xs text-surface-fg\">\n {file.name}\n </span>\n\n {/* Progress bar or status */}\n {isUploading ? (\n <div className=\"flex w-16 items-center gap-1\">\n <Progress\n size=\"sm\"\n color={getProgressColor(file.status)}\n value={getProgressValue(file)}\n className=\"flex-1\"\n />\n {file.progress !== undefined && (\n <span className=\"shrink-0 text-ds-xs tabular-nums text-surface-fg-muted\">\n {file.progress}%\n </span>\n )}\n </div>\n ) : file.status === 'complete' ? (\n <span className=\"text-ds-xs text-success-11\">Done</span>\n ) : file.status === 'error' ? (\n <span className=\"max-w-[60px] truncate text-ds-xs text-error-11\">\n {file.error || 'Failed'}\n </span>\n ) : (\n <span className=\"text-ds-xs text-surface-fg-muted\">\n {formatFileSize(file.size)}\n </span>\n )}\n\n {/* Actions */}\n {file.status === 'error' && onRetry && (\n <button\n type=\"button\"\n onClick={() => onRetry(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Retry ${file.name}`}\n title=\"Retry\"\n >\n <IconRefresh className=\"h-3.5 w-3.5\" />\n </button>\n )}\n {!isTerminal && onRemove && (\n <button\n type=\"button\"\n onClick={() => onRemove(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Cancel ${file.name}`}\n title=\"Remove\"\n >\n <IconX className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </motion.div>\n )\n}\n\nfunction UploadToastContent({\n files,\n onRetry,\n onRemove,\n selfDismissId,\n}: {\n files: UploadFile[]\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const completeCount = files.filter((f) => f.status === 'complete').length\n const errorCount = files.filter((f) => f.status === 'error').length\n const allTerminal = files.every(\n (f) => f.status === 'complete' || f.status === 'error',\n )\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(UPLOAD_COMPLETE_DELAY)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || !allTerminal) return\n if (paused) {\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, allTerminal, paused])\n\n const accentClass = allTerminal\n ? errorCount > 0\n ? 'bg-error-7'\n : 'bg-success-7'\n : 'bg-accent-9'\n\n const timerBarType: ToastType = allTerminal\n ? errorCount > 0\n ? 'error'\n : 'success'\n : 'loading'\n\n const uploadUrgent = allTerminal && errorCount > 0\n return (\n <motion.div\n layout\n role={uploadUrgent ? 'alert' : 'status'}\n aria-live={uploadUrgent ? 'assertive' : 'polite'}\n aria-label=\"File uploads\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm border border-surface-border-strong bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n <div className={cn('w-1 shrink-0 rounded-l-overlay-sm', accentClass)} />\n\n {/* Content */}\n <div className=\"min-w-0 flex-1 p-ds-04\">\n {/* Header — icon morphs between upload ↔ result */}\n <div className=\"flex items-center gap-ds-02\">\n <AnimatePresence mode=\"wait\">\n {allTerminal ? (\n errorCount > 0 ? (\n <motion.div\n key=\"error-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconAlertCircle} size=\"sm\" className=\"shrink-0 text-error-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"success-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"shrink-0 text-success-11\" />\n </motion.div>\n )\n ) : (\n <motion.div\n key=\"upload-icon\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"sm\" className=\"shrink-0 text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {allTerminal\n ? errorCount > 0\n ? `${completeCount} of ${files.length} uploaded`\n : `${files.length} file${files.length > 1 ? 's' : ''} uploaded`\n : `Uploading ${files.length} file${files.length > 1 ? 's' : ''}`}\n </p>\n </div>\n {!allTerminal && (\n <p className=\"mt-0.5 text-ds-xs text-surface-fg-muted\">\n {completeCount} of {files.length} complete\n {errorCount > 0 && ` · ${errorCount} failed`}\n </p>\n )}\n\n {/* File list */}\n <div className=\"mt-ds-02 max-h-[140px] overflow-y-auto\">\n <AnimatePresence>\n {files.map((file) => (\n <UploadFileRow\n key={file.id}\n file={file}\n onRetry={onRetry}\n onRemove={onRemove}\n />\n ))}\n </AnimatePresence>\n </div>\n\n {/* Screen reader announcement */}\n <div className=\"sr-only\" aria-live=\"polite\">\n {completeCount} of {files.length} files uploaded\n {errorCount > 0 && `, ${errorCount} failed`}\n </div>\n </div>\n\n {/* Timer bar — only after all terminal */}\n {allTerminal && (\n <TimerBar\n duration={UPLOAD_COMPLETE_DELAY}\n type={timerBarType}\n paused={false}\n />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toast API — wraps Sonner with our styled UI\n * ------------------------------------------------------------------------ */\n\nfunction createSonnerOptions(\n options?: ToastOptions,\n duration?: number,\n): ExternalToast {\n return {\n id: options?.id,\n duration: options?.duration ?? duration ?? DEFAULT_DURATION,\n unstyled: true,\n classNames: { toast: 'w-full' },\n }\n}\n\nfunction createTypedToast(\n type: ToastType,\n message: string | React.ReactNode,\n options?: ToastOptions,\n) {\n assertToasterMounted()\n const duration = options?.duration ?? DEFAULT_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n description={options?.description}\n action={options?.action}\n cancel={options?.cancel}\n duration={duration}\n />\n ),\n createSonnerOptions(options, duration),\n )\n}\n\n/**\n * Toast API — imperative toast notifications with Sonner rendering engine.\n *\n * Mount `<Toaster />` once at your root layout. Then call `toast()` from anywhere.\n *\n * @example\n * toast('Something happened')\n * toast.success('Saved!')\n * toast.error('Failed to save')\n * toast.promise(saveFn(), { loading: 'Saving...', success: 'Done!', error: 'Failed' })\n * toast.undo('Deleted', { onUndo: () => restore() })\n * toast.upload({ files, onRetry, onRemove })\n */\nfunction toast(message: string | React.ReactNode, options?: ToastOptions) {\n return createTypedToast('message', message, options)\n}\n\ntoast.message = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('message', message, options)\n}\n\ntoast.success = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('success', message, options)\n}\n\ntoast.error = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('error', message, options)\n}\n\ntoast.warning = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('warning', message, options)\n}\n\ntoast.info = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('info', message, options)\n}\n\ntoast.loading = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('loading', message, {\n ...options,\n duration: options?.duration ?? Infinity,\n })\n}\n\ntoast.promise = <T,>(\n promise: Promise<T>,\n options: {\n loading: string | React.ReactNode\n success: string | React.ReactNode | ((data: T) => string | React.ReactNode)\n error:\n | string\n | React.ReactNode\n | ((error: unknown) => string | React.ReactNode)\n },\n) => {\n const id = sonnerToast.custom(\n () => (\n <ToastContent type=\"loading\" title={options.loading} showTimerBar={false} />\n ),\n { duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n\n const replaceWith = (type: ToastType, message: React.ReactNode) => {\n // Sonner doesn't reset its auto-dismiss timer when updating a custom toast\n // in-place from duration: Infinity, so ToastContent manages its own\n // hover-aware dismiss timer via selfDismissId.\n sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n duration={DEFAULT_DURATION}\n selfDismissId={id}\n />\n ),\n { id, duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n }\n\n promise\n .then((data) => {\n const message =\n typeof options.success === 'function'\n ? options.success(data)\n : options.success\n replaceWith('success', message)\n })\n .catch((error) => {\n const message =\n typeof options.error === 'function'\n ? options.error(error)\n : options.error\n replaceWith('error', message)\n })\n\n return id\n}\n\ntoast.undo = (message: string | React.ReactNode, options: ToastUndoOptions) => {\n const duration = options.duration ?? UNDO_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type=\"message\"\n title={message}\n description={options.description}\n action={{ label: 'Undo', onClick: options.onUndo }}\n duration={duration}\n />\n ),\n { duration, unstyled: true, classNames: { toast: 'w-full' } },\n )\n}\n\ntoast.upload = (options: ToastUploadOptions) => {\n // Pre-determine the id so we can pass it to UploadToastContent for self-dismiss.\n const toastId = options.id ?? String(Date.now())\n\n sonnerToast.custom(\n () => (\n <UploadToastContent\n files={options.files}\n onRetry={options.onRetry}\n onRemove={options.onRemove}\n selfDismissId={toastId}\n />\n ),\n {\n id: toastId,\n duration: Infinity,\n unstyled: true,\n classNames: { toast: 'w-full' },\n },\n )\n\n return toastId\n}\n\ntoast.custom = (\n render: (id: string | number) => React.ReactElement,\n options?: ExternalToast,\n) => {\n return sonnerToast.custom(render, {\n ...options,\n unstyled: true,\n classNames: { toast: 'w-full', ...(options?.classNames as Record<string, string>) },\n })\n}\n\ntoast.dismiss = (id?: string | number) => {\n if (id !== undefined) {\n sonnerToast.dismiss(id)\n } else {\n sonnerToast.dismiss()\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------ */\n\nexport type ToastProps = ToastOptions\n\nexport {\n formatFileSize,\n TimerBar,\n toast,\n ToastContent,\n UploadFileRow,\n UploadToastContent,\n}\nexport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../src/ui/toast.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertCircle,\n IconAlertTriangle,\n IconCheck,\n IconCircleCheck,\n IconCircleX,\n IconFile,\n IconInfoCircle,\n IconPhoto,\n IconRefresh,\n IconUpload,\n IconX,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { type ExternalToast,toast as sonnerToast } from 'sonner'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Progress } from './progress'\nimport { Spinner } from './spinner'\nimport { assertToasterMounted } from './toast-registry'\nimport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n\n/* ---------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------ */\n\nconst DEFAULT_DURATION = 5000\nconst UNDO_DURATION = 8000\nconst UPLOAD_COMPLETE_DELAY = 3000\n\n/* ---------------------------------------------------------------------------\n * Accent bar & icon config per toast type\n * ------------------------------------------------------------------------ */\n\nconst TOAST_TYPE_CONFIG: Record<\n ToastType,\n {\n accentClass: string\n iconClass: string\n icon: React.ForwardRefExoticComponent<any> | null\n timerBarClass: string\n }\n> = {\n message: {\n accentClass: '',\n iconClass: '',\n icon: null,\n timerBarClass: 'bg-surface-border',\n },\n success: {\n accentClass: 'bg-success-9',\n iconClass: 'text-success-11',\n icon: IconCircleCheck,\n timerBarClass: 'bg-success-9',\n },\n error: {\n accentClass: 'bg-error-9',\n iconClass: 'text-error-11',\n icon: IconCircleX,\n timerBarClass: 'bg-error-9',\n },\n warning: {\n accentClass: 'bg-warning-9',\n iconClass: 'text-warning-11',\n icon: IconAlertTriangle,\n timerBarClass: 'bg-warning-9',\n },\n info: {\n accentClass: 'bg-info-9',\n iconClass: 'text-info-11',\n icon: IconInfoCircle,\n timerBarClass: 'bg-info-9',\n },\n loading: {\n accentClass: 'bg-accent-9',\n iconClass: 'text-accent-11',\n icon: null,\n timerBarClass: 'bg-accent-9',\n },\n}\n\n/* ---------------------------------------------------------------------------\n * Timer bar — shows auto-dismiss countdown\n * ------------------------------------------------------------------------ */\n\nfunction TimerBar({\n duration,\n type,\n paused,\n}: {\n duration: number\n type: ToastType\n paused: boolean\n}) {\n const config = TOAST_TYPE_CONFIG[type]\n return (\n <div className=\"absolute bottom-0 left-0 right-0 h-[2px] overflow-hidden\">\n <div\n className={cn(\n 'h-full w-full origin-left opacity-30',\n config.timerBarClass,\n 'motion-safe:animate-timer-bar',\n )}\n style={{\n animationDuration: `${duration}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }}\n />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Base toast UI — used by all typed toasts\n * ------------------------------------------------------------------------ */\n\nfunction ToastContent({\n type,\n title,\n description,\n action,\n cancel,\n duration = DEFAULT_DURATION,\n showTimerBar = true,\n selfDismissId,\n}: {\n type: ToastType\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionOptions\n cancel?: ToastActionOptions\n duration?: number\n showTimerBar?: boolean\n /** When set, this component manages its own dismiss timer (hover-aware). */\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const config = TOAST_TYPE_CONFIG[type]\n const StatusIcon = config.icon\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(duration)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || duration === Infinity) return\n if (paused) {\n // Pause: save remaining time\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n // Start/resume timer\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, duration, paused])\n\n // Error toasts announce assertively so screen readers interrupt;\n // warnings stay polite but atomically re-read the message.\n const isUrgent = type === 'error'\n return (\n <motion.div\n layout=\"position\"\n role={isUrgent ? 'alert' : 'status'}\n aria-live={isUrgent ? 'assertive' : 'polite'}\n aria-atomic=\"true\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n {config.accentClass && (\n <div\n className={cn('w-1 shrink-0 rounded-l-overlay-sm', config.accentClass)}\n />\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 items-start gap-ds-03 p-ds-04\">\n {/* Status icon — animates between spinner ↔ typed icon */}\n <AnimatePresence mode=\"wait\">\n {type === 'loading' ? (\n <motion.div\n key=\"loading\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n className=\"mt-0.5 shrink-0\"\n >\n <Spinner size=\"sm\" className=\"h-4 w-4\" />\n </motion.div>\n ) : StatusIcon ? (\n <motion.div\n key={type}\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n className=\"mt-0.5 shrink-0\"\n >\n <Icon icon={StatusIcon} size=\"sm\" className={config.iconClass} />\n </motion.div>\n ) : null}\n </AnimatePresence>\n\n {/* Text */}\n <div className=\"min-w-0 flex-1\">\n {title && (\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {title}\n </p>\n )}\n {description && (\n <p\n className={cn(\n 'text-ds-sm text-surface-fg-muted',\n title && 'mt-0.5',\n )}\n >\n {description}\n </p>\n )}\n\n {/* Action / cancel buttons */}\n {(action || cancel) && (\n <div className=\"mt-ds-03 flex items-center gap-ds-03\">\n {action && (\n <button\n type=\"button\"\n onClick={action.onClick}\n className=\"text-ds-sm font-medium text-accent-11 underline-offset-2 hover:underline hover:bg-surface-raised-hover rounded-control-inner px-ds-02 py-ds-01 transition-[color,background-color] duration-fast-01 ease-productive-standard focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {action.label}\n </button>\n )}\n {cancel && (\n <button\n type=\"button\"\n onClick={cancel.onClick}\n className=\"text-ds-sm text-surface-fg-muted hover:text-surface-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {cancel.label}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n\n {/* Timer bar */}\n {showTimerBar && type !== 'loading' && (\n <TimerBar duration={duration} type={type} paused={paused} />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Upload toast UI\n * ------------------------------------------------------------------------ */\n\nconst IMAGE_EXTENSIONS = new Set([\n 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif',\n])\n\nfunction isImageFile(file: UploadFile): boolean {\n if (file.previewUrl) return true\n const ext = file.name.split('.').pop()?.toLowerCase() ?? ''\n return IMAGE_EXTENSIONS.has(ext)\n}\n\nfunction getProgressColor(\n status: UploadFile['status'],\n): 'default' | 'success' | 'error' {\n switch (status) {\n case 'complete':\n return 'success'\n case 'error':\n return 'error'\n default:\n return 'default'\n }\n}\n\nfunction getProgressValue(file: UploadFile): number | undefined {\n switch (file.status) {\n case 'pending':\n return 0\n case 'uploading':\n return file.progress ?? 0\n case 'processing':\n return undefined\n case 'complete':\n return 100\n case 'error':\n return file.progress ?? 0\n }\n}\n\nfunction formatFileSize(bytes: number): string {\n if (!bytes || bytes < 0 || !Number.isFinite(bytes)) return '0 B'\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`\n}\n\nfunction UploadFileRow({\n file,\n onRetry,\n onRemove,\n}: {\n file: UploadFile\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n}) {\n const isTerminal = file.status === 'complete' || file.status === 'error'\n const isUploading =\n file.status === 'uploading' || file.status === 'processing'\n\n return (\n <motion.div\n layout\n className=\"flex items-center gap-ds-02 py-1\"\n initial={false}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, x: -20, height: 0 }}\n transition={springs.snappy}\n >\n {/* File icon — animates between states */}\n <div className=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <AnimatePresence mode=\"wait\">\n {file.status === 'complete' ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"xs\" className=\"text-success-11\" />\n </motion.div>\n ) : file.status === 'error' ? (\n <motion.div\n key=\"error\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Icon icon={IconAlertCircle} size=\"xs\" className=\"text-error-11\" />\n </motion.div>\n ) : file.status === 'processing' ? (\n <motion.div\n key=\"processing\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Spinner size=\"sm\" className=\"h-3.5 w-3.5\" />\n </motion.div>\n ) : isImageFile(file) ? (\n <motion.div\n key=\"image\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconPhoto} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"file\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconFile} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n\n {/* Filename */}\n <span className=\"min-w-0 flex-1 truncate text-ds-xs text-surface-fg\">\n {file.name}\n </span>\n\n {/* Progress bar or status */}\n {isUploading ? (\n <div className=\"flex w-16 items-center gap-1\">\n <Progress\n size=\"sm\"\n color={getProgressColor(file.status)}\n value={getProgressValue(file)}\n className=\"flex-1\"\n />\n {file.progress !== undefined && (\n <span className=\"shrink-0 text-ds-xs tabular-nums text-surface-fg-muted\">\n {file.progress}%\n </span>\n )}\n </div>\n ) : file.status === 'complete' ? (\n <span className=\"text-ds-xs text-success-11\">Done</span>\n ) : file.status === 'error' ? (\n <span className=\"max-w-[60px] truncate text-ds-xs text-error-11\">\n {file.error || 'Failed'}\n </span>\n ) : (\n <span className=\"text-ds-xs text-surface-fg-muted\">\n {formatFileSize(file.size)}\n </span>\n )}\n\n {/* Actions */}\n {file.status === 'error' && onRetry && (\n <button\n type=\"button\"\n onClick={() => onRetry(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Retry ${file.name}`}\n title=\"Retry\"\n >\n <IconRefresh className=\"h-3.5 w-3.5\" />\n </button>\n )}\n {!isTerminal && onRemove && (\n <button\n type=\"button\"\n onClick={() => onRemove(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Cancel ${file.name}`}\n title=\"Remove\"\n >\n <IconX className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </motion.div>\n )\n}\n\nfunction UploadToastContent({\n files,\n onRetry,\n onRemove,\n selfDismissId,\n}: {\n files: UploadFile[]\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const completeCount = files.filter((f) => f.status === 'complete').length\n const errorCount = files.filter((f) => f.status === 'error').length\n const allTerminal = files.every(\n (f) => f.status === 'complete' || f.status === 'error',\n )\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(UPLOAD_COMPLETE_DELAY)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || !allTerminal) return\n if (paused) {\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, allTerminal, paused])\n\n const accentClass = allTerminal\n ? errorCount > 0\n ? 'bg-error-7'\n : 'bg-success-7'\n : 'bg-accent-9'\n\n const timerBarType: ToastType = allTerminal\n ? errorCount > 0\n ? 'error'\n : 'success'\n : 'loading'\n\n const uploadUrgent = allTerminal && errorCount > 0\n return (\n <motion.div\n layout\n role={uploadUrgent ? 'alert' : 'status'}\n aria-live={uploadUrgent ? 'assertive' : 'polite'}\n aria-label=\"File uploads\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n <div className={cn('w-1 shrink-0 rounded-l-overlay-sm', accentClass)} />\n\n {/* Content */}\n <div className=\"min-w-0 flex-1 p-ds-04\">\n {/* Header — icon morphs between upload ↔ result */}\n <div className=\"flex items-center gap-ds-02\">\n <AnimatePresence mode=\"wait\">\n {allTerminal ? (\n errorCount > 0 ? (\n <motion.div\n key=\"error-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconAlertCircle} size=\"sm\" className=\"shrink-0 text-error-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"success-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"shrink-0 text-success-11\" />\n </motion.div>\n )\n ) : (\n <motion.div\n key=\"upload-icon\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"sm\" className=\"shrink-0 text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {allTerminal\n ? errorCount > 0\n ? `${completeCount} of ${files.length} uploaded`\n : `${files.length} file${files.length > 1 ? 's' : ''} uploaded`\n : `Uploading ${files.length} file${files.length > 1 ? 's' : ''}`}\n </p>\n </div>\n {!allTerminal && (\n <p className=\"mt-0.5 text-ds-xs text-surface-fg-muted\">\n {completeCount} of {files.length} complete\n {errorCount > 0 && ` · ${errorCount} failed`}\n </p>\n )}\n\n {/* File list */}\n <div className=\"mt-ds-02 max-h-[140px] overflow-y-auto\">\n <AnimatePresence>\n {files.map((file) => (\n <UploadFileRow\n key={file.id}\n file={file}\n onRetry={onRetry}\n onRemove={onRemove}\n />\n ))}\n </AnimatePresence>\n </div>\n\n {/* Screen reader announcement */}\n <div className=\"sr-only\" aria-live=\"polite\">\n {completeCount} of {files.length} files uploaded\n {errorCount > 0 && `, ${errorCount} failed`}\n </div>\n </div>\n\n {/* Timer bar — only after all terminal */}\n {allTerminal && (\n <TimerBar\n duration={UPLOAD_COMPLETE_DELAY}\n type={timerBarType}\n paused={false}\n />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toast API — wraps Sonner with our styled UI\n * ------------------------------------------------------------------------ */\n\nfunction createSonnerOptions(\n options?: ToastOptions,\n duration?: number,\n): ExternalToast {\n return {\n id: options?.id,\n duration: options?.duration ?? duration ?? DEFAULT_DURATION,\n unstyled: true,\n classNames: { toast: 'w-full' },\n }\n}\n\nfunction createTypedToast(\n type: ToastType,\n message: string | React.ReactNode,\n options?: ToastOptions,\n) {\n assertToasterMounted()\n const duration = options?.duration ?? DEFAULT_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n description={options?.description}\n action={options?.action}\n cancel={options?.cancel}\n duration={duration}\n />\n ),\n createSonnerOptions(options, duration),\n )\n}\n\n/**\n * Toast API — imperative toast notifications with Sonner rendering engine.\n *\n * Mount `<Toaster />` once at your root layout. Then call `toast()` from anywhere.\n *\n * @example\n * toast('Something happened')\n * toast.success('Saved!')\n * toast.error('Failed to save')\n * toast.promise(saveFn(), { loading: 'Saving...', success: 'Done!', error: 'Failed' })\n * toast.undo('Deleted', { onUndo: () => restore() })\n * toast.upload({ files, onRetry, onRemove })\n */\nfunction toast(message: string | React.ReactNode, options?: ToastOptions) {\n return createTypedToast('message', message, options)\n}\n\ntoast.message = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('message', message, options)\n}\n\ntoast.success = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('success', message, options)\n}\n\ntoast.error = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('error', message, options)\n}\n\ntoast.warning = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('warning', message, options)\n}\n\ntoast.info = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('info', message, options)\n}\n\ntoast.loading = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('loading', message, {\n ...options,\n duration: options?.duration ?? Infinity,\n })\n}\n\ntoast.promise = <T,>(\n promise: Promise<T>,\n options: {\n loading: string | React.ReactNode\n success: string | React.ReactNode | ((data: T) => string | React.ReactNode)\n error:\n | string\n | React.ReactNode\n | ((error: unknown) => string | React.ReactNode)\n },\n) => {\n const id = sonnerToast.custom(\n () => (\n <ToastContent type=\"loading\" title={options.loading} showTimerBar={false} />\n ),\n { duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n\n const replaceWith = (type: ToastType, message: React.ReactNode) => {\n // Sonner doesn't reset its auto-dismiss timer when updating a custom toast\n // in-place from duration: Infinity, so ToastContent manages its own\n // hover-aware dismiss timer via selfDismissId.\n sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n duration={DEFAULT_DURATION}\n selfDismissId={id}\n />\n ),\n { id, duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n }\n\n promise\n .then((data) => {\n const message =\n typeof options.success === 'function'\n ? options.success(data)\n : options.success\n replaceWith('success', message)\n })\n .catch((error) => {\n const message =\n typeof options.error === 'function'\n ? options.error(error)\n : options.error\n replaceWith('error', message)\n })\n\n return id\n}\n\ntoast.undo = (message: string | React.ReactNode, options: ToastUndoOptions) => {\n const duration = options.duration ?? UNDO_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type=\"message\"\n title={message}\n description={options.description}\n action={{ label: 'Undo', onClick: options.onUndo }}\n duration={duration}\n />\n ),\n { duration, unstyled: true, classNames: { toast: 'w-full' } },\n )\n}\n\ntoast.upload = (options: ToastUploadOptions) => {\n // Pre-determine the id so we can pass it to UploadToastContent for self-dismiss.\n const toastId = options.id ?? String(Date.now())\n\n sonnerToast.custom(\n () => (\n <UploadToastContent\n files={options.files}\n onRetry={options.onRetry}\n onRemove={options.onRemove}\n selfDismissId={toastId}\n />\n ),\n {\n id: toastId,\n duration: Infinity,\n unstyled: true,\n classNames: { toast: 'w-full' },\n },\n )\n\n return toastId\n}\n\ntoast.custom = (\n render: (id: string | number) => React.ReactElement,\n options?: ExternalToast,\n) => {\n return sonnerToast.custom(render, {\n ...options,\n unstyled: true,\n classNames: { toast: 'w-full', ...(options?.classNames as Record<string, string>) },\n })\n}\n\ntoast.dismiss = (id?: string | number) => {\n if (id !== undefined) {\n sonnerToast.dismiss(id)\n } else {\n sonnerToast.dismiss()\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------ */\n\nexport type ToastProps = ToastOptions\n\nexport {\n formatFileSize,\n TimerBar,\n toast,\n ToastContent,\n UploadFileRow,\n UploadToastContent,\n}\nexport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n"],"mappings":"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"}
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
comparisonLabel: string (shown after delta, e.g. "vs last month")
|
|
17
17
|
secondaryLabel: string (below main value, e.g. "of $50,000 target")
|
|
18
18
|
progress: number (0-100, renders thin progress bar below value)
|
|
19
|
-
|
|
19
|
+
surface: "raised" | "flat" (raised = ring-in-shadow, no border [default]; flat = border, no shadow)
|
|
20
|
+
accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
|
|
21
|
+
iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
|
|
22
|
+
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
|
|
23
|
+
flashSpeed: "fast" | "normal" | "slow" (default normal)
|
|
20
24
|
sparkline: number[] (renders mini SVG line chart)
|
|
21
25
|
onClick: () => void (makes card clickable with hover state)
|
|
22
26
|
href: string (makes card a link via LinkContext)
|
|
@@ -34,7 +38,8 @@
|
|
|
34
38
|
delta={{ value: "+12%", direction: "up" }}
|
|
35
39
|
comparisonLabel="vs last month"
|
|
36
40
|
icon={<IconCurrencyDollar />}
|
|
37
|
-
|
|
41
|
+
accentStyle="icon"
|
|
42
|
+
flash="up"
|
|
38
43
|
/>
|
|
39
44
|
|
|
40
45
|
<StatCard
|
|
@@ -51,7 +56,8 @@
|
|
|
51
56
|
- **High-density metric card** — optimized for dashboards. Everything optional except `value`. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs.
|
|
52
57
|
- **Router integration via href:** Internally uses `LinkContext` to resolve framework-specific Link components (Next.js, react-router). Set `href` in a LinkProvider-wrapped tree to get seamless client-side navigation without custom asChild wiring.
|
|
53
58
|
- **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
|
|
54
|
-
- **Accent
|
|
59
|
+
- **Accent (composable, opt-in):** `accentStyle="icon"` wraps `icon` in an accent chip (`iconFill="soft" | "solid"`); `accentStyle="tint"` applies a subtle accent surface wash + accent value. `surface` picks edge-vs-elevation. No colored rail — the DS never stacks a border + drop shadow (make-kit rule #6). Trend health reads from `delta.direction` (up=green, down=red).
|
|
60
|
+
- **Flash motion (opt-in):** `flash` mounts a toned state glyph (`up`/`down`/`goal`/`record`/`alert`/`live`, or `{ tone, icon }`) that settles to `icon`; `flashSpeed` tunes timing. Reuses the standalone `StatFlash` primitive. Honors `prefers-reduced-motion`.
|
|
55
61
|
- **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
|
|
56
62
|
- **Icon auto-sizing:** Accepts `ComponentType<{ className }>` OR `ReactNode`. The component prop (e.g. `icon={IconBolt}`) is preferred — icon is rendered at a consistent size.
|
|
57
63
|
- **Loading state:** `loading={true}` renders the full card skeleton — use during initial data fetch.
|
|
@@ -63,6 +69,10 @@
|
|
|
63
69
|
- `sparkline` needs at least 2 data points to render
|
|
64
70
|
|
|
65
71
|
## Changes
|
|
72
|
+
### v0.43.0
|
|
73
|
+
- **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
|
|
74
|
+
- **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
|
|
75
|
+
|
|
66
76
|
### v0.2.0
|
|
67
77
|
- **Added** `icon` prop now accepts `React.ComponentType` (e.g., `icon={IconBolt}`) in addition to `ReactNode`
|
|
68
78
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# StatFlash
|
|
2
|
+
|
|
3
|
+
- Import: @devalok/shilp-sutra/ui/stat-flash
|
|
4
|
+
- Server-safe: No
|
|
5
|
+
- Category: ui
|
|
6
|
+
|
|
7
|
+
## Props
|
|
8
|
+
icon: ReactNode | ComponentType<{ className?: string }> (REQUIRED — settled identity glyph)
|
|
9
|
+
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (REQUIRED — the entrance state)
|
|
10
|
+
fill: "soft" | "solid" (resting chip style; default soft)
|
|
11
|
+
speed: "fast" | "normal" | "slow" (coarse motion preset; default normal)
|
|
12
|
+
holdMs: number (override how long the flash holds before settling)
|
|
13
|
+
settleTransition: Transition (override the icon scale-in transition)
|
|
14
|
+
flashTransition: Transition (override the flash enter/exit fade)
|
|
15
|
+
|
|
16
|
+
## Defaults
|
|
17
|
+
fill="soft", speed="normal"
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
```jsx
|
|
21
|
+
// Preset: green up-arrow flashes, then settles to the folder icon
|
|
22
|
+
<StatFlash icon={<IconFolder />} flash="up" />
|
|
23
|
+
|
|
24
|
+
// Explicit tone + glyph
|
|
25
|
+
<StatFlash icon={<IconActivity />} flash={{ tone: "info", icon: <IconBolt /> }} fill="solid" />
|
|
26
|
+
|
|
27
|
+
// Composable speed: coarse preset + fine override
|
|
28
|
+
<StatFlash icon={<IconFolder />} flash="up" speed="fast" holdMs={300} />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Composability
|
|
32
|
+
- **State → identity primitive:** the chip mounts showing a transient *state* (a toned glyph — up-arrow on green, check, alert…) then settles into the stable *identity* icon. Used by `StatCard`'s `flash` prop, but standalone too (list rows, badges, toasts).
|
|
33
|
+
- **Tone slot:** six named presets (`up`/`down`/`goal`/`record`/`alert`/`live`) map to DS semantic tones (success/error/warning/info/accent) + a glyph. Pass `{ tone, icon }` for anything else.
|
|
34
|
+
- **Composable motion:** `speed` is a coarse preset built from the DS motion tokens; `holdMs` / `settleTransition` / `flashTransition` each override one slice and fall back to the preset.
|
|
35
|
+
- **Accessible by default:** honors `prefers-reduced-motion` — renders the settled identity directly, no flash. The chip is `aria-hidden` (decorative; the metric's text carries meaning).
|
|
36
|
+
|
|
37
|
+
## Gotchas
|
|
38
|
+
- `flash` and `icon` are both required — the flash settles *to* the icon.
|
|
39
|
+
- Fine overrides win over `speed`.
|
|
40
|
+
- Decorative only — never the sole carrier of meaning (it's `aria-hidden`).
|
|
41
|
+
|
|
42
|
+
## Changes
|
|
43
|
+
### v0.43.0
|
|
44
|
+
- **Added** Initial release. Powers `StatCard`'s `flash` prop.
|
package/llms-full.txt
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> All variant values and props verified from source CVA definitions.
|
|
6
6
|
>
|
|
7
7
|
> Package: @devalok/shilp-sutra
|
|
8
|
-
> Version: 0.
|
|
8
|
+
> Version: 0.43.0
|
|
9
9
|
>
|
|
10
10
|
> **If you are an AI agent reading this file top-to-bottom:** the Setup
|
|
11
11
|
> section below is authoritative. If any later per-component doc or a
|
|
@@ -4046,7 +4046,11 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4046
4046
|
comparisonLabel: string (shown after delta, e.g. "vs last month")
|
|
4047
4047
|
secondaryLabel: string (below main value, e.g. "of $50,000 target")
|
|
4048
4048
|
progress: number (0-100, renders thin progress bar below value)
|
|
4049
|
-
|
|
4049
|
+
surface: "raised" | "flat" (raised = ring-in-shadow, no border [default]; flat = border, no shadow)
|
|
4050
|
+
accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
|
|
4051
|
+
iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
|
|
4052
|
+
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
|
|
4053
|
+
flashSpeed: "fast" | "normal" | "slow" (default normal)
|
|
4050
4054
|
sparkline: number[] (renders mini SVG line chart)
|
|
4051
4055
|
onClick: () => void (makes card clickable with hover state)
|
|
4052
4056
|
href: string (makes card a link via LinkContext)
|
|
@@ -4064,7 +4068,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4064
4068
|
delta={{ value: "+12%", direction: "up" }}
|
|
4065
4069
|
comparisonLabel="vs last month"
|
|
4066
4070
|
icon={<IconCurrencyDollar />}
|
|
4067
|
-
|
|
4071
|
+
accentStyle="icon"
|
|
4072
|
+
flash="up"
|
|
4068
4073
|
/>
|
|
4069
4074
|
|
|
4070
4075
|
<StatCard
|
|
@@ -4081,7 +4086,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4081
4086
|
- **High-density metric card** — optimized for dashboards. Everything optional except `value`. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs.
|
|
4082
4087
|
- **Router integration via href:** Internally uses `LinkContext` to resolve framework-specific Link components (Next.js, react-router). Set `href` in a LinkProvider-wrapped tree to get seamless client-side navigation without custom asChild wiring.
|
|
4083
4088
|
- **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
|
|
4084
|
-
- **Accent
|
|
4089
|
+
- **Accent (composable, opt-in):** `accentStyle="icon"` wraps `icon` in an accent chip (`iconFill="soft" | "solid"`); `accentStyle="tint"` applies a subtle accent surface wash + accent value. `surface` picks edge-vs-elevation. No colored rail — the DS never stacks a border + drop shadow (make-kit rule #6). Trend health reads from `delta.direction` (up=green, down=red).
|
|
4090
|
+
- **Flash motion (opt-in):** `flash` mounts a toned state glyph (`up`/`down`/`goal`/`record`/`alert`/`live`, or `{ tone, icon }`) that settles to `icon`; `flashSpeed` tunes timing. Reuses the standalone `StatFlash` primitive. Honors `prefers-reduced-motion`.
|
|
4085
4091
|
- **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
|
|
4086
4092
|
- **Icon auto-sizing:** Accepts `ComponentType<{ className }>` OR `ReactNode`. The component prop (e.g. `icon={IconBolt}`) is preferred — icon is rendered at a consistent size.
|
|
4087
4093
|
- **Loading state:** `loading={true}` renders the full card skeleton — use during initial data fetch.
|
|
@@ -4093,11 +4099,59 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4093
4099
|
- `sparkline` needs at least 2 data points to render
|
|
4094
4100
|
|
|
4095
4101
|
## Changes
|
|
4102
|
+
### v0.43.0
|
|
4103
|
+
- **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
|
|
4104
|
+
- **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
|
|
4105
|
+
|
|
4096
4106
|
### v0.2.0
|
|
4097
4107
|
- **Added** `icon` prop now accepts `React.ComponentType` (e.g., `icon={IconBolt}`) in addition to `ReactNode`
|
|
4098
4108
|
|
|
4099
4109
|
### v0.1.0
|
|
4100
4110
|
- **Added** Initial release
|
|
4111
|
+
# StatFlash
|
|
4112
|
+
|
|
4113
|
+
- Import: @devalok/shilp-sutra/ui/stat-flash
|
|
4114
|
+
- Server-safe: No
|
|
4115
|
+
- Category: ui
|
|
4116
|
+
|
|
4117
|
+
## Props
|
|
4118
|
+
icon: ReactNode | ComponentType<{ className?: string }> (REQUIRED — settled identity glyph)
|
|
4119
|
+
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (REQUIRED — the entrance state)
|
|
4120
|
+
fill: "soft" | "solid" (resting chip style; default soft)
|
|
4121
|
+
speed: "fast" | "normal" | "slow" (coarse motion preset; default normal)
|
|
4122
|
+
holdMs: number (override how long the flash holds before settling)
|
|
4123
|
+
settleTransition: Transition (override the icon scale-in transition)
|
|
4124
|
+
flashTransition: Transition (override the flash enter/exit fade)
|
|
4125
|
+
|
|
4126
|
+
## Defaults
|
|
4127
|
+
fill="soft", speed="normal"
|
|
4128
|
+
|
|
4129
|
+
## Example
|
|
4130
|
+
```jsx
|
|
4131
|
+
// Preset: green up-arrow flashes, then settles to the folder icon
|
|
4132
|
+
<StatFlash icon={<IconFolder />} flash="up" />
|
|
4133
|
+
|
|
4134
|
+
// Explicit tone + glyph
|
|
4135
|
+
<StatFlash icon={<IconActivity />} flash={{ tone: "info", icon: <IconBolt /> }} fill="solid" />
|
|
4136
|
+
|
|
4137
|
+
// Composable speed: coarse preset + fine override
|
|
4138
|
+
<StatFlash icon={<IconFolder />} flash="up" speed="fast" holdMs={300} />
|
|
4139
|
+
```
|
|
4140
|
+
|
|
4141
|
+
## Composability
|
|
4142
|
+
- **State → identity primitive:** the chip mounts showing a transient *state* (a toned glyph — up-arrow on green, check, alert…) then settles into the stable *identity* icon. Used by `StatCard`'s `flash` prop, but standalone too (list rows, badges, toasts).
|
|
4143
|
+
- **Tone slot:** six named presets (`up`/`down`/`goal`/`record`/`alert`/`live`) map to DS semantic tones (success/error/warning/info/accent) + a glyph. Pass `{ tone, icon }` for anything else.
|
|
4144
|
+
- **Composable motion:** `speed` is a coarse preset built from the DS motion tokens; `holdMs` / `settleTransition` / `flashTransition` each override one slice and fall back to the preset.
|
|
4145
|
+
- **Accessible by default:** honors `prefers-reduced-motion` — renders the settled identity directly, no flash. The chip is `aria-hidden` (decorative; the metric's text carries meaning).
|
|
4146
|
+
|
|
4147
|
+
## Gotchas
|
|
4148
|
+
- `flash` and `icon` are both required — the flash settles *to* the icon.
|
|
4149
|
+
- Fine overrides win over `speed`.
|
|
4150
|
+
- Decorative only — never the sole carrier of meaning (it's `aria-hidden`).
|
|
4151
|
+
|
|
4152
|
+
## Changes
|
|
4153
|
+
### v0.43.0
|
|
4154
|
+
- **Added** Initial release. Powers `StatCard`'s `flash` prop.
|
|
4101
4155
|
# StatusDot
|
|
4102
4156
|
|
|
4103
4157
|
- Import: @devalok/shilp-sutra/ui/status-dot
|
package/llms.txt
CHANGED
|
@@ -41,6 +41,10 @@ Paste the snippet *after* `@import "@devalok/shilp-sutra/css";` in the global st
|
|
|
41
41
|
|
|
42
42
|
The repo URL for these files is `https://github.com/devalok-design/shilp-sutra/tree/main/packages/core/docs/recipes`. Consumer projects should also have an `AGENTS.md` at their root with the rules above pre-loaded — read that first if it exists.
|
|
43
43
|
|
|
44
|
+
## NEW (v0.42.0)
|
|
45
|
+
|
|
46
|
+
- **Figma Make kit.** 26 guideline files at `node_modules/@devalok/shilp-sutra/make-kit/` (top-level `Guidelines.md` + `setup.md`, eight `foundations/*.md`, sixteen `components/*.md`). Subpath exports `@devalok/shilp-sutra/make-kit` → `Guidelines.md`, `/make-kit/*` → individual files. Used by Figma Make to generate apps + prototypes against the DS. Setup walkthrough: https://shilp-sutra.devalok.in/figma-make. Public npm registry; cross-org sharing not supported by Figma — each consumer org self-registers. Pin to a specific npm version; no auto-update flow yet. Make kits need Organization / Enterprise Figma plan; Free + Pro can still install the package directly in React projects.
|
|
47
|
+
|
|
44
48
|
## NEW (v0.40.0)
|
|
45
49
|
|
|
46
50
|
- **OAuthButton.** Brand-aware social/login buttons. Subpath: `@devalok/shilp-sutra/ui/oauth-button`. 13 providers (`google` `apple` `github` `microsoft` `x` `linkedin` `facebook` `discord` `slack` `gitlab` `sso` `email` `passkey`). Props: `provider`, `intent` (`continue|signin|signup`), `appearance` (`brand|outline|dark`), `icon` (override default glyph), `iconOnly`, `compact` (renders just "Google" instead of "Continue with Google"; aria-label keeps long form), `lastUsed` (inline right-edge pill inside button), `helperText`. Inherits Button async/loading/sizes. Siblings: `OAuthGroup` (with `reorderLastUsedFirst` for Stripe-style ordering), `OAuthDivider`, `OAuthConnectionRow` (settings-page linked state). Default glyphs from Tabler peer dep; pass `icon` to drop in a brand's official multicolour SVG. In dark mode every brand appearance lands on the same DS surface — brand identity comes from the glyph, not the bg, so rows stay visually coherent.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devalok/shilp-sutra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Devalok Design System — accessible React components, OKLCH design tokens, and Tailwind 4 CSS-first setup. Ships with AI-agent setup recipes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Devalok Design & Strategy Studios <shilp-sutra@devalok.in>",
|
package/skill/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: shilp-sutra
|
|
|
3
3
|
description: Add, configure, and use components from Devalok's shilp-sutra design system (@devalok/shilp-sutra) — a Tailwind 4 + React 19 + CVA library with 110+ accessible components, OKLCH design tokens, framer-motion animations, and per-component RSC-safe entry points. Use this skill whenever the user mentions shilp-sutra, Devalok, the @devalok npm scope, or asks to install/add/style/theme UI in any React project that already depends on the package — even if they don't name it explicitly. Use it instead of generic shadcn/ui, MUI, or Chakra knowledge when shilp-sutra is in the project. Covers Next.js (App + Pages), Vite, Astro, Remix, TanStack Start setup playbooks; component API and variant reference; brand token customization; Server Component import patterns; and a troubleshoot tree for the thirteen most common breakages.
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.43.0"
|
|
7
7
|
author: Devalok Design & Strategy Studios
|
|
8
8
|
homepage: https://github.com/devalok-design/shilp-sutra
|
|
9
9
|
npm: https://www.npmjs.com/package/@devalok/shilp-sutra
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> All variant values and props verified from source CVA definitions.
|
|
8
8
|
>
|
|
9
9
|
> Package: @devalok/shilp-sutra
|
|
10
|
-
> Version: 0.
|
|
10
|
+
> Version: 0.43.0
|
|
11
11
|
>
|
|
12
12
|
> **If you are an AI agent reading this file top-to-bottom:** the Setup
|
|
13
13
|
> section below is authoritative. If any later per-component doc or a
|
|
@@ -4048,7 +4048,11 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4048
4048
|
comparisonLabel: string (shown after delta, e.g. "vs last month")
|
|
4049
4049
|
secondaryLabel: string (below main value, e.g. "of $50,000 target")
|
|
4050
4050
|
progress: number (0-100, renders thin progress bar below value)
|
|
4051
|
-
|
|
4051
|
+
surface: "raised" | "flat" (raised = ring-in-shadow, no border [default]; flat = border, no shadow)
|
|
4052
|
+
accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
|
|
4053
|
+
iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
|
|
4054
|
+
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
|
|
4055
|
+
flashSpeed: "fast" | "normal" | "slow" (default normal)
|
|
4052
4056
|
sparkline: number[] (renders mini SVG line chart)
|
|
4053
4057
|
onClick: () => void (makes card clickable with hover state)
|
|
4054
4058
|
href: string (makes card a link via LinkContext)
|
|
@@ -4066,7 +4070,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4066
4070
|
delta={{ value: "+12%", direction: "up" }}
|
|
4067
4071
|
comparisonLabel="vs last month"
|
|
4068
4072
|
icon={<IconCurrencyDollar />}
|
|
4069
|
-
|
|
4073
|
+
accentStyle="icon"
|
|
4074
|
+
flash="up"
|
|
4070
4075
|
/>
|
|
4071
4076
|
|
|
4072
4077
|
<StatCard
|
|
@@ -4083,7 +4088,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4083
4088
|
- **High-density metric card** — optimized for dashboards. Everything optional except `value`. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs.
|
|
4084
4089
|
- **Router integration via href:** Internally uses `LinkContext` to resolve framework-specific Link components (Next.js, react-router). Set `href` in a LinkProvider-wrapped tree to get seamless client-side navigation without custom asChild wiring.
|
|
4085
4090
|
- **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
|
|
4086
|
-
- **Accent
|
|
4091
|
+
- **Accent (composable, opt-in):** `accentStyle="icon"` wraps `icon` in an accent chip (`iconFill="soft" | "solid"`); `accentStyle="tint"` applies a subtle accent surface wash + accent value. `surface` picks edge-vs-elevation. No colored rail — the DS never stacks a border + drop shadow (make-kit rule #6). Trend health reads from `delta.direction` (up=green, down=red).
|
|
4092
|
+
- **Flash motion (opt-in):** `flash` mounts a toned state glyph (`up`/`down`/`goal`/`record`/`alert`/`live`, or `{ tone, icon }`) that settles to `icon`; `flashSpeed` tunes timing. Reuses the standalone `StatFlash` primitive. Honors `prefers-reduced-motion`.
|
|
4087
4093
|
- **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
|
|
4088
4094
|
- **Icon auto-sizing:** Accepts `ComponentType<{ className }>` OR `ReactNode`. The component prop (e.g. `icon={IconBolt}`) is preferred — icon is rendered at a consistent size.
|
|
4089
4095
|
- **Loading state:** `loading={true}` renders the full card skeleton — use during initial data fetch.
|
|
@@ -4095,11 +4101,59 @@ import { SplitButton } from '@devalok/shilp-sutra'
|
|
|
4095
4101
|
- `sparkline` needs at least 2 data points to render
|
|
4096
4102
|
|
|
4097
4103
|
## Changes
|
|
4104
|
+
### v0.43.0
|
|
4105
|
+
- **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
|
|
4106
|
+
- **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
|
|
4107
|
+
|
|
4098
4108
|
### v0.2.0
|
|
4099
4109
|
- **Added** `icon` prop now accepts `React.ComponentType` (e.g., `icon={IconBolt}`) in addition to `ReactNode`
|
|
4100
4110
|
|
|
4101
4111
|
### v0.1.0
|
|
4102
4112
|
- **Added** Initial release
|
|
4113
|
+
# StatFlash
|
|
4114
|
+
|
|
4115
|
+
- Import: @devalok/shilp-sutra/ui/stat-flash
|
|
4116
|
+
- Server-safe: No
|
|
4117
|
+
- Category: ui
|
|
4118
|
+
|
|
4119
|
+
## Props
|
|
4120
|
+
icon: ReactNode | ComponentType<{ className?: string }> (REQUIRED — settled identity glyph)
|
|
4121
|
+
flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (REQUIRED — the entrance state)
|
|
4122
|
+
fill: "soft" | "solid" (resting chip style; default soft)
|
|
4123
|
+
speed: "fast" | "normal" | "slow" (coarse motion preset; default normal)
|
|
4124
|
+
holdMs: number (override how long the flash holds before settling)
|
|
4125
|
+
settleTransition: Transition (override the icon scale-in transition)
|
|
4126
|
+
flashTransition: Transition (override the flash enter/exit fade)
|
|
4127
|
+
|
|
4128
|
+
## Defaults
|
|
4129
|
+
fill="soft", speed="normal"
|
|
4130
|
+
|
|
4131
|
+
## Example
|
|
4132
|
+
```jsx
|
|
4133
|
+
// Preset: green up-arrow flashes, then settles to the folder icon
|
|
4134
|
+
<StatFlash icon={<IconFolder />} flash="up" />
|
|
4135
|
+
|
|
4136
|
+
// Explicit tone + glyph
|
|
4137
|
+
<StatFlash icon={<IconActivity />} flash={{ tone: "info", icon: <IconBolt /> }} fill="solid" />
|
|
4138
|
+
|
|
4139
|
+
// Composable speed: coarse preset + fine override
|
|
4140
|
+
<StatFlash icon={<IconFolder />} flash="up" speed="fast" holdMs={300} />
|
|
4141
|
+
```
|
|
4142
|
+
|
|
4143
|
+
## Composability
|
|
4144
|
+
- **State → identity primitive:** the chip mounts showing a transient *state* (a toned glyph — up-arrow on green, check, alert…) then settles into the stable *identity* icon. Used by `StatCard`'s `flash` prop, but standalone too (list rows, badges, toasts).
|
|
4145
|
+
- **Tone slot:** six named presets (`up`/`down`/`goal`/`record`/`alert`/`live`) map to DS semantic tones (success/error/warning/info/accent) + a glyph. Pass `{ tone, icon }` for anything else.
|
|
4146
|
+
- **Composable motion:** `speed` is a coarse preset built from the DS motion tokens; `holdMs` / `settleTransition` / `flashTransition` each override one slice and fall back to the preset.
|
|
4147
|
+
- **Accessible by default:** honors `prefers-reduced-motion` — renders the settled identity directly, no flash. The chip is `aria-hidden` (decorative; the metric's text carries meaning).
|
|
4148
|
+
|
|
4149
|
+
## Gotchas
|
|
4150
|
+
- `flash` and `icon` are both required — the flash settles *to* the icon.
|
|
4151
|
+
- Fine overrides win over `speed`.
|
|
4152
|
+
- Decorative only — never the sole carrier of meaning (it's `aria-hidden`).
|
|
4153
|
+
|
|
4154
|
+
## Changes
|
|
4155
|
+
### v0.43.0
|
|
4156
|
+
- **Added** Initial release. Powers `StatCard`'s `flash` prop.
|
|
4103
4157
|
# StatusDot
|
|
4104
4158
|
|
|
4105
4159
|
- Import: @devalok/shilp-sutra/ui/status-dot
|
|
@@ -43,6 +43,10 @@ Paste the snippet *after* `@import "@devalok/shilp-sutra/css";` in the global st
|
|
|
43
43
|
|
|
44
44
|
The repo URL for these files is `https://github.com/devalok-design/shilp-sutra/tree/main/packages/core/docs/recipes`. Consumer projects should also have an `AGENTS.md` at their root with the rules above pre-loaded — read that first if it exists.
|
|
45
45
|
|
|
46
|
+
## NEW (v0.42.0)
|
|
47
|
+
|
|
48
|
+
- **Figma Make kit.** 26 guideline files at `node_modules/@devalok/shilp-sutra/make-kit/` (top-level `Guidelines.md` + `setup.md`, eight `foundations/*.md`, sixteen `components/*.md`). Subpath exports `@devalok/shilp-sutra/make-kit` → `Guidelines.md`, `/make-kit/*` → individual files. Used by Figma Make to generate apps + prototypes against the DS. Setup walkthrough: https://shilp-sutra.devalok.in/figma-make. Public npm registry; cross-org sharing not supported by Figma — each consumer org self-registers. Pin to a specific npm version; no auto-update flow yet. Make kits need Organization / Enterprise Figma plan; Free + Pro can still install the package directly in React projects.
|
|
49
|
+
|
|
46
50
|
## NEW (v0.40.0)
|
|
47
51
|
|
|
48
52
|
- **OAuthButton.** Brand-aware social/login buttons. Subpath: `@devalok/shilp-sutra/ui/oauth-button`. 13 providers (`google` `apple` `github` `microsoft` `x` `linkedin` `facebook` `discord` `slack` `gitlab` `sso` `email` `passkey`). Props: `provider`, `intent` (`continue|signin|signup`), `appearance` (`brand|outline|dark`), `icon` (override default glyph), `iconOnly`, `compact` (renders just "Google" instead of "Continue with Google"; aria-label keeps long form), `lastUsed` (inline right-edge pill inside button), `helperText`. Inherits Button async/loading/sizes. Siblings: `OAuthGroup` (with `reorderLastUsedFirst` for Stripe-style ordering), `OAuthDivider`, `OAuthConnectionRow` (settings-page linked state). Default glyphs from Tabler peer dep; pass `icon` to drop in a brand's official multicolour SVG. In dark mode every brand appearance lands on the same DS surface — brand identity comes from the glyph, not the bg, so rows stay visually coherent.
|