@refraction-ui/react 0.20.0 → 0.21.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/dist/{chunk-F7B64KT3.js → chunk-NKCWOPNX.js} +5 -1
- package/dist/chunk-NKCWOPNX.js.map +7 -0
- package/dist/index.cjs +61 -17
- package/dist/index.cjs.map +1 -1
- package/dist/internal/badge/index.d.cts +2 -2
- package/dist/internal/badge/index.d.ts +2 -2
- package/dist/internal/button/index.d.cts +2 -2
- package/dist/internal/button/index.d.ts +2 -2
- package/dist/internal/card/index.d.cts +10 -2
- package/dist/internal/card/index.d.ts +10 -2
- package/dist/internal/react-card/index.d.cts +3 -0
- package/dist/internal/react-card/index.d.ts +3 -0
- package/dist/internal/react-separator/index.d.cts +6 -1
- package/dist/internal/react-separator/index.d.ts +6 -1
- package/dist/internal/separator/index.d.cts +6 -1
- package/dist/internal/separator/index.d.ts +6 -1
- package/dist/react-avatar-group.js +1 -1
- package/dist/react-avatar-group.js.map +1 -1
- package/dist/react-badge.js +14 -2
- package/dist/react-badge.js.map +2 -2
- package/dist/react-browser-chrome-mock.js.map +2 -2
- package/dist/react-button.js +1 -1
- package/dist/react-card.js +12 -3
- package/dist/react-card.js.map +2 -2
- package/dist/react-diff-viewer.js +3 -0
- package/dist/react-diff-viewer.js.map +2 -2
- package/dist/react-presence-indicator.js +1 -1
- package/dist/react-presence-indicator.js.map +1 -1
- package/dist/react-separator.js +18 -6
- package/dist/react-separator.js.map +2 -2
- package/dist/react-slide-viewer.js +2 -0
- package/dist/react-slide-viewer.js.map +2 -2
- package/dist/react-social-auth-button.js +1 -1
- package/dist/react-status-indicator.js +4 -4
- package/dist/react-status-indicator.js.map +1 -1
- package/dist/react-sticky-note.js +2 -0
- package/dist/react-sticky-note.js.map +2 -2
- package/dist/styles.css +56 -0
- package/package.json +1 -1
- package/dist/chunk-F7B64KT3.js.map +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../diff-viewer/src/diff-viewer.ts", "../../diff-viewer/src/diff-viewer.styles.ts", "../../react-diff-viewer/src/DiffViewer.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type DiffViewerTheme = 'light' | 'dark'\nexport type DiffViewMode = 'side-by-side' | 'inline'\n\nexport type DiffFileStatus = 'added' | 'modified' | 'deleted' | 'renamed'\n\nexport interface DiffFile {\n /** File path */\n path: string\n /** Change status */\n status: DiffFileStatus\n /** Lines added */\n additions: number\n /** Lines deleted */\n deletions: number\n /** Unified diff content for this file */\n diff?: string\n /** Old path for renamed files */\n oldPath?: string\n}\n\nexport interface DiffViewerProps {\n /** Files in this diff */\n files?: DiffFile[]\n /** Index of the active file */\n activeFileIndex?: number\n /** View mode */\n viewMode?: DiffViewMode\n /** Theme */\n theme?: DiffViewerTheme\n /** Language for syntax highlighting */\n language?: string\n /** Whether the sidebar is visible */\n sidebarOpen?: boolean\n /** Callback when active file changes */\n onFileSelect?: (index: number) => void\n /** Callback when view mode changes */\n onViewModeChange?: (mode: DiffViewMode) => void\n /** Callback when sidebar toggled */\n onSidebarToggle?: () => void\n}\n\nexport interface DiffViewerState {\n activeFileIndex: number\n viewMode: DiffViewMode\n theme: DiffViewerTheme\n language: string\n sidebarOpen: boolean\n files: DiffFile[]\n}\n\nexport interface DiffViewerAPI {\n /** Current viewer state */\n state: DiffViewerState\n /** ARIA attributes for the root element */\n ariaProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** Data attributes for CSS styling hooks */\n dataAttributes: Record<string, string>\n /** Select a file by index */\n selectFile: (index: number) => void\n /** Move to next file */\n nextFile: () => void\n /** Move to previous file */\n prevFile: () => void\n /** Toggle view mode */\n toggleViewMode: () => void\n /** Toggle sidebar */\n toggleSidebar: () => void\n /** Get language label for a file path */\n getLanguageForFile: (path: string) => string\n /** Get human-readable file status icon */\n getFileStatusIcon: (status: DiffFileStatus) => string\n /** Get total additions across all files */\n totalAdditions: () => number\n /** Get total deletions across all files */\n totalDeletions: () => number\n}\n\n// ---------------------------------------------------------------------------\n// Language detection\n// ---------------------------------------------------------------------------\n\nconst extToLanguage: Record<string, string> = {\n ts: 'typescript',\n tsx: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n py: 'python',\n go: 'go',\n rs: 'rust',\n rb: 'ruby',\n java: 'java',\n swift: 'swift',\n kt: 'kotlin',\n dart: 'dart',\n php: 'php',\n lua: 'lua',\n r: 'r',\n scala: 'scala',\n json: 'json',\n yaml: 'yaml',\n yml: 'yaml',\n md: 'markdown',\n html: 'html',\n css: 'css',\n scss: 'scss',\n sql: 'sql',\n sh: 'shell',\n bash: 'shell',\n zsh: 'shell',\n xml: 'xml',\n toml: 'toml',\n proto: 'protobuf',\n dockerfile: 'dockerfile',\n cpp: 'cpp',\n c: 'c',\n}\n\nfunction getLanguageForFile(filePath: string): string {\n const ext = filePath.split('.').pop()?.toLowerCase() || ''\n const basename = filePath.split('/').pop()?.toLowerCase() || ''\n if (basename === 'dockerfile') return 'dockerfile'\n return extToLanguage[ext] || 'plaintext'\n}\n\n// ---------------------------------------------------------------------------\n// Status icons\n// ---------------------------------------------------------------------------\n\nconst statusIcons: Record<DiffFileStatus, string> = {\n added: '\\u{1F7E2}', // green circle\n modified: '\\u{1F7E1}', // yellow circle\n deleted: '\\u{1F534}', // red circle\n renamed: '\\u{1F535}', // blue circle\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a headless diff viewer API.\n *\n * Manages file selection, view mode, sidebar state, and produces ARIA/data\n * attributes. The actual rendering (Monaco DiffEditor, custom renderer, etc.)\n * is handled by framework-specific wrappers.\n */\nexport function createDiffViewer(props: DiffViewerProps = {}): DiffViewerAPI {\n const {\n files = [],\n activeFileIndex = 0,\n viewMode = 'side-by-side',\n theme = 'dark',\n language,\n sidebarOpen = true,\n onFileSelect,\n onViewModeChange,\n onSidebarToggle,\n } = props\n\n let currentIndex = Math.min(activeFileIndex, Math.max(0, files.length - 1))\n let currentViewMode = viewMode\n let currentSidebarOpen = sidebarOpen\n\n const state: DiffViewerState = {\n activeFileIndex: currentIndex,\n viewMode: currentViewMode,\n theme,\n language: language || (files[currentIndex] ? getLanguageForFile(files[currentIndex].path) : 'plaintext'),\n sidebarOpen: currentSidebarOpen,\n files,\n }\n\n const ariaProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n role: 'region',\n 'aria-label': 'Diff viewer',\n }\n\n const dataAttributes: Record<string, string> = {\n 'data-view-mode': currentViewMode,\n 'data-theme': theme,\n 'data-file-count': String(files.length),\n }\n\n if (!currentSidebarOpen) {\n dataAttributes['data-sidebar-collapsed'] = ''\n }\n\n function selectFile(index: number) {\n if (index >= 0 && index < files.length) {\n currentIndex = index\n state.activeFileIndex = index\n state.language = language || getLanguageForFile(files[index].path)\n onFileSelect?.(index)\n }\n }\n\n function nextFile() {\n if (currentIndex < files.length - 1) {\n selectFile(currentIndex + 1)\n }\n }\n\n function prevFile() {\n if (currentIndex > 0) {\n selectFile(currentIndex - 1)\n }\n }\n\n function toggleViewMode() {\n currentViewMode = currentViewMode === 'side-by-side' ? 'inline' : 'side-by-side'\n state.viewMode = currentViewMode\n dataAttributes['data-view-mode'] = currentViewMode\n onViewModeChange?.(currentViewMode)\n }\n\n function toggleSidebar() {\n currentSidebarOpen = !currentSidebarOpen\n state.sidebarOpen = currentSidebarOpen\n if (currentSidebarOpen) {\n delete dataAttributes['data-sidebar-collapsed']\n } else {\n dataAttributes['data-sidebar-collapsed'] = ''\n }\n onSidebarToggle?.()\n }\n\n function totalAdditions(): number {\n return files.reduce((sum, f) => sum + f.additions, 0)\n }\n\n function totalDeletions(): number {\n return files.reduce((sum, f) => sum + f.deletions, 0)\n }\n\n return {\n state,\n ariaProps,\n dataAttributes,\n selectFile,\n nextFile,\n prevFile,\n toggleViewMode,\n toggleSidebar,\n getLanguageForFile,\n getFileStatusIcon: (status: DiffFileStatus) => statusIcons[status] || '\\u{26AA}',\n totalAdditions,\n totalDeletions,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\nimport type { TokenContract } from '@refraction-ui/shared'\n\nexport const diffViewerTokens: TokenContract = {\n name: 'diff-viewer',\n tokens: {\n bg: { variable: '--rfr-diff-bg', fallback: 'hsl(var(--background))' },\n fg: { variable: '--rfr-diff-fg', fallback: 'hsl(var(--foreground))' },\n sidebarBg: { variable: '--rfr-diff-sidebar-bg', fallback: 'hsl(var(--muted))' },\n headerBg: { variable: '--rfr-diff-header-bg', fallback: 'hsl(var(--muted))' },\n border: { variable: '--rfr-diff-border', fallback: 'hsl(var(--border))' },\n addBg: { variable: '--rfr-diff-add-bg', fallback: 'rgba(46, 160, 67, 0.15)' },\n delBg: { variable: '--rfr-diff-del-bg', fallback: 'rgba(248, 81, 73, 0.15)' },\n addFg: { variable: '--rfr-diff-add-fg', fallback: '#3fb950' },\n delFg: { variable: '--rfr-diff-del-fg', fallback: '#f85149' },\n statusbar: { variable: '--rfr-diff-statusbar', fallback: 'hsl(var(--primary))' },\n statusbarFg: { variable: '--rfr-diff-statusbar-fg', fallback: 'hsl(var(--primary-foreground))' },\n },\n}\n\nexport const diffViewerVariants = cva({\n base: 'flex flex-col overflow-hidden font-sans text-sm',\n variants: {\n theme: {\n light: 'bg-white text-gray-900',\n dark: 'bg-gray-950 text-gray-100',\n },\n fullscreen: {\n 'true': 'h-screen',\n 'false': '',\n },\n },\n defaultVariants: {\n theme: 'dark',\n fullscreen: 'true',\n },\n})\n\nexport const sidebarVariants = cva({\n base: 'border-r overflow-auto flex-shrink-0',\n variants: {\n theme: {\n light: 'bg-gray-50 border-gray-200',\n dark: 'bg-gray-900 border-gray-800',\n },\n },\n defaultVariants: {\n theme: 'dark',\n },\n})\n\nexport const sidebarItemVariants = cva({\n base: 'px-2.5 py-1 cursor-pointer text-xs font-mono transition-colors',\n variants: {\n active: {\n 'true': 'border-l-2 border-blue-500',\n 'false': 'border-l-2 border-transparent',\n },\n theme: {\n light: '',\n dark: '',\n },\n },\n compoundVariants: [\n { active: 'true', theme: 'dark', class: 'bg-gray-800 text-gray-100' },\n { active: 'false', theme: 'dark', class: 'text-gray-400 hover:bg-gray-800/50' },\n { active: 'true', theme: 'light', class: 'bg-blue-50 text-gray-900' },\n { active: 'false', theme: 'light', class: 'text-gray-600 hover:bg-gray-100' },\n ],\n defaultVariants: {\n active: 'false',\n theme: 'dark',\n },\n})\n\nexport const tabBarVariants = cva({\n base: 'flex items-center border-b overflow-auto flex-shrink-0',\n variants: {\n theme: {\n light: 'bg-gray-50 border-gray-200',\n dark: 'bg-gray-900 border-gray-800',\n },\n },\n defaultVariants: {\n theme: 'dark',\n },\n})\n\nexport const tabVariants = cva({\n base: 'px-3 h-[35px] flex items-center gap-1 cursor-pointer text-xs font-mono border-r shrink-0',\n variants: {\n active: {\n 'true': '',\n 'false': '',\n },\n theme: {\n light: 'border-gray-200',\n dark: 'border-gray-800',\n },\n },\n compoundVariants: [\n { active: 'true', theme: 'dark', class: 'bg-gray-950 text-gray-100 border-b-gray-950 -mb-px' },\n { active: 'false', theme: 'dark', class: 'text-gray-400' },\n { active: 'true', theme: 'light', class: 'bg-white text-gray-900 border-b-white -mb-px' },\n { active: 'false', theme: 'light', class: 'text-gray-500' },\n ],\n defaultVariants: {\n active: 'false',\n theme: 'dark',\n },\n})\n\nexport const statusBarVariants = cva({\n base: 'h-[22px] flex items-center px-2.5 gap-3 text-[11px] font-mono flex-shrink-0',\n variants: {\n theme: {\n light: 'bg-blue-600 text-white',\n dark: 'bg-blue-700 text-white',\n },\n },\n defaultVariants: {\n theme: 'dark',\n },\n})\n", "import * as React from 'react'\nimport {\n createDiffViewer,\n diffViewerVariants,\n sidebarVariants,\n sidebarItemVariants,\n tabBarVariants,\n tabVariants,\n statusBarVariants,\n type DiffFile,\n type DiffViewerTheme,\n type DiffViewMode,\n} from '@refraction-ui/diff-viewer'\nimport { cn } from '@refraction-ui/shared'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DiffViewerProps {\n /** Files in this diff */\n files: DiffFile[]\n /** Original file content (left side) */\n original?: string\n /** Modified file content (right side) */\n modified?: string\n /** Language for syntax highlighting (auto-detected from file if omitted) */\n language?: string\n /** Monaco theme name */\n monacoTheme?: string\n /** Color theme */\n theme?: DiffViewerTheme\n /** View mode */\n viewMode?: DiffViewMode\n /** Whether to show the file sidebar */\n showSidebar?: boolean\n /** Whether to show the tab bar */\n showTabs?: boolean\n /** Whether to show the status bar */\n showStatusBar?: boolean\n /** Sidebar width in pixels */\n sidebarWidth?: number\n /** Active file index */\n activeFileIndex?: number\n /** Called when the active file changes */\n onFileSelect?: (index: number) => void\n /** Called when view mode changes */\n onViewModeChange?: (mode: DiffViewMode) => void\n /** Title shown in the status bar */\n statusBarTitle?: string\n /** Status text shown in the status bar */\n statusBarStatus?: string\n /** Additional class name */\n className?: string\n /** Monaco DiffEditor options override */\n editorOptions?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Lazy Monaco import\n// ---------------------------------------------------------------------------\n\nconst MonacoDiffEditor = React.lazy(() =>\n import('@monaco-editor/react').then((m) => ({ default: m.DiffEditor })),\n)\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const DiffViewer = React.forwardRef<HTMLDivElement, DiffViewerProps>(\n function DiffViewer(\n {\n files,\n original = '',\n modified = '',\n language,\n monacoTheme = 'vs-dark',\n theme = 'dark',\n viewMode: controlledViewMode = 'side-by-side',\n showSidebar = true,\n showTabs = true,\n showStatusBar = true,\n sidebarWidth = 220,\n activeFileIndex: controlledIndex = 0,\n onFileSelect,\n onViewModeChange,\n statusBarTitle,\n statusBarStatus,\n className,\n editorOptions,\n },\n ref,\n ) {\n const [activeIdx, setActiveIdx] = React.useState(controlledIndex)\n const [sidebarOpen, setSidebarOpen] = React.useState(showSidebar)\n const [viewMode, setViewMode] = React.useState<DiffViewMode>(controlledViewMode)\n\n // Sync controlled props\n React.useEffect(() => setActiveIdx(controlledIndex), [controlledIndex])\n React.useEffect(() => setViewMode(controlledViewMode), [controlledViewMode])\n\n const api = React.useMemo(\n () =>\n createDiffViewer({\n files,\n activeFileIndex: activeIdx,\n viewMode,\n theme,\n language,\n sidebarOpen,\n onFileSelect: (i: number) => {\n setActiveIdx(i)\n onFileSelect?.(i)\n },\n onViewModeChange: (m: DiffViewMode) => {\n setViewMode(m)\n onViewModeChange?.(m)\n },\n onSidebarToggle: () => setSidebarOpen((s) => !s),\n }),\n [files, activeIdx, viewMode, theme, language, sidebarOpen, onFileSelect, onViewModeChange],\n )\n\n const activeFile = files[activeIdx]\n const detectedLang = language || (activeFile ? api.getLanguageForFile(activeFile.path) : 'plaintext')\n\n // Keyboard navigation\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return\n if (e.key === 'j') {\n setActiveIdx((i) => {\n const next = Math.min(i + 1, files.length - 1)\n onFileSelect?.(next)\n return next\n })\n } else if (e.key === 'k') {\n setActiveIdx((i) => {\n const prev = Math.max(i - 1, 0)\n onFileSelect?.(prev)\n return prev\n })\n } else if (e.key === 'b') {\n setSidebarOpen((s) => !s)\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [files.length, onFileSelect])\n\n return (\n <div\n ref={ref}\n className={cn(diffViewerVariants({ theme, fullscreen: 'true' }), className)}\n {...api.ariaProps}\n {...api.dataAttributes}\n >\n {/* Main area: sidebar + editor */}\n <div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>\n {/* File sidebar */}\n {sidebarOpen && (\n <div className={sidebarVariants({ theme })} style={{ width: sidebarWidth }}>\n <div className=\"px-2.5 py-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {`Files (${files.length})`}\n </div>\n {files.map((f, i) => {\n const active = i === activeIdx\n const fname = f.path.split('/').pop() || f.path\n const dir = f.path.split('/').slice(0, -1).join('/')\n return (\n <div\n key={f.path}\n onClick={() => {\n setActiveIdx(i)\n onFileSelect?.(i)\n }}\n className={sidebarItemVariants({ active: active ? 'true' : 'false', theme })}\n >\n <div className=\"flex items-center gap-1\">\n <span className=\"text-[10px]\">{api.getFileStatusIcon(f.status)}</span>\n <span className={active ? '' : 'opacity-70'}>{fname}</span>\n <span className=\"ml-auto text-[10px]\">\n <span className=\"text-green-500\">{`+${f.additions}`}</span>\n {f.deletions > 0 && <span className=\"text-red-500\">{`-${f.deletions}`}</span>}\n </span>\n </div>\n {dir && (\n <div className=\"text-[10px] opacity-50 mt-0.5 pl-4\">{dir}</div>\n )}\n </div>\n )\n })}\n </div>\n )}\n\n {/* Editor area */}\n <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>\n {/* Tab bar */}\n {showTabs && activeFile && (\n <div className={tabBarVariants({ theme })}>\n {files.map((f, i) => {\n const active = i === activeIdx\n const fname = f.path.split('/').pop() || f.path\n return (\n <div\n key={f.path}\n onClick={() => {\n setActiveIdx(i)\n onFileSelect?.(i)\n }}\n className={tabVariants({ active: active ? 'true' : 'false', theme })}\n >\n <span className=\"text-[9px]\">{api.getFileStatusIcon(f.status)}</span>\n {fname}\n </div>\n )\n })}\n </div>\n )}\n\n {/* Monaco DiffEditor */}\n <div style={{ flex: 1 }}>\n <React.Suspense\n fallback={\n <div className=\"flex items-center justify-center h-full text-xs opacity-50\">\n Loading editor...\n </div>\n }\n >\n <MonacoDiffEditor\n original={original}\n modified={modified}\n language={detectedLang}\n theme={monacoTheme}\n options={{\n readOnly: true,\n renderSideBySide: viewMode === 'side-by-side',\n minimap: { enabled: true, scale: 1, showSlider: 'mouseover' },\n fontSize: 13,\n lineHeight: 20,\n fontFamily: \"'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace\",\n fontLigatures: true,\n scrollBeyondLastLine: false,\n renderOverviewRuler: true,\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n padding: { top: 8, bottom: 8 },\n renderWhitespace: 'selection',\n bracketPairColorization: { enabled: true },\n guides: { indentation: true },\n smoothScrolling: true,\n cursorBlinking: 'smooth',\n ...editorOptions,\n }}\n />\n </React.Suspense>\n </div>\n </div>\n </div>\n\n {/* Status bar */}\n {showStatusBar && (\n <div className={statusBarVariants({ theme })}>\n {statusBarTitle && <span>{statusBarTitle}</span>}\n {activeFile && <span>{activeFile.path}</span>}\n <span style={{ marginLeft: 'auto' }}>\n {`+${api.totalAdditions()} -${api.totalDeletions()}`}\n </span>\n {statusBarStatus && <span>{statusBarStatus}</span>}\n <span>\n {`${files.length} ${files.length === 1 ? 'file' : 'files'}`}\n </span>\n </div>\n )}\n </div>\n )\n },\n)\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type DiffViewerTheme = 'light' | 'dark'\nexport type DiffViewMode = 'side-by-side' | 'inline'\n\nexport type DiffFileStatus = 'added' | 'modified' | 'deleted' | 'renamed'\n\nexport interface DiffFile {\n /** File path */\n path: string\n /** Change status */\n status: DiffFileStatus\n /** Lines added */\n additions: number\n /** Lines deleted */\n deletions: number\n /** Unified diff content for this file */\n diff?: string\n /** Old path for renamed files */\n oldPath?: string\n}\n\nexport interface DiffViewerProps {\n /** Files in this diff */\n files?: DiffFile[]\n /** Index of the active file */\n activeFileIndex?: number\n /** View mode */\n viewMode?: DiffViewMode\n /** Theme */\n theme?: DiffViewerTheme\n /** Language for syntax highlighting */\n language?: string\n /** Whether the sidebar is visible */\n sidebarOpen?: boolean\n /** Callback when active file changes */\n onFileSelect?: (index: number) => void\n /** Callback when view mode changes */\n onViewModeChange?: (mode: DiffViewMode) => void\n /** Callback when sidebar toggled */\n onSidebarToggle?: () => void\n}\n\nexport interface DiffViewerState {\n activeFileIndex: number\n viewMode: DiffViewMode\n theme: DiffViewerTheme\n language: string\n sidebarOpen: boolean\n files: DiffFile[]\n}\n\nexport interface DiffViewerAPI {\n /** Current viewer state */\n state: DiffViewerState\n /** ARIA attributes for the root element */\n ariaProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** Data attributes for CSS styling hooks */\n dataAttributes: Record<string, string>\n /** Select a file by index */\n selectFile: (index: number) => void\n /** Move to next file */\n nextFile: () => void\n /** Move to previous file */\n prevFile: () => void\n /** Toggle view mode */\n toggleViewMode: () => void\n /** Toggle sidebar */\n toggleSidebar: () => void\n /** Get language label for a file path */\n getLanguageForFile: (path: string) => string\n /** Get human-readable file status icon */\n getFileStatusIcon: (status: DiffFileStatus) => string\n /** Get total additions across all files */\n totalAdditions: () => number\n /** Get total deletions across all files */\n totalDeletions: () => number\n}\n\n// ---------------------------------------------------------------------------\n// Language detection\n// ---------------------------------------------------------------------------\n\nconst extToLanguage: Record<string, string> = {\n ts: 'typescript',\n tsx: 'typescript',\n js: 'javascript',\n jsx: 'javascript',\n py: 'python',\n go: 'go',\n rs: 'rust',\n rb: 'ruby',\n java: 'java',\n swift: 'swift',\n kt: 'kotlin',\n dart: 'dart',\n php: 'php',\n lua: 'lua',\n r: 'r',\n scala: 'scala',\n json: 'json',\n yaml: 'yaml',\n yml: 'yaml',\n md: 'markdown',\n html: 'html',\n css: 'css',\n scss: 'scss',\n sql: 'sql',\n sh: 'shell',\n bash: 'shell',\n zsh: 'shell',\n xml: 'xml',\n toml: 'toml',\n proto: 'protobuf',\n dockerfile: 'dockerfile',\n cpp: 'cpp',\n c: 'c',\n}\n\nfunction getLanguageForFile(filePath: string): string {\n const ext = filePath.split('.').pop()?.toLowerCase() || ''\n const basename = filePath.split('/').pop()?.toLowerCase() || ''\n if (basename === 'dockerfile') return 'dockerfile'\n return extToLanguage[ext] || 'plaintext'\n}\n\n// ---------------------------------------------------------------------------\n// Status icons\n// ---------------------------------------------------------------------------\n\nconst statusIcons: Record<DiffFileStatus, string> = {\n added: '\\u{1F7E2}', // green circle\n modified: '\\u{1F7E1}', // yellow circle\n deleted: '\\u{1F534}', // red circle\n renamed: '\\u{1F535}', // blue circle\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a headless diff viewer API.\n *\n * Manages file selection, view mode, sidebar state, and produces ARIA/data\n * attributes. The actual rendering (Monaco DiffEditor, custom renderer, etc.)\n * is handled by framework-specific wrappers.\n */\nexport function createDiffViewer(props: DiffViewerProps = {}): DiffViewerAPI {\n const {\n files = [],\n activeFileIndex = 0,\n viewMode = 'side-by-side',\n theme = 'dark',\n language,\n sidebarOpen = true,\n onFileSelect,\n onViewModeChange,\n onSidebarToggle,\n } = props\n\n let currentIndex = Math.min(activeFileIndex, Math.max(0, files.length - 1))\n let currentViewMode = viewMode\n let currentSidebarOpen = sidebarOpen\n\n const state: DiffViewerState = {\n activeFileIndex: currentIndex,\n viewMode: currentViewMode,\n theme,\n language: language || (files[currentIndex] ? getLanguageForFile(files[currentIndex].path) : 'plaintext'),\n sidebarOpen: currentSidebarOpen,\n files,\n }\n\n const ariaProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n role: 'region',\n 'aria-label': 'Diff viewer',\n }\n\n const dataAttributes: Record<string, string> = {\n 'data-view-mode': currentViewMode,\n 'data-theme': theme,\n 'data-file-count': String(files.length),\n }\n\n if (!currentSidebarOpen) {\n dataAttributes['data-sidebar-collapsed'] = ''\n }\n\n function selectFile(index: number) {\n if (index >= 0 && index < files.length) {\n currentIndex = index\n state.activeFileIndex = index\n state.language = language || getLanguageForFile(files[index].path)\n onFileSelect?.(index)\n }\n }\n\n function nextFile() {\n if (currentIndex < files.length - 1) {\n selectFile(currentIndex + 1)\n }\n }\n\n function prevFile() {\n if (currentIndex > 0) {\n selectFile(currentIndex - 1)\n }\n }\n\n function toggleViewMode() {\n currentViewMode = currentViewMode === 'side-by-side' ? 'inline' : 'side-by-side'\n state.viewMode = currentViewMode\n dataAttributes['data-view-mode'] = currentViewMode\n onViewModeChange?.(currentViewMode)\n }\n\n function toggleSidebar() {\n currentSidebarOpen = !currentSidebarOpen\n state.sidebarOpen = currentSidebarOpen\n if (currentSidebarOpen) {\n delete dataAttributes['data-sidebar-collapsed']\n } else {\n dataAttributes['data-sidebar-collapsed'] = ''\n }\n onSidebarToggle?.()\n }\n\n function totalAdditions(): number {\n return files.reduce((sum, f) => sum + f.additions, 0)\n }\n\n function totalDeletions(): number {\n return files.reduce((sum, f) => sum + f.deletions, 0)\n }\n\n return {\n state,\n ariaProps,\n dataAttributes,\n selectFile,\n nextFile,\n prevFile,\n toggleViewMode,\n toggleSidebar,\n getLanguageForFile,\n getFileStatusIcon: (status: DiffFileStatus) => statusIcons[status] || '\\u{26AA}',\n totalAdditions,\n totalDeletions,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\nimport type { TokenContract } from '@refraction-ui/shared'\n\nexport const diffViewerTokens: TokenContract = {\n name: 'diff-viewer',\n tokens: {\n bg: { variable: '--rfr-diff-bg', fallback: 'hsl(var(--background))' },\n fg: { variable: '--rfr-diff-fg', fallback: 'hsl(var(--foreground))' },\n sidebarBg: { variable: '--rfr-diff-sidebar-bg', fallback: 'hsl(var(--muted))' },\n headerBg: { variable: '--rfr-diff-header-bg', fallback: 'hsl(var(--muted))' },\n border: { variable: '--rfr-diff-border', fallback: 'hsl(var(--border))' },\n // Diff add/del colors are syntax semantics (like code highlighting), not\n // theme status tones — deliberately NOT routed through the success/\n // destructive tokens. The --rfr-diff-* variables are the theming seam.\n addBg: { variable: '--rfr-diff-add-bg', fallback: 'rgba(46, 160, 67, 0.15)' },\n delBg: { variable: '--rfr-diff-del-bg', fallback: 'rgba(248, 81, 73, 0.15)' },\n addFg: { variable: '--rfr-diff-add-fg', fallback: '#3fb950' },\n delFg: { variable: '--rfr-diff-del-fg', fallback: '#f85149' },\n statusbar: { variable: '--rfr-diff-statusbar', fallback: 'hsl(var(--primary))' },\n statusbarFg: { variable: '--rfr-diff-statusbar-fg', fallback: 'hsl(var(--primary-foreground))' },\n },\n}\n\nexport const diffViewerVariants = cva({\n base: 'flex flex-col overflow-hidden font-sans text-sm',\n variants: {\n theme: {\n light: 'bg-white text-gray-900',\n dark: 'bg-gray-950 text-gray-100',\n },\n fullscreen: {\n 'true': 'h-screen',\n 'false': '',\n },\n },\n defaultVariants: {\n theme: 'dark',\n fullscreen: 'true',\n },\n})\n\nexport const sidebarVariants = cva({\n base: 'border-r overflow-auto flex-shrink-0',\n variants: {\n theme: {\n light: 'bg-gray-50 border-gray-200',\n dark: 'bg-gray-900 border-gray-800',\n },\n },\n defaultVariants: {\n theme: 'dark',\n },\n})\n\nexport const sidebarItemVariants = cva({\n base: 'px-2.5 py-1 cursor-pointer text-xs font-mono transition-colors',\n variants: {\n active: {\n 'true': 'border-l-2 border-blue-500',\n 'false': 'border-l-2 border-transparent',\n },\n theme: {\n light: '',\n dark: '',\n },\n },\n compoundVariants: [\n { active: 'true', theme: 'dark', class: 'bg-gray-800 text-gray-100' },\n { active: 'false', theme: 'dark', class: 'text-gray-400 hover:bg-gray-800/50' },\n { active: 'true', theme: 'light', class: 'bg-blue-50 text-gray-900' },\n { active: 'false', theme: 'light', class: 'text-gray-600 hover:bg-gray-100' },\n ],\n defaultVariants: {\n active: 'false',\n theme: 'dark',\n },\n})\n\nexport const tabBarVariants = cva({\n base: 'flex items-center border-b overflow-auto flex-shrink-0',\n variants: {\n theme: {\n light: 'bg-gray-50 border-gray-200',\n dark: 'bg-gray-900 border-gray-800',\n },\n },\n defaultVariants: {\n theme: 'dark',\n },\n})\n\nexport const tabVariants = cva({\n base: 'px-3 h-[35px] flex items-center gap-1 cursor-pointer text-xs font-mono border-r shrink-0',\n variants: {\n active: {\n 'true': '',\n 'false': '',\n },\n theme: {\n light: 'border-gray-200',\n dark: 'border-gray-800',\n },\n },\n compoundVariants: [\n { active: 'true', theme: 'dark', class: 'bg-gray-950 text-gray-100 border-b-gray-950 -mb-px' },\n { active: 'false', theme: 'dark', class: 'text-gray-400' },\n { active: 'true', theme: 'light', class: 'bg-white text-gray-900 border-b-white -mb-px' },\n { active: 'false', theme: 'light', class: 'text-gray-500' },\n ],\n defaultVariants: {\n active: 'false',\n theme: 'dark',\n },\n})\n\nexport const statusBarVariants = cva({\n base: 'h-[22px] flex items-center px-2.5 gap-3 text-[11px] font-mono flex-shrink-0',\n variants: {\n theme: {\n light: 'bg-blue-600 text-white',\n dark: 'bg-blue-700 text-white',\n },\n },\n defaultVariants: {\n theme: 'dark',\n },\n})\n", "import * as React from 'react'\nimport {\n createDiffViewer,\n diffViewerVariants,\n sidebarVariants,\n sidebarItemVariants,\n tabBarVariants,\n tabVariants,\n statusBarVariants,\n type DiffFile,\n type DiffViewerTheme,\n type DiffViewMode,\n} from '@refraction-ui/diff-viewer'\nimport { cn } from '@refraction-ui/shared'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DiffViewerProps {\n /** Files in this diff */\n files: DiffFile[]\n /** Original file content (left side) */\n original?: string\n /** Modified file content (right side) */\n modified?: string\n /** Language for syntax highlighting (auto-detected from file if omitted) */\n language?: string\n /** Monaco theme name */\n monacoTheme?: string\n /** Color theme */\n theme?: DiffViewerTheme\n /** View mode */\n viewMode?: DiffViewMode\n /** Whether to show the file sidebar */\n showSidebar?: boolean\n /** Whether to show the tab bar */\n showTabs?: boolean\n /** Whether to show the status bar */\n showStatusBar?: boolean\n /** Sidebar width in pixels */\n sidebarWidth?: number\n /** Active file index */\n activeFileIndex?: number\n /** Called when the active file changes */\n onFileSelect?: (index: number) => void\n /** Called when view mode changes */\n onViewModeChange?: (mode: DiffViewMode) => void\n /** Title shown in the status bar */\n statusBarTitle?: string\n /** Status text shown in the status bar */\n statusBarStatus?: string\n /** Additional class name */\n className?: string\n /** Monaco DiffEditor options override */\n editorOptions?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Lazy Monaco import\n// ---------------------------------------------------------------------------\n\nconst MonacoDiffEditor = React.lazy(() =>\n import('@monaco-editor/react').then((m) => ({ default: m.DiffEditor })),\n)\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const DiffViewer = React.forwardRef<HTMLDivElement, DiffViewerProps>(\n function DiffViewer(\n {\n files,\n original = '',\n modified = '',\n language,\n monacoTheme = 'vs-dark',\n theme = 'dark',\n viewMode: controlledViewMode = 'side-by-side',\n showSidebar = true,\n showTabs = true,\n showStatusBar = true,\n sidebarWidth = 220,\n activeFileIndex: controlledIndex = 0,\n onFileSelect,\n onViewModeChange,\n statusBarTitle,\n statusBarStatus,\n className,\n editorOptions,\n },\n ref,\n ) {\n const [activeIdx, setActiveIdx] = React.useState(controlledIndex)\n const [sidebarOpen, setSidebarOpen] = React.useState(showSidebar)\n const [viewMode, setViewMode] = React.useState<DiffViewMode>(controlledViewMode)\n\n // Sync controlled props\n React.useEffect(() => setActiveIdx(controlledIndex), [controlledIndex])\n React.useEffect(() => setViewMode(controlledViewMode), [controlledViewMode])\n\n const api = React.useMemo(\n () =>\n createDiffViewer({\n files,\n activeFileIndex: activeIdx,\n viewMode,\n theme,\n language,\n sidebarOpen,\n onFileSelect: (i: number) => {\n setActiveIdx(i)\n onFileSelect?.(i)\n },\n onViewModeChange: (m: DiffViewMode) => {\n setViewMode(m)\n onViewModeChange?.(m)\n },\n onSidebarToggle: () => setSidebarOpen((s) => !s),\n }),\n [files, activeIdx, viewMode, theme, language, sidebarOpen, onFileSelect, onViewModeChange],\n )\n\n const activeFile = files[activeIdx]\n const detectedLang = language || (activeFile ? api.getLanguageForFile(activeFile.path) : 'plaintext')\n\n // Keyboard navigation\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return\n if (e.key === 'j') {\n setActiveIdx((i) => {\n const next = Math.min(i + 1, files.length - 1)\n onFileSelect?.(next)\n return next\n })\n } else if (e.key === 'k') {\n setActiveIdx((i) => {\n const prev = Math.max(i - 1, 0)\n onFileSelect?.(prev)\n return prev\n })\n } else if (e.key === 'b') {\n setSidebarOpen((s) => !s)\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [files.length, onFileSelect])\n\n return (\n <div\n ref={ref}\n className={cn(diffViewerVariants({ theme, fullscreen: 'true' }), className)}\n {...api.ariaProps}\n {...api.dataAttributes}\n >\n {/* Main area: sidebar + editor */}\n <div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>\n {/* File sidebar */}\n {sidebarOpen && (\n <div className={sidebarVariants({ theme })} style={{ width: sidebarWidth }}>\n <div className=\"px-2.5 py-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {`Files (${files.length})`}\n </div>\n {files.map((f, i) => {\n const active = i === activeIdx\n const fname = f.path.split('/').pop() || f.path\n const dir = f.path.split('/').slice(0, -1).join('/')\n return (\n <div\n key={f.path}\n onClick={() => {\n setActiveIdx(i)\n onFileSelect?.(i)\n }}\n className={sidebarItemVariants({ active: active ? 'true' : 'false', theme })}\n >\n <div className=\"flex items-center gap-1\">\n <span className=\"text-[10px]\">{api.getFileStatusIcon(f.status)}</span>\n <span className={active ? '' : 'opacity-70'}>{fname}</span>\n <span className=\"ml-auto text-[10px]\">\n <span className=\"text-green-500\">{`+${f.additions}`}</span>\n {f.deletions > 0 && <span className=\"text-red-500\">{`-${f.deletions}`}</span>}\n </span>\n </div>\n {dir && (\n <div className=\"text-[10px] opacity-50 mt-0.5 pl-4\">{dir}</div>\n )}\n </div>\n )\n })}\n </div>\n )}\n\n {/* Editor area */}\n <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>\n {/* Tab bar */}\n {showTabs && activeFile && (\n <div className={tabBarVariants({ theme })}>\n {files.map((f, i) => {\n const active = i === activeIdx\n const fname = f.path.split('/').pop() || f.path\n return (\n <div\n key={f.path}\n onClick={() => {\n setActiveIdx(i)\n onFileSelect?.(i)\n }}\n className={tabVariants({ active: active ? 'true' : 'false', theme })}\n >\n <span className=\"text-[9px]\">{api.getFileStatusIcon(f.status)}</span>\n {fname}\n </div>\n )\n })}\n </div>\n )}\n\n {/* Monaco DiffEditor */}\n <div style={{ flex: 1 }}>\n <React.Suspense\n fallback={\n <div className=\"flex items-center justify-center h-full text-xs opacity-50\">\n Loading editor...\n </div>\n }\n >\n <MonacoDiffEditor\n original={original}\n modified={modified}\n language={detectedLang}\n theme={monacoTheme}\n options={{\n readOnly: true,\n renderSideBySide: viewMode === 'side-by-side',\n minimap: { enabled: true, scale: 1, showSlider: 'mouseover' },\n fontSize: 13,\n lineHeight: 20,\n fontFamily: \"'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace\",\n fontLigatures: true,\n scrollBeyondLastLine: false,\n renderOverviewRuler: true,\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n padding: { top: 8, bottom: 8 },\n renderWhitespace: 'selection',\n bracketPairColorization: { enabled: true },\n guides: { indentation: true },\n smoothScrolling: true,\n cursorBlinking: 'smooth',\n ...editorOptions,\n }}\n />\n </React.Suspense>\n </div>\n </div>\n </div>\n\n {/* Status bar */}\n {showStatusBar && (\n <div className={statusBarVariants({ theme })}>\n {statusBarTitle && <span>{statusBarTitle}</span>}\n {activeFile && <span>{activeFile.path}</span>}\n <span style={{ marginLeft: 'auto' }}>\n {`+${api.totalAdditions()} -${api.totalDeletions()}`}\n </span>\n {statusBarStatus && <span>{statusBarStatus}</span>}\n <span>\n {`${files.length} ${files.length === 1 ? 'file' : 'files'}`}\n </span>\n </div>\n )}\n </div>\n )\n },\n)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["DiffViewer"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../presence-indicator/src/presence-indicator.ts", "../../presence-indicator/src/presence-indicator.styles.ts", "../../react-presence-indicator/src/presence-indicator.tsx"],
|
|
4
|
-
"sourcesContent": ["export type PresenceStatus = 'online' | 'offline' | 'away' | 'busy' | 'dnd'\n\nexport interface PresenceProps {\n /** The presence status to display */\n status: PresenceStatus\n /** Show a text label alongside the indicator */\n showLabel?: boolean\n /** Custom label override */\n label?: string\n}\n\nexport interface PresenceAPI {\n /** ARIA attributes for the indicator */\n ariaProps: Record<string, string | number | boolean>\n /** CSS color for the status dot */\n color: string\n /** Human-readable label for the status */\n label: string\n /** Whether to show the label text */\n showLabel: boolean\n /** The current status */\n status: PresenceStatus\n}\n\nconst STATUS_COLORS: Record<PresenceStatus, string> = {\n online: 'green',\n offline: 'gray',\n away: 'yellow',\n busy: 'red',\n dnd: 'red',\n}\n\nconst STATUS_LABELS: Record<PresenceStatus, string> = {\n online: 'Online',\n offline: 'Offline',\n away: 'Away',\n busy: 'Busy',\n dnd: 'Do Not Disturb',\n}\n\nexport { STATUS_COLORS, STATUS_LABELS }\n\nexport function createPresence(props: PresenceProps): PresenceAPI {\n const { status, showLabel: showLabelProp = false, label: labelOverride } = props\n\n const color = STATUS_COLORS[status]\n const label = labelOverride ?? STATUS_LABELS[status]\n\n const ariaProps: Record<string, string | number | boolean> = {\n role: 'status',\n 'aria-label': label,\n }\n\n return {\n ariaProps,\n color,\n label,\n showLabel: showLabelProp,\n status,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const presenceDotVariants = cva({\n base: 'inline-block rounded-full',\n variants: {\n status: {\n online: 'bg-success',\n offline: 'bg-
|
|
4
|
+
"sourcesContent": ["export type PresenceStatus = 'online' | 'offline' | 'away' | 'busy' | 'dnd'\n\nexport interface PresenceProps {\n /** The presence status to display */\n status: PresenceStatus\n /** Show a text label alongside the indicator */\n showLabel?: boolean\n /** Custom label override */\n label?: string\n}\n\nexport interface PresenceAPI {\n /** ARIA attributes for the indicator */\n ariaProps: Record<string, string | number | boolean>\n /** CSS color for the status dot */\n color: string\n /** Human-readable label for the status */\n label: string\n /** Whether to show the label text */\n showLabel: boolean\n /** The current status */\n status: PresenceStatus\n}\n\nconst STATUS_COLORS: Record<PresenceStatus, string> = {\n online: 'green',\n offline: 'gray',\n away: 'yellow',\n busy: 'red',\n dnd: 'red',\n}\n\nconst STATUS_LABELS: Record<PresenceStatus, string> = {\n online: 'Online',\n offline: 'Offline',\n away: 'Away',\n busy: 'Busy',\n dnd: 'Do Not Disturb',\n}\n\nexport { STATUS_COLORS, STATUS_LABELS }\n\nexport function createPresence(props: PresenceProps): PresenceAPI {\n const { status, showLabel: showLabelProp = false, label: labelOverride } = props\n\n const color = STATUS_COLORS[status]\n const label = labelOverride ?? STATUS_LABELS[status]\n\n const ariaProps: Record<string, string | number | boolean> = {\n role: 'status',\n 'aria-label': label,\n }\n\n return {\n ariaProps,\n color,\n label,\n showLabel: showLabelProp,\n status,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const presenceDotVariants = cva({\n base: 'inline-block rounded-full',\n variants: {\n status: {\n online: 'bg-success',\n offline: 'bg-neutral',\n away: 'bg-warning',\n busy: 'bg-destructive',\n dnd: 'bg-destructive',\n },\n size: {\n sm: 'h-2 w-2',\n md: 'h-2.5 w-2.5',\n lg: 'h-3 w-3',\n },\n },\n defaultVariants: {\n status: 'offline',\n size: 'md',\n },\n})\n\nexport const presenceContainerStyles =\n 'inline-flex items-center gap-1.5'\n\nexport const presenceLabelStyles =\n 'text-sm text-muted-foreground'\n", "import * as React from 'react'\nimport {\n createPresence,\n presenceDotVariants,\n presenceContainerStyles,\n presenceLabelStyles,\n type PresenceStatus,\n} from '@refraction-ui/presence-indicator'\nimport { cn } from '@refraction-ui/shared'\n\nexport interface PresenceIndicatorProps {\n status: PresenceStatus\n showLabel?: boolean\n label?: string\n size?: 'sm' | 'md' | 'lg'\n className?: string\n}\n\nexport const PresenceIndicator = React.forwardRef<HTMLSpanElement, PresenceIndicatorProps>(\n function PresenceIndicator({ status, showLabel = false, label, size = 'md', className }, ref) {\n const api = createPresence({ status, showLabel, label })\n\n return React.createElement(\n 'span',\n { ref, ...api.ariaProps, className: cn(presenceContainerStyles, className) },\n React.createElement('span', {\n className: presenceDotVariants({ status, size }),\n }),\n api.showLabel &&\n React.createElement('span', { className: presenceLabelStyles }, api.label),\n )\n },\n)\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;AAwBA,IAAM,gBAAgD;EACpD,QAAQ;EACR,SAAS;EACT,MAAM;EACN,MAAM;EACN,KAAK;AACP;AAEA,IAAM,gBAAgD;EACpD,QAAQ;EACR,SAAS;EACT,MAAM;EACN,MAAM;EACN,KAAK;AACP;AAIO,SAAS,eAAe,OAAmC;AAChE,QAAM,EAAE,QAAQ,WAAW,gBAAgB,OAAO,OAAO,cAAA,IAAkB;AAE3E,QAAM,QAAQ,cAAc,MAAM;AAClC,QAAM,QAAQ,iBAAiB,cAAc,MAAM;AAEnD,QAAM,YAAuD;IAC3D,MAAM;IACN,cAAc;EAAA;AAGhB,SAAO;IACL;IACA;IACA;IACA,WAAW;IACX;EAAA;AAEJ;AC1DO,IAAM,sBAAsB,IAAI;EACrC,MAAM;EACN,UAAU;IACR,QAAQ;MACN,QAAQ;MACR,SAAS;MACT,MAAM;MACN,MAAM;MACN,KAAK;IAAA;IAEP,MAAM;MACJ,IAAI;MACJ,IAAI;MACJ,IAAI;IAAA;EACN;EAEF,iBAAiB;IACf,QAAQ;IACR,MAAM;EAAA;AAEV,CAAC;AAEM,IAAM,0BACX;AAEK,IAAM,sBACX;;;ACVK,IAAM,oBAA0B;EACrC,SAASA,mBAAkB,EAAE,QAAQ,YAAY,OAAO,OAAO,OAAO,MAAM,UAAA,GAAa,KAAK;AAC9F,UAAM,MAAM,eAAe,EAAE,QAAQ,WAAW,MAAA,CAAO;AAEvD,WAAa;MACX;MACA,EAAE,KAAK,GAAG,IAAI,WAAW,WAAW,GAAG,yBAAyB,SAAS,EAAA;MACnE,oBAAc,QAAQ;QAC1B,WAAW,oBAAoB,EAAE,QAAQ,KAAA,CAAM;MAAA,CAChD;MACD,IAAI,aACI,oBAAc,QAAQ,EAAE,WAAW,oBAAA,GAAuB,IAAI,KAAK;IAAA;EAE7E;AACF;",
|
|
6
6
|
"names": ["PresenceIndicator"]
|
|
7
7
|
}
|
package/dist/react-separator.js
CHANGED
|
@@ -22,8 +22,16 @@ var separatorVariants = cva({
|
|
|
22
22
|
base: "",
|
|
23
23
|
variants: {
|
|
24
24
|
orientation: {
|
|
25
|
-
horizontal: "h-px w-full
|
|
26
|
-
vertical: "w-px h-full
|
|
25
|
+
horizontal: "h-px w-full",
|
|
26
|
+
vertical: "w-px h-full"
|
|
27
|
+
},
|
|
28
|
+
// Line color (issue #485). `default` is the standard border token;
|
|
29
|
+
// `subtle` uses the hairline `border-subtle` token for a lighter rule.
|
|
30
|
+
// Keeping the color here (not in `orientation`) keeps every existing call
|
|
31
|
+
// pixel-identical while adding the subtle option.
|
|
32
|
+
tone: {
|
|
33
|
+
default: "bg-border",
|
|
34
|
+
subtle: "bg-border-subtle"
|
|
27
35
|
},
|
|
28
36
|
labeled: {
|
|
29
37
|
true: "flex items-center",
|
|
@@ -32,10 +40,12 @@ var separatorVariants = cva({
|
|
|
32
40
|
},
|
|
33
41
|
defaultVariants: {
|
|
34
42
|
orientation: "horizontal",
|
|
43
|
+
tone: "default",
|
|
35
44
|
labeled: "false"
|
|
36
45
|
}
|
|
37
46
|
});
|
|
38
47
|
var separatorLineClass = "h-px flex-1 bg-border";
|
|
48
|
+
var separatorSubtleLineClass = "h-px flex-1 bg-border-subtle";
|
|
39
49
|
var separatorLabelClass = "px-3 text-xs uppercase tracking-wide text-muted-foreground";
|
|
40
50
|
|
|
41
51
|
// ../react-separator/dist/index.js
|
|
@@ -43,23 +53,25 @@ import { jsxs, jsx } from "react/jsx-runtime";
|
|
|
43
53
|
var Separator = React.forwardRef(
|
|
44
54
|
function Separator2({
|
|
45
55
|
orientation = "horizontal",
|
|
56
|
+
tone = "default",
|
|
46
57
|
label,
|
|
47
58
|
decorative = true,
|
|
48
59
|
className,
|
|
49
60
|
...props
|
|
50
61
|
}, ref) {
|
|
51
62
|
if (orientation === "horizontal" && label != null) {
|
|
63
|
+
const lineClass = tone === "subtle" ? separatorSubtleLineClass : separatorLineClass;
|
|
52
64
|
return /* @__PURE__ */ jsxs(
|
|
53
65
|
"div",
|
|
54
66
|
{
|
|
55
67
|
ref,
|
|
56
68
|
role: "none",
|
|
57
|
-
className: cn(separatorVariants({ labeled: "true" }), className),
|
|
69
|
+
className: cn(separatorVariants({ labeled: "true", tone }), className),
|
|
58
70
|
...props,
|
|
59
71
|
children: [
|
|
60
|
-
/* @__PURE__ */ jsx("span", { className:
|
|
72
|
+
/* @__PURE__ */ jsx("span", { className: lineClass }),
|
|
61
73
|
/* @__PURE__ */ jsx("span", { className: separatorLabelClass, children: label }),
|
|
62
|
-
/* @__PURE__ */ jsx("span", { className:
|
|
74
|
+
/* @__PURE__ */ jsx("span", { className: lineClass })
|
|
63
75
|
]
|
|
64
76
|
}
|
|
65
77
|
);
|
|
@@ -72,7 +84,7 @@ var Separator = React.forwardRef(
|
|
|
72
84
|
"div",
|
|
73
85
|
{
|
|
74
86
|
ref,
|
|
75
|
-
className: cn(separatorVariants({ orientation }), className),
|
|
87
|
+
className: cn(separatorVariants({ orientation, tone }), className),
|
|
76
88
|
...ariaProps,
|
|
77
89
|
...dataAttributes,
|
|
78
90
|
...props
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../separator/src/separator.ts", "../../separator/src/separator.styles.ts", "../../react-separator/src/separator.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\n\nexport type SeparatorOrientation = 'horizontal' | 'vertical'\n\nexport interface SeparatorProps {\n /** Orientation of the separator. Defaults to `'horizontal'`. */\n orientation?: SeparatorOrientation\n /**\n * When `true` (the default) the separator is purely visual and is hidden\n * from assistive tech (`role=\"none\"`). When `false` it is exposed as a\n * semantic `role=\"separator\"` with the appropriate `aria-orientation`.\n */\n decorative?: boolean\n}\n\n/**\n * ARIA attributes for a separator. Extends the shared base with\n * `aria-orientation`, which is specific to the `separator` role.\n */\nexport interface SeparatorAriaProps extends Partial<AccessibilityProps> {\n 'aria-orientation'?: SeparatorOrientation\n}\n\nexport interface SeparatorAPI {\n /** ARIA attributes to spread on the element. */\n ariaProps: SeparatorAriaProps\n /** Data attributes for CSS styling hooks. */\n dataAttributes: Record<string, string>\n}\n\n/**\n * Headless separator core — computes ARIA semantics and data attributes for\n * a separator rule. JSX-free so it can be shared across framework adapters.\n *\n * Per ARIA, a decorative separator must not be announced (`role=\"none\"`),\n * while a semantic one exposes `role=\"separator\"` and `aria-orientation`.\n */\nexport function createSeparator(props: SeparatorProps = {}): SeparatorAPI {\n const { orientation = 'horizontal', decorative = true } = props\n\n const ariaProps: SeparatorAriaProps = decorative\n ? { role: 'none' }\n : { role: 'separator', 'aria-orientation': orientation }\n\n const dataAttributes: Record<string, string> = {\n 'data-orientation': orientation,\n }\n\n return {\n ariaProps,\n dataAttributes,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\n/**\n * Visual variants for the separator rule.\n *\n * - `orientation: horizontal` → a full-width 1px line.\n * - `orientation: vertical` → a full-height 1px line.\n * - `labeled` → flex layout used by the labeled-divider variant, which\n * arranges a centered label between two flanking lines.\n */\nexport const separatorVariants = cva({\n base: '',\n variants: {\n orientation: {\n horizontal: 'h-px w-full
|
|
5
|
-
"mappings": ";;;;;;;;;AAqCO,SAAS,gBAAgB,QAAwB,CAAA,GAAkB;AACxE,QAAM,EAAE,cAAc,cAAc,aAAa,KAAA,IAAS;AAE1D,QAAM,YAAgC,aAClC,EAAE,MAAM,OAAA,IACR,EAAE,MAAM,aAAa,oBAAoB,YAAA;AAE7C,QAAM,iBAAyC;IAC7C,oBAAoB;EAAA;AAGtB,SAAO;IACL;IACA;EAAA;AAEJ;AC1CO,IAAM,oBAAoB,IAAI;EACnC,MAAM;EACN,UAAU;IACR,aAAa;MACX,YAAY;MACZ,UAAU;IAAA;
|
|
4
|
+
"sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\n\nexport type SeparatorOrientation = 'horizontal' | 'vertical'\n\nexport interface SeparatorProps {\n /** Orientation of the separator. Defaults to `'horizontal'`. */\n orientation?: SeparatorOrientation\n /**\n * When `true` (the default) the separator is purely visual and is hidden\n * from assistive tech (`role=\"none\"`). When `false` it is exposed as a\n * semantic `role=\"separator\"` with the appropriate `aria-orientation`.\n */\n decorative?: boolean\n}\n\n/**\n * ARIA attributes for a separator. Extends the shared base with\n * `aria-orientation`, which is specific to the `separator` role.\n */\nexport interface SeparatorAriaProps extends Partial<AccessibilityProps> {\n 'aria-orientation'?: SeparatorOrientation\n}\n\nexport interface SeparatorAPI {\n /** ARIA attributes to spread on the element. */\n ariaProps: SeparatorAriaProps\n /** Data attributes for CSS styling hooks. */\n dataAttributes: Record<string, string>\n}\n\n/**\n * Headless separator core — computes ARIA semantics and data attributes for\n * a separator rule. JSX-free so it can be shared across framework adapters.\n *\n * Per ARIA, a decorative separator must not be announced (`role=\"none\"`),\n * while a semantic one exposes `role=\"separator\"` and `aria-orientation`.\n */\nexport function createSeparator(props: SeparatorProps = {}): SeparatorAPI {\n const { orientation = 'horizontal', decorative = true } = props\n\n const ariaProps: SeparatorAriaProps = decorative\n ? { role: 'none' }\n : { role: 'separator', 'aria-orientation': orientation }\n\n const dataAttributes: Record<string, string> = {\n 'data-orientation': orientation,\n }\n\n return {\n ariaProps,\n dataAttributes,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\n/**\n * Visual variants for the separator rule.\n *\n * - `orientation: horizontal` → a full-width 1px line.\n * - `orientation: vertical` → a full-height 1px line.\n * - `labeled` → flex layout used by the labeled-divider variant, which\n * arranges a centered label between two flanking lines.\n */\nexport const separatorVariants = cva({\n base: '',\n variants: {\n orientation: {\n horizontal: 'h-px w-full',\n vertical: 'w-px h-full',\n },\n // Line color (issue #485). `default` is the standard border token;\n // `subtle` uses the hairline `border-subtle` token for a lighter rule.\n // Keeping the color here (not in `orientation`) keeps every existing call\n // pixel-identical while adding the subtle option.\n tone: {\n default: 'bg-border',\n subtle: 'bg-border-subtle',\n },\n labeled: {\n true: 'flex items-center',\n false: '',\n },\n },\n defaultVariants: {\n orientation: 'horizontal',\n tone: 'default',\n labeled: 'false',\n },\n})\n\n/** Line tone for the separator rule and labeled-divider flanks. */\nexport type SeparatorTone = 'default' | 'subtle'\n\n/** Class for each flanking line in the labeled-divider variant. */\nexport const separatorLineClass = 'h-px flex-1 bg-border'\n\n/** Hairline variant of {@link separatorLineClass} (issue #485). */\nexport const separatorSubtleLineClass = 'h-px flex-1 bg-border-subtle'\n\n/** Class for the centered label in the labeled-divider variant. */\nexport const separatorLabelClass =\n 'px-3 text-xs uppercase tracking-wide text-muted-foreground'\n", "import * as React from 'react'\nimport {\n createSeparator,\n separatorVariants,\n separatorLineClass,\n separatorSubtleLineClass,\n separatorLabelClass,\n type SeparatorOrientation,\n type SeparatorTone,\n} from '@refraction-ui/separator'\nimport { cn } from '@refraction-ui/shared'\n\nexport interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Orientation of the separator. Defaults to `'horizontal'`. */\n orientation?: SeparatorOrientation\n /**\n * Line tone. `'default'` uses the standard border token; `'subtle'` uses the\n * hairline `border-subtle` token for a lighter rule (issue #485).\n */\n tone?: SeparatorTone\n /**\n * Optional centered label. Only meaningful for horizontal separators —\n * renders a line on each side of the label (a \"labeled divider\").\n */\n label?: React.ReactNode\n /**\n * When `true` (the default) the separator is purely visual and is hidden\n * from assistive tech (`role=\"none\"`). When `false` it is exposed as a\n * semantic `role=\"separator\"` with the appropriate `aria-orientation`.\n */\n decorative?: boolean\n}\n\n/**\n * Separator / LabeledDivider — a thin rule that visually divides content.\n *\n * Supports horizontal and vertical orientations and an optional centered\n * label (the \"labeled divider\" variant, horizontal only). State and ARIA\n * semantics come from the headless @refraction-ui/separator core; styling\n * via Tailwind utility classes (no external CSS-in-JS).\n */\nexport const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(\n function Separator(\n {\n orientation = 'horizontal',\n tone = 'default',\n label,\n decorative = true,\n className,\n ...props\n },\n ref,\n ) {\n // Labeled divider — decorative chrome wrapping a centered label.\n if (orientation === 'horizontal' && label != null) {\n const lineClass = tone === 'subtle' ? separatorSubtleLineClass : separatorLineClass\n return (\n <div\n ref={ref}\n role=\"none\"\n className={cn(separatorVariants({ labeled: 'true', tone }), className)}\n {...props}\n >\n <span className={lineClass} />\n <span className={separatorLabelClass}>{label}</span>\n <span className={lineClass} />\n </div>\n )\n }\n\n const { ariaProps, dataAttributes } = createSeparator({\n orientation,\n decorative,\n })\n\n return (\n <div\n ref={ref}\n className={cn(separatorVariants({ orientation, tone }), className)}\n {...ariaProps}\n {...dataAttributes}\n {...props}\n />\n )\n },\n)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;AAqCO,SAAS,gBAAgB,QAAwB,CAAA,GAAkB;AACxE,QAAM,EAAE,cAAc,cAAc,aAAa,KAAA,IAAS;AAE1D,QAAM,YAAgC,aAClC,EAAE,MAAM,OAAA,IACR,EAAE,MAAM,aAAa,oBAAoB,YAAA;AAE7C,QAAM,iBAAyC;IAC7C,oBAAoB;EAAA;AAGtB,SAAO;IACL;IACA;EAAA;AAEJ;AC1CO,IAAM,oBAAoB,IAAI;EACnC,MAAM;EACN,UAAU;IACR,aAAa;MACX,YAAY;MACZ,UAAU;IAAA;;;;;IAMZ,MAAM;MACJ,SAAS;MACT,QAAQ;IAAA;IAEV,SAAS;MACP,MAAM;MACN,OAAO;IAAA;EACT;EAEF,iBAAiB;IACf,aAAa;IACb,MAAM;IACN,SAAS;EAAA;AAEb,CAAC;AAMM,IAAM,qBAAqB;AAG3B,IAAM,2BAA2B;AAGjC,IAAM,sBACX;;;;ACPK,IAAM,YAAkB;EAC7B,SAASA,WACP;IACE,cAAc;IACd,OAAO;IACP;IACA,aAAa;IACb;IACA,GAAG;EAAA,GAEL,KACA;AAEA,QAAI,gBAAgB,gBAAgB,SAAS,MAAM;AACjD,YAAM,YAAY,SAAS,WAAW,2BAA2B;AACjE,aACE;QAAC;QAAA;UACC;UACA,MAAK;UACL,WAAW,GAAG,kBAAkB,EAAE,SAAS,QAAQ,KAAA,CAAM,GAAG,SAAS;UACpE,GAAG;UAEJ,UAAA;YAAA,oBAAC,QAAA,EAAK,WAAW,UAAA,CAAW;YAC5B,oBAAC,QAAA,EAAK,WAAW,qBAAsB,UAAA,MAAA,CAAM;YAC7C,oBAAC,QAAA,EAAK,WAAW,UAAA,CAAW;UAAA;QAAA;MAAA;IAGlC;AAEA,UAAM,EAAE,WAAW,eAAA,IAAmB,gBAAgB;MACpD;MACA;IAAA,CACD;AAED,WACE;MAAC;MAAA;QACC;QACA,WAAW,GAAG,kBAAkB,EAAE,aAAa,KAAA,CAAM,GAAG,SAAS;QAChE,GAAG;QACH,GAAG;QACH,GAAG;MAAA;IAAA;EAGV;AACF;",
|
|
6
6
|
"names": ["Separator"]
|
|
7
7
|
}
|
|
@@ -133,6 +133,8 @@ var slideViewerProgressBarVariants = cva({
|
|
|
133
133
|
var slideTypeBadgeVariants = cva({
|
|
134
134
|
base: "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
|
|
135
135
|
variants: {
|
|
136
|
+
// Slide-type accents are a content-category palette (lesson/quiz/exercise…),
|
|
137
|
+
// not status tones — deliberately not routed through the theme tokens.
|
|
136
138
|
type: {
|
|
137
139
|
lesson: "bg-blue-100 text-blue-800",
|
|
138
140
|
quiz: "bg-purple-100 text-purple-800",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../slide-viewer/src/slide-viewer.ts", "../../slide-viewer/src/slide-viewer.styles.ts", "../../react-slide-viewer/src/SlideViewer.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { AccessibilityProps, KeyboardHandlerMap } from '@refraction-ui/shared'\nimport { Keys } from '@refraction-ui/shared'\n\nexport type SlideType = 'lesson' | 'quiz' | 'exercise' | 'intro' | 'summary'\n\nexport type BookmarkType = 'important' | 'difficult' | 'to-revise'\n\nexport interface SlideData {\n id: string\n type: SlideType\n content: string\n}\n\nexport interface SlideViewerProps {\n slides: SlideData[]\n initialSlide?: number\n onSlideChange?: (index: number) => void\n onComplete?: () => void\n}\n\nexport interface SlideViewerState {\n currentSlide: number\n totalSlides: number\n bookmarks: Map<number, BookmarkType>\n}\n\nexport interface SlideViewerAPI {\n /** Current viewer state */\n state: SlideViewerState\n /** Navigate to the next slide */\n next: () => void\n /** Navigate to the previous slide */\n prev: () => void\n /** Navigate to a specific slide index */\n goTo: (index: number) => void\n /** Toggle a bookmark on the current slide */\n toggleBookmark: (type: BookmarkType) => void\n /** Get all bookmarks as an array of entries */\n getBookmarks: () => Array<{ slide: number; type: BookmarkType }>\n /** Current progress as a number between 0 and 1 */\n progress: number\n /** Keyboard handlers for slide navigation */\n keyboardHandlers: KeyboardHandlerMap\n /** ARIA props for the current slide */\n getSlideAriaProps: () => Partial<AccessibilityProps> & { 'aria-roledescription'?: string }\n}\n\n/**\n * Create a headless slide viewer.\n *\n * Manages slide navigation state, bookmarks, progress tracking,\n * and keyboard navigation. Framework wrappers handle rendering.\n */\nexport function createSlideViewer(props: SlideViewerProps): SlideViewerAPI {\n const { slides, initialSlide = 0, onSlideChange, onComplete } = props\n\n const totalSlides = slides.length\n const bookmarks = new Map<number, BookmarkType>()\n\n const state: SlideViewerState = {\n currentSlide: Math.max(0, Math.min(initialSlide, totalSlides - 1)),\n totalSlides,\n bookmarks,\n }\n\n function updateProgress(): number {\n if (totalSlides <= 1) return 1\n return state.currentSlide / (totalSlides - 1)\n }\n\n function next() {\n if (state.currentSlide < totalSlides - 1) {\n state.currentSlide++\n api.progress = updateProgress()\n onSlideChange?.(state.currentSlide)\n } else {\n // Already on the last slide — trigger complete\n onComplete?.()\n }\n }\n\n function prev() {\n if (state.currentSlide > 0) {\n state.currentSlide--\n api.progress = updateProgress()\n onSlideChange?.(state.currentSlide)\n }\n }\n\n function goTo(index: number) {\n if (index >= 0 && index < totalSlides) {\n state.currentSlide = index\n api.progress = updateProgress()\n onSlideChange?.(state.currentSlide)\n }\n }\n\n function toggleBookmark(type: BookmarkType) {\n const current = state.currentSlide\n if (bookmarks.has(current) && bookmarks.get(current) === type) {\n bookmarks.delete(current)\n } else {\n bookmarks.set(current, type)\n }\n }\n\n function getBookmarks(): Array<{ slide: number; type: BookmarkType }> {\n const result: Array<{ slide: number; type: BookmarkType }> = []\n for (const [slide, type] of bookmarks.entries()) {\n result.push({ slide, type })\n }\n return result.sort((a, b) => a.slide - b.slide)\n }\n\n const keyboardHandlers: KeyboardHandlerMap = {\n [Keys.ArrowRight]: (e) => {\n e.preventDefault()\n next()\n },\n [Keys.ArrowLeft]: (e) => {\n e.preventDefault()\n prev()\n },\n [Keys.Escape]: (e) => {\n e.preventDefault()\n },\n }\n\n function getSlideAriaProps(): Partial<AccessibilityProps> & { 'aria-roledescription'?: string } {\n const slide = slides[state.currentSlide]\n return {\n role: 'region',\n 'aria-roledescription': 'slide',\n 'aria-label': `Slide ${state.currentSlide + 1} of ${totalSlides}: ${slide?.type ?? 'unknown'} slide`,\n 'aria-live': 'polite',\n }\n }\n\n const api: SlideViewerAPI = {\n state,\n next,\n prev,\n goTo,\n toggleBookmark,\n getBookmarks,\n progress: updateProgress(),\n keyboardHandlers,\n getSlideAriaProps,\n }\n\n return api\n}\n", "import { cva } from '@refraction-ui/shared'\nimport type { TokenContract } from '@refraction-ui/shared'\n\nexport const slideViewerTokens: TokenContract = {\n name: 'slide-viewer',\n tokens: {\n bg: { variable: '--rfr-slide-bg', fallback: 'hsl(var(--background))' },\n fg: { variable: '--rfr-slide-fg', fallback: 'hsl(var(--foreground))' },\n progressBg: { variable: '--rfr-slide-progress-bg', fallback: 'hsl(var(--primary))' },\n progressTrack: { variable: '--rfr-slide-progress-track', fallback: 'hsl(var(--muted))' },\n badgeBg: { variable: '--rfr-slide-badge-bg', fallback: 'hsl(var(--secondary))' },\n },\n}\n\nexport const slideViewerVariants = cva({\n base: 'flex flex-col h-full w-full relative overflow-hidden',\n variants: {\n size: {\n compact: 'max-w-2xl',\n default: 'max-w-4xl',\n full: 'max-w-none',\n },\n },\n defaultVariants: {\n size: 'default',\n },\n})\n\nconst slideViewerProgressBarVariants = cva({\n base: 'h-1 w-full bg-muted rounded-full overflow-hidden',\n variants: {\n position: {\n top: 'rounded-none',\n bottom: 'rounded-none',\n inline: 'rounded-full',\n },\n },\n defaultVariants: {\n position: 'top',\n },\n})\n\nexport { slideViewerProgressBarVariants as progressBarVariants }\n\nexport const slideTypeBadgeVariants = cva({\n base: 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',\n variants: {\n type: {\n lesson: 'bg-blue-100 text-blue-800',\n quiz: 'bg-purple-100 text-purple-800',\n exercise: 'bg-green-100 text-green-800',\n intro: 'bg-gray-100 text-gray-800',\n summary: 'bg-orange-100 text-orange-800',\n },\n },\n defaultVariants: {\n type: 'lesson',\n },\n})\n", "import * as React from 'react'\nimport {\n createSlideViewer,\n slideViewerVariants,\n progressBarVariants,\n slideTypeBadgeVariants,\n type SlideData,\n type BookmarkType,\n type SlideType,\n} from '@refraction-ui/slide-viewer'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { SlideData, BookmarkType, SlideType }\n\nexport interface SlideViewerProps {\n slides: SlideData[]\n initialSlide?: number\n onSlideChange?: (index: number) => void\n onComplete?: () => void\n className?: string\n size?: 'compact' | 'default' | 'full'\n /** Render function for custom slide content */\n renderSlide?: (slide: SlideData, index: number) => React.ReactNode\n}\n\n/**\n * SlideViewer component — slide-based content viewer with navigation,\n * progress bar, type badges, and bookmark support.\n *\n * Uses the headless @refraction-ui/slide-viewer core for state and navigation.\n */\nexport const SlideViewer = React.forwardRef<HTMLDivElement, SlideViewerProps>(\n function SlideViewer(\n {\n slides,\n initialSlide,\n onSlideChange,\n onComplete,\n className,\n size,\n renderSlide,\n },\n ref,\n ) {\n const [, setTick] = React.useState(0)\n const rerender = () => setTick((t) => t + 1)\n\n const apiRef = React.useRef(\n createSlideViewer({ slides, initialSlide, onSlideChange, onComplete }),\n )\n const api = apiRef.current\n\n const containerClasses = cn(slideViewerVariants({ size }), className)\n const currentSlide = slides[api.state.currentSlide]\n const slideAriaProps = api.getSlideAriaProps()\n // The core's keyboard handlers mutate state in place; re-render after a\n // handled key so the counter, progress bar, and buttons stay in sync.\n function handleKeyDown(event: KeyboardEvent) {\n const handler =\n api.keyboardHandlers[event.key as keyof typeof api.keyboardHandlers]\n if (!handler) return\n handler(event)\n rerender()\n }\n\n function handlePrev() {\n api.prev()\n rerender()\n }\n\n function handleNext() {\n api.next()\n rerender()\n }\n\n function handleBookmark(type: BookmarkType) {\n api.toggleBookmark(type)\n rerender()\n }\n\n const isBookmarked = api.state.bookmarks.has(api.state.currentSlide)\n\n return (\n <div\n ref={ref}\n className={containerClasses}\n onKeyDown={handleKeyDown as unknown as React.KeyboardEventHandler}\n tabIndex={0}\n >\n {/* Progress bar */}\n <div className={progressBarVariants({ position: 'top' })}>\n <div\n className=\"h-full bg-primary transition-all duration-300\"\n style={{ width: `${api.progress * 100}%` }}\n />\n </div>\n\n {/* Header with type badge and bookmark */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b\">\n <div className=\"flex items-center gap-2\">\n {currentSlide && (\n <span className={slideTypeBadgeVariants({ type: currentSlide.type })}>\n {currentSlide.type}\n </span>\n )}\n <span className=\"text-sm text-muted-foreground\">\n {api.state.currentSlide + 1} / {api.state.totalSlides}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleBookmark('important')}\n className={cn(\n 'px-2 py-1 rounded text-xs transition-colors',\n isBookmarked\n ? 'bg-yellow-100 text-yellow-800'\n : 'hover:bg-muted text-muted-foreground',\n )}\n aria-pressed={isBookmarked}\n aria-label=\"Toggle bookmark\"\n >\n {isBookmarked ? 'Bookmarked' : 'Bookmark'}\n </button>\n </div>\n\n {/* Slide content */}\n <div className=\"flex-1 overflow-auto p-6\" {...slideAriaProps}>\n {currentSlide &&\n (renderSlide\n ? renderSlide(currentSlide, api.state.currentSlide)\n : <div dangerouslySetInnerHTML={{ __html: currentSlide.content }} />)}\n </div>\n\n {/* Navigation buttons */}\n <div className=\"flex items-center justify-between px-4 py-3 border-t\">\n <button\n type=\"button\"\n onClick={handlePrev}\n disabled={api.state.currentSlide === 0}\n className=\"px-4 py-2 rounded text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors\"\n aria-label=\"Previous slide\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"px-4 py-2 rounded text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 transition-colors\"\n aria-label={\n api.state.currentSlide === api.state.totalSlides - 1\n ? 'Complete'\n : 'Next slide'\n }\n >\n {api.state.currentSlide === api.state.totalSlides - 1 ? 'Complete' : 'Next'}\n </button>\n </div>\n </div>\n )\n },\n)\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { AccessibilityProps, KeyboardHandlerMap } from '@refraction-ui/shared'\nimport { Keys } from '@refraction-ui/shared'\n\nexport type SlideType = 'lesson' | 'quiz' | 'exercise' | 'intro' | 'summary'\n\nexport type BookmarkType = 'important' | 'difficult' | 'to-revise'\n\nexport interface SlideData {\n id: string\n type: SlideType\n content: string\n}\n\nexport interface SlideViewerProps {\n slides: SlideData[]\n initialSlide?: number\n onSlideChange?: (index: number) => void\n onComplete?: () => void\n}\n\nexport interface SlideViewerState {\n currentSlide: number\n totalSlides: number\n bookmarks: Map<number, BookmarkType>\n}\n\nexport interface SlideViewerAPI {\n /** Current viewer state */\n state: SlideViewerState\n /** Navigate to the next slide */\n next: () => void\n /** Navigate to the previous slide */\n prev: () => void\n /** Navigate to a specific slide index */\n goTo: (index: number) => void\n /** Toggle a bookmark on the current slide */\n toggleBookmark: (type: BookmarkType) => void\n /** Get all bookmarks as an array of entries */\n getBookmarks: () => Array<{ slide: number; type: BookmarkType }>\n /** Current progress as a number between 0 and 1 */\n progress: number\n /** Keyboard handlers for slide navigation */\n keyboardHandlers: KeyboardHandlerMap\n /** ARIA props for the current slide */\n getSlideAriaProps: () => Partial<AccessibilityProps> & { 'aria-roledescription'?: string }\n}\n\n/**\n * Create a headless slide viewer.\n *\n * Manages slide navigation state, bookmarks, progress tracking,\n * and keyboard navigation. Framework wrappers handle rendering.\n */\nexport function createSlideViewer(props: SlideViewerProps): SlideViewerAPI {\n const { slides, initialSlide = 0, onSlideChange, onComplete } = props\n\n const totalSlides = slides.length\n const bookmarks = new Map<number, BookmarkType>()\n\n const state: SlideViewerState = {\n currentSlide: Math.max(0, Math.min(initialSlide, totalSlides - 1)),\n totalSlides,\n bookmarks,\n }\n\n function updateProgress(): number {\n if (totalSlides <= 1) return 1\n return state.currentSlide / (totalSlides - 1)\n }\n\n function next() {\n if (state.currentSlide < totalSlides - 1) {\n state.currentSlide++\n api.progress = updateProgress()\n onSlideChange?.(state.currentSlide)\n } else {\n // Already on the last slide — trigger complete\n onComplete?.()\n }\n }\n\n function prev() {\n if (state.currentSlide > 0) {\n state.currentSlide--\n api.progress = updateProgress()\n onSlideChange?.(state.currentSlide)\n }\n }\n\n function goTo(index: number) {\n if (index >= 0 && index < totalSlides) {\n state.currentSlide = index\n api.progress = updateProgress()\n onSlideChange?.(state.currentSlide)\n }\n }\n\n function toggleBookmark(type: BookmarkType) {\n const current = state.currentSlide\n if (bookmarks.has(current) && bookmarks.get(current) === type) {\n bookmarks.delete(current)\n } else {\n bookmarks.set(current, type)\n }\n }\n\n function getBookmarks(): Array<{ slide: number; type: BookmarkType }> {\n const result: Array<{ slide: number; type: BookmarkType }> = []\n for (const [slide, type] of bookmarks.entries()) {\n result.push({ slide, type })\n }\n return result.sort((a, b) => a.slide - b.slide)\n }\n\n const keyboardHandlers: KeyboardHandlerMap = {\n [Keys.ArrowRight]: (e) => {\n e.preventDefault()\n next()\n },\n [Keys.ArrowLeft]: (e) => {\n e.preventDefault()\n prev()\n },\n [Keys.Escape]: (e) => {\n e.preventDefault()\n },\n }\n\n function getSlideAriaProps(): Partial<AccessibilityProps> & { 'aria-roledescription'?: string } {\n const slide = slides[state.currentSlide]\n return {\n role: 'region',\n 'aria-roledescription': 'slide',\n 'aria-label': `Slide ${state.currentSlide + 1} of ${totalSlides}: ${slide?.type ?? 'unknown'} slide`,\n 'aria-live': 'polite',\n }\n }\n\n const api: SlideViewerAPI = {\n state,\n next,\n prev,\n goTo,\n toggleBookmark,\n getBookmarks,\n progress: updateProgress(),\n keyboardHandlers,\n getSlideAriaProps,\n }\n\n return api\n}\n", "import { cva } from '@refraction-ui/shared'\nimport type { TokenContract } from '@refraction-ui/shared'\n\nexport const slideViewerTokens: TokenContract = {\n name: 'slide-viewer',\n tokens: {\n bg: { variable: '--rfr-slide-bg', fallback: 'hsl(var(--background))' },\n fg: { variable: '--rfr-slide-fg', fallback: 'hsl(var(--foreground))' },\n progressBg: { variable: '--rfr-slide-progress-bg', fallback: 'hsl(var(--primary))' },\n progressTrack: { variable: '--rfr-slide-progress-track', fallback: 'hsl(var(--muted))' },\n badgeBg: { variable: '--rfr-slide-badge-bg', fallback: 'hsl(var(--secondary))' },\n },\n}\n\nexport const slideViewerVariants = cva({\n base: 'flex flex-col h-full w-full relative overflow-hidden',\n variants: {\n size: {\n compact: 'max-w-2xl',\n default: 'max-w-4xl',\n full: 'max-w-none',\n },\n },\n defaultVariants: {\n size: 'default',\n },\n})\n\nconst slideViewerProgressBarVariants = cva({\n base: 'h-1 w-full bg-muted rounded-full overflow-hidden',\n variants: {\n position: {\n top: 'rounded-none',\n bottom: 'rounded-none',\n inline: 'rounded-full',\n },\n },\n defaultVariants: {\n position: 'top',\n },\n})\n\nexport { slideViewerProgressBarVariants as progressBarVariants }\n\nexport const slideTypeBadgeVariants = cva({\n base: 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',\n variants: {\n // Slide-type accents are a content-category palette (lesson/quiz/exercise…),\n // not status tones — deliberately not routed through the theme tokens.\n type: {\n lesson: 'bg-blue-100 text-blue-800',\n quiz: 'bg-purple-100 text-purple-800',\n exercise: 'bg-green-100 text-green-800',\n intro: 'bg-gray-100 text-gray-800',\n summary: 'bg-orange-100 text-orange-800',\n },\n },\n defaultVariants: {\n type: 'lesson',\n },\n})\n", "import * as React from 'react'\nimport {\n createSlideViewer,\n slideViewerVariants,\n progressBarVariants,\n slideTypeBadgeVariants,\n type SlideData,\n type BookmarkType,\n type SlideType,\n} from '@refraction-ui/slide-viewer'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { SlideData, BookmarkType, SlideType }\n\nexport interface SlideViewerProps {\n slides: SlideData[]\n initialSlide?: number\n onSlideChange?: (index: number) => void\n onComplete?: () => void\n className?: string\n size?: 'compact' | 'default' | 'full'\n /** Render function for custom slide content */\n renderSlide?: (slide: SlideData, index: number) => React.ReactNode\n}\n\n/**\n * SlideViewer component — slide-based content viewer with navigation,\n * progress bar, type badges, and bookmark support.\n *\n * Uses the headless @refraction-ui/slide-viewer core for state and navigation.\n */\nexport const SlideViewer = React.forwardRef<HTMLDivElement, SlideViewerProps>(\n function SlideViewer(\n {\n slides,\n initialSlide,\n onSlideChange,\n onComplete,\n className,\n size,\n renderSlide,\n },\n ref,\n ) {\n const [, setTick] = React.useState(0)\n const rerender = () => setTick((t) => t + 1)\n\n const apiRef = React.useRef(\n createSlideViewer({ slides, initialSlide, onSlideChange, onComplete }),\n )\n const api = apiRef.current\n\n const containerClasses = cn(slideViewerVariants({ size }), className)\n const currentSlide = slides[api.state.currentSlide]\n const slideAriaProps = api.getSlideAriaProps()\n // The core's keyboard handlers mutate state in place; re-render after a\n // handled key so the counter, progress bar, and buttons stay in sync.\n function handleKeyDown(event: KeyboardEvent) {\n const handler =\n api.keyboardHandlers[event.key as keyof typeof api.keyboardHandlers]\n if (!handler) return\n handler(event)\n rerender()\n }\n\n function handlePrev() {\n api.prev()\n rerender()\n }\n\n function handleNext() {\n api.next()\n rerender()\n }\n\n function handleBookmark(type: BookmarkType) {\n api.toggleBookmark(type)\n rerender()\n }\n\n const isBookmarked = api.state.bookmarks.has(api.state.currentSlide)\n\n return (\n <div\n ref={ref}\n className={containerClasses}\n onKeyDown={handleKeyDown as unknown as React.KeyboardEventHandler}\n tabIndex={0}\n >\n {/* Progress bar */}\n <div className={progressBarVariants({ position: 'top' })}>\n <div\n className=\"h-full bg-primary transition-all duration-300\"\n style={{ width: `${api.progress * 100}%` }}\n />\n </div>\n\n {/* Header with type badge and bookmark */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b\">\n <div className=\"flex items-center gap-2\">\n {currentSlide && (\n <span className={slideTypeBadgeVariants({ type: currentSlide.type })}>\n {currentSlide.type}\n </span>\n )}\n <span className=\"text-sm text-muted-foreground\">\n {api.state.currentSlide + 1} / {api.state.totalSlides}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleBookmark('important')}\n className={cn(\n 'px-2 py-1 rounded text-xs transition-colors',\n isBookmarked\n ? 'bg-yellow-100 text-yellow-800'\n : 'hover:bg-muted text-muted-foreground',\n )}\n aria-pressed={isBookmarked}\n aria-label=\"Toggle bookmark\"\n >\n {isBookmarked ? 'Bookmarked' : 'Bookmark'}\n </button>\n </div>\n\n {/* Slide content */}\n <div className=\"flex-1 overflow-auto p-6\" {...slideAriaProps}>\n {currentSlide &&\n (renderSlide\n ? renderSlide(currentSlide, api.state.currentSlide)\n : <div dangerouslySetInnerHTML={{ __html: currentSlide.content }} />)}\n </div>\n\n {/* Navigation buttons */}\n <div className=\"flex items-center justify-between px-4 py-3 border-t\">\n <button\n type=\"button\"\n onClick={handlePrev}\n disabled={api.state.currentSlide === 0}\n className=\"px-4 py-2 rounded text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors\"\n aria-label=\"Previous slide\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"px-4 py-2 rounded text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 transition-colors\"\n aria-label={\n api.state.currentSlide === api.state.totalSlides - 1\n ? 'Complete'\n : 'Next slide'\n }\n >\n {api.state.currentSlide === api.state.totalSlides - 1 ? 'Complete' : 'Next'}\n </button>\n </div>\n </div>\n )\n },\n)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["SlideViewer"]
|
|
7
7
|
}
|
|
@@ -49,8 +49,8 @@ var statusDotVariants = cva({
|
|
|
49
49
|
error: "bg-destructive",
|
|
50
50
|
warning: "bg-warning",
|
|
51
51
|
info: "bg-info",
|
|
52
|
-
pending: "bg-
|
|
53
|
-
neutral: "bg-
|
|
52
|
+
pending: "bg-pending",
|
|
53
|
+
neutral: "bg-neutral"
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
defaultVariants: {
|
|
@@ -65,8 +65,8 @@ var statusPulseVariants = cva({
|
|
|
65
65
|
error: "bg-destructive",
|
|
66
66
|
warning: "bg-warning",
|
|
67
67
|
info: "bg-info",
|
|
68
|
-
pending: "bg-
|
|
69
|
-
neutral: "bg-
|
|
68
|
+
pending: "bg-pending",
|
|
69
|
+
neutral: "bg-neutral"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
defaultVariants: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../status-indicator/src/status-indicator.ts", "../../status-indicator/src/status-indicator.styles.ts", "../../react-status-indicator/src/status-indicator.tsx"],
|
|
4
|
-
"sourcesContent": ["export type StatusType = 'success' | 'error' | 'warning' | 'info' | 'pending' | 'neutral'\n\nexport interface StatusProps {\n /** The status type */\n type: StatusType\n /** Label to display alongside the status dot */\n label?: string\n /** Whether to show a pulse animation (primarily for 'pending' status) */\n pulse?: boolean\n}\n\nexport interface StatusAPI {\n /** ARIA attributes for the status indicator */\n ariaProps: Record<string, string | number | boolean>\n /** The status type */\n type: StatusType\n /** The display label */\n label: string\n /** Whether to show pulse animation */\n pulse: boolean\n /** CSS color class name for the dot */\n color: string\n}\n\nconst STATUS_INDICATOR_COLORS: Record<StatusType, string> = {\n success: 'green',\n error: 'red',\n warning: 'yellow',\n info: 'blue',\n pending: 'orange',\n neutral: 'gray',\n}\n\nconst STATUS_INDICATOR_LABELS: Record<StatusType, string> = {\n success: 'Success',\n error: 'Error',\n warning: 'Warning',\n info: 'Info',\n pending: 'Pending',\n neutral: 'Neutral',\n}\n\nexport {\n STATUS_INDICATOR_COLORS as STATUS_COLORS,\n STATUS_INDICATOR_LABELS as STATUS_LABELS,\n}\n\nexport function createStatusIndicator(props: StatusProps): StatusAPI {\n const { type, label: labelOverride, pulse: pulseOverride } = props\n\n const color = STATUS_INDICATOR_COLORS[type]\n const label = labelOverride ?? STATUS_INDICATOR_LABELS[type]\n const pulse = pulseOverride ?? (type === 'pending')\n\n const ariaProps: Record<string, string | number | boolean> = {\n role: 'status',\n 'aria-label': label,\n }\n\n return {\n ariaProps,\n type,\n label,\n pulse,\n color,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const statusContainerStyles =\n 'inline-flex items-center gap-1.5'\n\nexport const statusDotVariants = cva({\n base: 'inline-block h-2 w-2 rounded-full',\n variants: {\n type: {\n success: 'bg-success',\n error: 'bg-destructive',\n warning: 'bg-warning',\n info: 'bg-info',\n pending: 'bg-
|
|
4
|
+
"sourcesContent": ["export type StatusType = 'success' | 'error' | 'warning' | 'info' | 'pending' | 'neutral'\n\nexport interface StatusProps {\n /** The status type */\n type: StatusType\n /** Label to display alongside the status dot */\n label?: string\n /** Whether to show a pulse animation (primarily for 'pending' status) */\n pulse?: boolean\n}\n\nexport interface StatusAPI {\n /** ARIA attributes for the status indicator */\n ariaProps: Record<string, string | number | boolean>\n /** The status type */\n type: StatusType\n /** The display label */\n label: string\n /** Whether to show pulse animation */\n pulse: boolean\n /** CSS color class name for the dot */\n color: string\n}\n\nconst STATUS_INDICATOR_COLORS: Record<StatusType, string> = {\n success: 'green',\n error: 'red',\n warning: 'yellow',\n info: 'blue',\n pending: 'orange',\n neutral: 'gray',\n}\n\nconst STATUS_INDICATOR_LABELS: Record<StatusType, string> = {\n success: 'Success',\n error: 'Error',\n warning: 'Warning',\n info: 'Info',\n pending: 'Pending',\n neutral: 'Neutral',\n}\n\nexport {\n STATUS_INDICATOR_COLORS as STATUS_COLORS,\n STATUS_INDICATOR_LABELS as STATUS_LABELS,\n}\n\nexport function createStatusIndicator(props: StatusProps): StatusAPI {\n const { type, label: labelOverride, pulse: pulseOverride } = props\n\n const color = STATUS_INDICATOR_COLORS[type]\n const label = labelOverride ?? STATUS_INDICATOR_LABELS[type]\n const pulse = pulseOverride ?? (type === 'pending')\n\n const ariaProps: Record<string, string | number | boolean> = {\n role: 'status',\n 'aria-label': label,\n }\n\n return {\n ariaProps,\n type,\n label,\n pulse,\n color,\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const statusContainerStyles =\n 'inline-flex items-center gap-1.5'\n\nexport const statusDotVariants = cva({\n base: 'inline-block h-2 w-2 rounded-full',\n variants: {\n type: {\n success: 'bg-success',\n error: 'bg-destructive',\n warning: 'bg-warning',\n info: 'bg-info',\n pending: 'bg-pending',\n neutral: 'bg-neutral',\n },\n },\n defaultVariants: {\n type: 'neutral',\n },\n})\n\nexport const statusPulseVariants = cva({\n base: 'animate-pulse inline-block h-2 w-2 rounded-full',\n variants: {\n type: {\n success: 'bg-success',\n error: 'bg-destructive',\n warning: 'bg-warning',\n info: 'bg-info',\n pending: 'bg-pending',\n neutral: 'bg-neutral',\n },\n },\n defaultVariants: {\n type: 'pending',\n },\n})\n\nexport const statusLabelStyles =\n 'text-sm text-muted-foreground'\n", "import * as React from 'react'\nimport {\n createStatusIndicator,\n statusContainerStyles,\n statusDotVariants,\n statusPulseVariants,\n statusLabelStyles,\n type StatusType,\n} from '@refraction-ui/status-indicator'\nimport { cn } from '@refraction-ui/shared'\n\nexport interface StatusIndicatorProps {\n type: StatusType\n /** Explicit text label. Takes precedence over `children`. */\n label?: string\n /** Composable label content. Used when `label` is omitted. */\n children?: React.ReactNode\n pulse?: boolean\n showLabel?: boolean\n className?: string\n}\n\n/** Best ARIA-label: explicit prop wins, then string children, else headless default. */\nfunction deriveAriaLabel(\n label: string | undefined,\n children: React.ReactNode,\n fallback: string,\n): string {\n if (label) return label\n if (typeof children === 'string' || typeof children === 'number') {\n return String(children)\n }\n return fallback\n}\n\nexport const StatusIndicator = React.forwardRef<HTMLSpanElement, StatusIndicatorProps>(\n function StatusIndicator({ type, label, children, pulse, showLabel = true, className }, ref) {\n const api = createStatusIndicator({ type, label, pulse })\n const ariaLabel = deriveAriaLabel(label, children, api.label)\n const visibleLabel: React.ReactNode = label ?? children ?? api.label\n\n const dotClassName = api.pulse\n ? statusPulseVariants({ type })\n : statusDotVariants({ type })\n\n return (\n <span\n ref={ref}\n {...api.ariaProps}\n aria-label={ariaLabel}\n className={cn(statusContainerStyles, className)}\n >\n <span className={dotClassName} />\n {showLabel && <span className={statusLabelStyles}>{visibleLabel}</span>}\n </span>\n )\n },\n)\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;AAwBA,IAAM,0BAAsD;EAC1D,SAAS;EACT,OAAO;EACP,SAAS;EACT,MAAM;EACN,SAAS;EACT,SAAS;AACX;AAEA,IAAM,0BAAsD;EAC1D,SAAS;EACT,OAAO;EACP,SAAS;EACT,MAAM;EACN,SAAS;EACT,SAAS;AACX;AAOO,SAAS,sBAAsB,OAA+B;AACnE,QAAM,EAAE,MAAM,OAAO,eAAe,OAAO,cAAA,IAAkB;AAE7D,QAAM,QAAQ,wBAAwB,IAAI;AAC1C,QAAM,QAAQ,iBAAiB,wBAAwB,IAAI;AAC3D,QAAM,QAAQ,iBAAkB,SAAS;AAEzC,QAAM,YAAuD;IAC3D,MAAM;IACN,cAAc;EAAA;AAGhB,SAAO;IACL;IACA;IACA;IACA;IACA;EAAA;AAEJ;AChEO,IAAM,wBACX;AAEK,IAAM,oBAAoB,IAAI;EACnC,MAAM;EACN,UAAU;IACR,MAAM;MACJ,SAAS;MACT,OAAO;MACP,SAAS;MACT,MAAM;MACN,SAAS;MACT,SAAS;IAAA;EACX;EAEF,iBAAiB;IACf,MAAM;EAAA;AAEV,CAAC;AAEM,IAAM,sBAAsB,IAAI;EACrC,MAAM;EACN,UAAU;IACR,MAAM;MACJ,SAAS;MACT,OAAO;MACP,SAAS;MACT,MAAM;MACN,SAAS;MACT,SAAS;IAAA;EACX;EAEF,iBAAiB;IACf,MAAM;EAAA;AAEV,CAAC;AAEM,IAAM,oBACX;;;;ACjBF,SAAS,gBACP,OACA,UACA,UACQ;AACR,MAAI,MAAO,QAAO;AAClB,MAAI,OAAO,aAAa,YAAY,OAAO,aAAa,UAAU;AAChE,WAAO,OAAO,QAAQ;EACxB;AACA,SAAO;AACT;AAEO,IAAM,kBAAwB;EACnC,SAASA,iBAAgB,EAAE,MAAM,OAAO,UAAU,OAAO,YAAY,MAAM,UAAA,GAAa,KAAK;AAC7F,UAAM,MAAM,sBAAsB,EAAE,MAAM,OAAO,MAAA,CAAO;AACxD,UAAM,YAAY,gBAAgB,OAAO,UAAU,IAAI,KAAK;AAC5D,UAAM,eAAgC,SAAS,YAAY,IAAI;AAE/D,UAAM,eAAe,IAAI,QACrB,oBAAoB,EAAE,KAAA,CAAM,IAC5B,kBAAkB,EAAE,KAAA,CAAM;AAE9B,WACE;MAAC;MAAA;QACC;QACC,GAAG,IAAI;QACR,cAAY;QACZ,WAAW,GAAG,uBAAuB,SAAS;QAE9C,UAAA;UAAA,oBAAC,QAAA,EAAK,WAAW,aAAA,CAAc;UAC9B,aAAa,oBAAC,QAAA,EAAK,WAAW,mBAAoB,UAAA,aAAA,CAAa;QAAA;MAAA;IAAA;EAGpE;AACF;",
|
|
6
6
|
"names": ["StatusIndicator"]
|
|
7
7
|
}
|
|
@@ -33,6 +33,8 @@ var stickyNoteVariants = cva({
|
|
|
33
33
|
"min-w-[160px] min-h-[120px]"
|
|
34
34
|
].join(" "),
|
|
35
35
|
variants: {
|
|
36
|
+
// Named paper colors are product semantics (physical sticky notes),
|
|
37
|
+
// not status tones — deliberately not routed through the theme tokens.
|
|
36
38
|
color: {
|
|
37
39
|
yellow: "bg-yellow-100 text-yellow-900",
|
|
38
40
|
pink: "bg-pink-100 text-pink-900",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../sticky-note/src/sticky-note.ts", "../../sticky-note/src/sticky-note.styles.ts", "../../react-sticky-note/src/sticky-note.tsx"],
|
|
4
|
-
"sourcesContent": ["/** The available color variants for a sticky note. */\nexport type StickyNoteColor = 'yellow' | 'pink' | 'blue' | 'green' | 'purple' | 'orange'\n\n/** Ordered list of all sticky note colors — used for cycling and iteration. */\nexport const STICKY_NOTE_COLORS: StickyNoteColor[] = [\n 'yellow',\n 'pink',\n 'blue',\n 'green',\n 'purple',\n 'orange',\n]\n\n/**\n * Returns the next color in the cycle. Wraps from the last color back to the\n * first. Useful for \"add note\" flows that auto-assign colors in sequence.\n */\nexport function nextStickyColor(color: StickyNoteColor): StickyNoteColor {\n const index = STICKY_NOTE_COLORS.indexOf(color)\n const next = (index + 1) % STICKY_NOTE_COLORS.length\n return STICKY_NOTE_COLORS[next]\n}\n\nexport interface CreateStickyNoteOptions {\n /** Color of the note. */\n color: StickyNoteColor\n}\n\nexport interface StickyNoteAPI {\n /** Data attributes for styling hooks; spread onto the note container. */\n dataAttributes: { 'data-color': StickyNoteColor }\n /** ARIA props for the note container. */\n ariaProps: { role: 'group' }\n}\n\n/**\n * Build the framework-agnostic props for a sticky note container.\n *\n * Returns `role=\"group\"` plus the `data-color` attribute that adapters use\n * as a styling hook. Logic is pure and SSR-safe — no Math.random, no Date.\n */\nexport function createStickyNote(options: CreateStickyNoteOptions): StickyNoteAPI {\n return {\n dataAttributes: { 'data-color': options.color },\n ariaProps: { role: 'group' },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\n/**\n * Note container with color variant.\n *\n * Sticky notes are intrinsically colorful — an approved exception to the\n * semantic-token-only rule. Each color maps to a soft tint background with a\n * readable dark text.\n */\nexport const stickyNoteVariants = cva({\n base: [\n 'relative inline-flex flex-col gap-2 rounded-md p-4',\n 'shadow-md',\n 'min-w-[160px] min-h-[120px]',\n ].join(' '),\n variants: {\n color: {\n yellow: 'bg-yellow-100 text-yellow-900',\n pink: 'bg-pink-100 text-pink-900',\n blue: 'bg-blue-100 text-blue-900',\n green: 'bg-green-100 text-green-900',\n purple: 'bg-purple-100 text-purple-900',\n orange: 'bg-orange-100 text-orange-900',\n },\n },\n defaultVariants: {\n color: 'yellow',\n },\n})\n\n/** Class for the editable/static text area inside the note. */\nexport const stickyNoteTextClass =\n 'flex-1 w-full resize-none bg-transparent text-sm leading-relaxed placeholder:opacity-60 outline-none'\n\n/** Class for the author chip rendered in the note footer. */\nexport const stickyNoteAuthorClass =\n 'mt-auto text-xs font-medium opacity-70 truncate'\n", "import * as React from 'react'\nimport {\n createStickyNote,\n stickyNoteVariants,\n stickyNoteTextClass,\n stickyNoteAuthorClass,\n type StickyNoteColor,\n} from '@refraction-ui/sticky-note'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { StickyNoteColor }\n\nexport interface StickyNoteProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'content' | 'onSelect'> {\n /** Color of the sticky note. */\n color?: StickyNoteColor\n /** Text content of the note. */\n text?: string\n /** Called when the text changes (makes text editable via a textarea). */\n onTextChange?: (value: string) => void\n /** Optional author name shown in the note footer. */\n author?: string\n /** Absolute x position in pixels (for board/canvas placement). */\n x?: number\n /** Absolute y position in pixels (for board/canvas placement). */\n y?: number\n /** Whether the note can be dragged. Requires `onMove` to receive updates. */\n draggable?: boolean\n /** Called with the new `{ x, y }` position after a drag completes. */\n onMove?: (position: { x: number; y: number }) => void\n}\n\n/**\n * StickyNote — a draggable colored note for whiteboard / canvas surfaces.\n *\n * - `color` selects the soft-tint palette (yellow, pink, blue, green, purple, orange).\n * - `text` + `onTextChange` switch to an editable textarea; without `onTextChange` the\n * text is static.\n * - `x` / `y` absolutely position the note; pair with `draggable` + `onMove` for\n * drag-to-reposition. Drag is implemented via pointer events and is SSR-safe (no\n * handlers attach until the component mounts on the client).\n * - Forwards the ref to the outer `<div>`.\n */\nexport const StickyNote = React.forwardRef<HTMLDivElement, StickyNoteProps>(\n function StickyNote(\n {\n color = 'yellow',\n text,\n onTextChange,\n author,\n x,\n y,\n draggable = false,\n onMove,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) {\n const { ariaProps, dataAttributes } = createStickyNote({ color })\n\n // Drag state — kept in refs to avoid re-renders during drag.\n const dragOrigin = React.useRef<{ pointerX: number; pointerY: number; noteX: number; noteY: number } | null>(null)\n\n const positionStyle: React.CSSProperties =\n x !== undefined && y !== undefined\n ? { position: 'absolute', left: x, top: y }\n : {}\n\n const handlePointerDown = React.useCallback(\n (e: React.PointerEvent<HTMLDivElement>) => {\n if (!draggable || !onMove) return\n // Only primary button.\n if (e.button !== 0) return\n e.currentTarget.setPointerCapture(e.pointerId)\n dragOrigin.current = {\n pointerX: e.clientX,\n pointerY: e.clientY,\n noteX: x ?? 0,\n noteY: y ?? 0,\n }\n },\n [draggable, onMove, x, y],\n )\n\n const handlePointerMove = React.useCallback(\n (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragOrigin.current || !onMove) return\n const dx = e.clientX - dragOrigin.current.pointerX\n const dy = e.clientY - dragOrigin.current.pointerY\n onMove({ x: dragOrigin.current.noteX + dx, y: dragOrigin.current.noteY + dy })\n },\n [onMove],\n )\n\n const handlePointerUp = React.useCallback(() => {\n dragOrigin.current = null\n }, [])\n\n return (\n <div\n ref={ref}\n className={cn(stickyNoteVariants({ color }), className)}\n style={{ ...positionStyle, ...style }}\n {...ariaProps}\n {...dataAttributes}\n onPointerDown={draggable && onMove ? handlePointerDown : undefined}\n onPointerMove={draggable && onMove ? handlePointerMove : undefined}\n onPointerUp={draggable && onMove ? handlePointerUp : undefined}\n {...props}\n >\n {onTextChange != null ? (\n <textarea\n className={stickyNoteTextClass}\n value={text ?? ''}\n onChange={(e) => onTextChange(e.target.value)}\n placeholder=\"Type a note…\"\n />\n ) : (\n <p className={`${stickyNoteTextClass} whitespace-pre-wrap`}>{text ?? children}</p>\n )}\n {author != null && (\n <span className={stickyNoteAuthorClass}>{author}</span>\n )}\n </div>\n )\n },\n)\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;AAIO,IAAM,qBAAwC;EACnD;EACA;EACA;EACA;EACA;EACA;AACF;AAMO,SAAS,gBAAgB,OAAyC;AACvE,QAAM,QAAQ,mBAAmB,QAAQ,KAAK;AAC9C,QAAM,QAAQ,QAAQ,KAAK,mBAAmB;AAC9C,SAAO,mBAAmB,IAAI;AAChC;AAoBO,SAAS,iBAAiB,SAAiD;AAChF,SAAO;IACL,gBAAgB,EAAE,cAAc,QAAQ,MAAA;IACxC,WAAW,EAAE,MAAM,QAAA;EAAQ;AAE/B;ACrCO,IAAM,qBAAqB,IAAI;EACpC,MAAM;IACJ;IACA;IACA;EAAA,EACA,KAAK,GAAG;EACV,UAAU
|
|
4
|
+
"sourcesContent": ["/** The available color variants for a sticky note. */\nexport type StickyNoteColor = 'yellow' | 'pink' | 'blue' | 'green' | 'purple' | 'orange'\n\n/** Ordered list of all sticky note colors — used for cycling and iteration. */\nexport const STICKY_NOTE_COLORS: StickyNoteColor[] = [\n 'yellow',\n 'pink',\n 'blue',\n 'green',\n 'purple',\n 'orange',\n]\n\n/**\n * Returns the next color in the cycle. Wraps from the last color back to the\n * first. Useful for \"add note\" flows that auto-assign colors in sequence.\n */\nexport function nextStickyColor(color: StickyNoteColor): StickyNoteColor {\n const index = STICKY_NOTE_COLORS.indexOf(color)\n const next = (index + 1) % STICKY_NOTE_COLORS.length\n return STICKY_NOTE_COLORS[next]\n}\n\nexport interface CreateStickyNoteOptions {\n /** Color of the note. */\n color: StickyNoteColor\n}\n\nexport interface StickyNoteAPI {\n /** Data attributes for styling hooks; spread onto the note container. */\n dataAttributes: { 'data-color': StickyNoteColor }\n /** ARIA props for the note container. */\n ariaProps: { role: 'group' }\n}\n\n/**\n * Build the framework-agnostic props for a sticky note container.\n *\n * Returns `role=\"group\"` plus the `data-color` attribute that adapters use\n * as a styling hook. Logic is pure and SSR-safe — no Math.random, no Date.\n */\nexport function createStickyNote(options: CreateStickyNoteOptions): StickyNoteAPI {\n return {\n dataAttributes: { 'data-color': options.color },\n ariaProps: { role: 'group' },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\n/**\n * Note container with color variant.\n *\n * Sticky notes are intrinsically colorful — an approved exception to the\n * semantic-token-only rule. Each color maps to a soft tint background with a\n * readable dark text.\n */\nexport const stickyNoteVariants = cva({\n base: [\n 'relative inline-flex flex-col gap-2 rounded-md p-4',\n 'shadow-md',\n 'min-w-[160px] min-h-[120px]',\n ].join(' '),\n variants: {\n // Named paper colors are product semantics (physical sticky notes),\n // not status tones — deliberately not routed through the theme tokens.\n color: {\n yellow: 'bg-yellow-100 text-yellow-900',\n pink: 'bg-pink-100 text-pink-900',\n blue: 'bg-blue-100 text-blue-900',\n green: 'bg-green-100 text-green-900',\n purple: 'bg-purple-100 text-purple-900',\n orange: 'bg-orange-100 text-orange-900',\n },\n },\n defaultVariants: {\n color: 'yellow',\n },\n})\n\n/** Class for the editable/static text area inside the note. */\nexport const stickyNoteTextClass =\n 'flex-1 w-full resize-none bg-transparent text-sm leading-relaxed placeholder:opacity-60 outline-none'\n\n/** Class for the author chip rendered in the note footer. */\nexport const stickyNoteAuthorClass =\n 'mt-auto text-xs font-medium opacity-70 truncate'\n", "import * as React from 'react'\nimport {\n createStickyNote,\n stickyNoteVariants,\n stickyNoteTextClass,\n stickyNoteAuthorClass,\n type StickyNoteColor,\n} from '@refraction-ui/sticky-note'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { StickyNoteColor }\n\nexport interface StickyNoteProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'content' | 'onSelect'> {\n /** Color of the sticky note. */\n color?: StickyNoteColor\n /** Text content of the note. */\n text?: string\n /** Called when the text changes (makes text editable via a textarea). */\n onTextChange?: (value: string) => void\n /** Optional author name shown in the note footer. */\n author?: string\n /** Absolute x position in pixels (for board/canvas placement). */\n x?: number\n /** Absolute y position in pixels (for board/canvas placement). */\n y?: number\n /** Whether the note can be dragged. Requires `onMove` to receive updates. */\n draggable?: boolean\n /** Called with the new `{ x, y }` position after a drag completes. */\n onMove?: (position: { x: number; y: number }) => void\n}\n\n/**\n * StickyNote — a draggable colored note for whiteboard / canvas surfaces.\n *\n * - `color` selects the soft-tint palette (yellow, pink, blue, green, purple, orange).\n * - `text` + `onTextChange` switch to an editable textarea; without `onTextChange` the\n * text is static.\n * - `x` / `y` absolutely position the note; pair with `draggable` + `onMove` for\n * drag-to-reposition. Drag is implemented via pointer events and is SSR-safe (no\n * handlers attach until the component mounts on the client).\n * - Forwards the ref to the outer `<div>`.\n */\nexport const StickyNote = React.forwardRef<HTMLDivElement, StickyNoteProps>(\n function StickyNote(\n {\n color = 'yellow',\n text,\n onTextChange,\n author,\n x,\n y,\n draggable = false,\n onMove,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) {\n const { ariaProps, dataAttributes } = createStickyNote({ color })\n\n // Drag state — kept in refs to avoid re-renders during drag.\n const dragOrigin = React.useRef<{ pointerX: number; pointerY: number; noteX: number; noteY: number } | null>(null)\n\n const positionStyle: React.CSSProperties =\n x !== undefined && y !== undefined\n ? { position: 'absolute', left: x, top: y }\n : {}\n\n const handlePointerDown = React.useCallback(\n (e: React.PointerEvent<HTMLDivElement>) => {\n if (!draggable || !onMove) return\n // Only primary button.\n if (e.button !== 0) return\n e.currentTarget.setPointerCapture(e.pointerId)\n dragOrigin.current = {\n pointerX: e.clientX,\n pointerY: e.clientY,\n noteX: x ?? 0,\n noteY: y ?? 0,\n }\n },\n [draggable, onMove, x, y],\n )\n\n const handlePointerMove = React.useCallback(\n (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragOrigin.current || !onMove) return\n const dx = e.clientX - dragOrigin.current.pointerX\n const dy = e.clientY - dragOrigin.current.pointerY\n onMove({ x: dragOrigin.current.noteX + dx, y: dragOrigin.current.noteY + dy })\n },\n [onMove],\n )\n\n const handlePointerUp = React.useCallback(() => {\n dragOrigin.current = null\n }, [])\n\n return (\n <div\n ref={ref}\n className={cn(stickyNoteVariants({ color }), className)}\n style={{ ...positionStyle, ...style }}\n {...ariaProps}\n {...dataAttributes}\n onPointerDown={draggable && onMove ? handlePointerDown : undefined}\n onPointerMove={draggable && onMove ? handlePointerMove : undefined}\n onPointerUp={draggable && onMove ? handlePointerUp : undefined}\n {...props}\n >\n {onTextChange != null ? (\n <textarea\n className={stickyNoteTextClass}\n value={text ?? ''}\n onChange={(e) => onTextChange(e.target.value)}\n placeholder=\"Type a note…\"\n />\n ) : (\n <p className={`${stickyNoteTextClass} whitespace-pre-wrap`}>{text ?? children}</p>\n )}\n {author != null && (\n <span className={stickyNoteAuthorClass}>{author}</span>\n )}\n </div>\n )\n },\n)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;AAIO,IAAM,qBAAwC;EACnD;EACA;EACA;EACA;EACA;EACA;AACF;AAMO,SAAS,gBAAgB,OAAyC;AACvE,QAAM,QAAQ,mBAAmB,QAAQ,KAAK;AAC9C,QAAM,QAAQ,QAAQ,KAAK,mBAAmB;AAC9C,SAAO,mBAAmB,IAAI;AAChC;AAoBO,SAAS,iBAAiB,SAAiD;AAChF,SAAO;IACL,gBAAgB,EAAE,cAAc,QAAQ,MAAA;IACxC,WAAW,EAAE,MAAM,QAAA;EAAQ;AAE/B;ACrCO,IAAM,qBAAqB,IAAI;EACpC,MAAM;IACJ;IACA;IACA;EAAA,EACA,KAAK,GAAG;EACV,UAAU;;;IAGR,OAAO;MACL,QAAQ;MACR,MAAM;MACN,MAAM;MACN,OAAO;MACP,QAAQ;MACR,QAAQ;IAAA;EACV;EAEF,iBAAiB;IACf,OAAO;EAAA;AAEX,CAAC;AAGM,IAAM,sBACX;AAGK,IAAM,wBACX;;;;ACKK,IAAM,aAAmB;EAC9B,SAASA,YACP;IACE,QAAQ;IACR;IACA;IACA;IACA;IACA;IACA,YAAY;IACZ;IACA;IACA;IACA;IACA,GAAG;EAAA,GAEL,KACA;AACA,UAAM,EAAE,WAAW,eAAA,IAAmB,iBAAiB,EAAE,MAAA,CAAO;AAGhE,UAAM,aAAmB,aAAoF,IAAI;AAEjH,UAAM,gBACJ,MAAM,UAAa,MAAM,SACrB,EAAE,UAAU,YAAY,MAAM,GAAG,KAAK,EAAA,IACtC,CAAA;AAEN,UAAM,oBAA0B;MAC9B,CAAC,MAA0C;AACzC,YAAI,CAAC,aAAa,CAAC,OAAQ;AAE3B,YAAI,EAAE,WAAW,EAAG;AACpB,UAAE,cAAc,kBAAkB,EAAE,SAAS;AAC7C,mBAAW,UAAU;UACnB,UAAU,EAAE;UACZ,UAAU,EAAE;UACZ,OAAO,KAAK;UACZ,OAAO,KAAK;QAAA;MAEhB;MACA,CAAC,WAAW,QAAQ,GAAG,CAAC;IAAA;AAG1B,UAAM,oBAA0B;MAC9B,CAAC,MAA0C;AACzC,YAAI,CAAC,WAAW,WAAW,CAAC,OAAQ;AACpC,cAAM,KAAK,EAAE,UAAU,WAAW,QAAQ;AAC1C,cAAM,KAAK,EAAE,UAAU,WAAW,QAAQ;AAC1C,eAAO,EAAE,GAAG,WAAW,QAAQ,QAAQ,IAAI,GAAG,WAAW,QAAQ,QAAQ,GAAA,CAAI;MAC/E;MACA,CAAC,MAAM;IAAA;AAGT,UAAM,kBAAwB,kBAAY,MAAM;AAC9C,iBAAW,UAAU;IACvB,GAAG,CAAA,CAAE;AAEL,WACE;MAAC;MAAA;QACC;QACA,WAAW,GAAG,mBAAmB,EAAE,MAAA,CAAO,GAAG,SAAS;QACtD,OAAO,EAAE,GAAG,eAAe,GAAG,MAAA;QAC7B,GAAG;QACH,GAAG;QACJ,eAAe,aAAa,SAAS,oBAAoB;QACzD,eAAe,aAAa,SAAS,oBAAoB;QACzD,aAAa,aAAa,SAAS,kBAAkB;QACpD,GAAG;QAEH,UAAA;UAAA,gBAAgB,OACf;YAAC;YAAA;cACC,WAAW;cACX,OAAO,QAAQ;cACf,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;cAC5C,aAAY;YAAA;UAAA,IACd,oBAEC,KAAA,EAAE,WAAW,GAAG,mBAAmB,wBAAyB,UAAA,QAAQ,SAAA,CAAS;UAE/E,UAAU,QACT,oBAAC,QAAA,EAAK,WAAW,uBAAwB,UAAA,OAAA,CAAO;QAAA;MAAA;IAAA;EAIxD;AACF;",
|
|
6
6
|
"names": ["StickyNote"]
|
|
7
7
|
}
|