@marlinjai/clearify 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/bin/clearify.js +2 -0
  4. package/dist/node/chunk-5TD7NQIW.js +25 -0
  5. package/dist/node/chunk-B2Q23JW3.js +55 -0
  6. package/dist/node/chunk-CQ4MNGBE.js +301 -0
  7. package/dist/node/chunk-GFD54GNO.js +223 -0
  8. package/dist/node/chunk-IBK35HZR.js +194 -0
  9. package/dist/node/chunk-L24ILRSX.js +125 -0
  10. package/dist/node/chunk-NXQNNLGC.js +395 -0
  11. package/dist/node/chunk-PRTER35L.js +48 -0
  12. package/dist/node/chunk-SCZZB7OE.js +9 -0
  13. package/dist/node/chunk-V7LLYIRO.js +8 -0
  14. package/dist/node/chunk-WT5W333R.js +136 -0
  15. package/dist/node/cli/index.d.ts +2 -0
  16. package/dist/node/cli/index.js +41 -0
  17. package/dist/node/core/config.d.ts +9 -0
  18. package/dist/node/core/config.js +16 -0
  19. package/dist/node/core/mermaid-renderer.d.ts +22 -0
  20. package/dist/node/core/mermaid-renderer.js +125 -0
  21. package/dist/node/core/mermaid-utils.d.ts +3 -0
  22. package/dist/node/core/mermaid-utils.js +6 -0
  23. package/dist/node/core/navigation.d.ts +2 -0
  24. package/dist/node/core/navigation.js +13 -0
  25. package/dist/node/core/openapi-parser.d.ts +14 -0
  26. package/dist/node/core/openapi-parser.js +6 -0
  27. package/dist/node/core/remark-mermaid.d.ts +10 -0
  28. package/dist/node/core/remark-mermaid.js +11 -0
  29. package/dist/node/core/search.d.ts +31 -0
  30. package/dist/node/core/search.js +6 -0
  31. package/dist/node/node/build.d.ts +3 -0
  32. package/dist/node/node/build.js +14 -0
  33. package/dist/node/node/check.d.ts +3 -0
  34. package/dist/node/node/check.js +10 -0
  35. package/dist/node/node/index.d.ts +11 -0
  36. package/dist/node/node/index.js +108 -0
  37. package/dist/node/node/init.d.ts +6 -0
  38. package/dist/node/node/init.js +6 -0
  39. package/dist/node/presets/nestjs.d.ts +15 -0
  40. package/dist/node/presets/nestjs.js +98 -0
  41. package/dist/node/types/index.d.ts +79 -0
  42. package/dist/node/types/index.js +6 -0
  43. package/dist/node/vite-plugin/index.d.ts +13 -0
  44. package/dist/node/vite-plugin/index.js +11 -0
  45. package/package.json +94 -0
  46. package/src/client/App.tsx +101 -0
  47. package/src/client/Page.tsx +15 -0
  48. package/src/client/entry-server.tsx +79 -0
  49. package/src/client/index.html +18 -0
  50. package/src/client/main.tsx +11 -0
  51. package/src/theme/CodeBlock.tsx +103 -0
  52. package/src/theme/Content.tsx +32 -0
  53. package/src/theme/Footer.tsx +53 -0
  54. package/src/theme/Head.tsx +80 -0
  55. package/src/theme/HeadContext.tsx +32 -0
  56. package/src/theme/Header.tsx +177 -0
  57. package/src/theme/Layout.tsx +44 -0
  58. package/src/theme/MDXComponents.tsx +40 -0
  59. package/src/theme/NotFound.tsx +246 -0
  60. package/src/theme/Search.tsx +359 -0
  61. package/src/theme/Sidebar.tsx +325 -0
  62. package/src/theme/TableOfContents.tsx +153 -0
  63. package/src/theme/ThemeProvider.tsx +77 -0
  64. package/src/theme/components/Accordion.tsx +109 -0
  65. package/src/theme/components/Badge.tsx +72 -0
  66. package/src/theme/components/Breadcrumbs.tsx +88 -0
  67. package/src/theme/components/Callout.tsx +115 -0
  68. package/src/theme/components/Card.tsx +103 -0
  69. package/src/theme/components/CodeGroup.tsx +79 -0
  70. package/src/theme/components/Columns.tsx +42 -0
  71. package/src/theme/components/Frame.tsx +55 -0
  72. package/src/theme/components/Mermaid.tsx +99 -0
  73. package/src/theme/components/MermaidStatic.tsx +32 -0
  74. package/src/theme/components/OpenAPI.tsx +160 -0
  75. package/src/theme/components/OpenAPIPage.tsx +16 -0
  76. package/src/theme/components/Steps.tsx +76 -0
  77. package/src/theme/components/Tabs.tsx +75 -0
  78. package/src/theme/components/Tooltip.tsx +108 -0
  79. package/src/theme/components/index.ts +14 -0
  80. package/src/theme/styles/globals.css +363 -0
@@ -0,0 +1,153 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ interface Heading {
4
+ id: string;
5
+ text: string;
6
+ level: number;
7
+ }
8
+
9
+ export function TableOfContents() {
10
+ const [headings, setHeadings] = useState<Heading[]>([]);
11
+ const [activeId, setActiveId] = useState('');
12
+
13
+ useEffect(() => {
14
+ const elements = document.querySelectorAll('.clearify-prose h2, .clearify-prose h3');
15
+ const items: Heading[] = [];
16
+
17
+ elements.forEach((el) => {
18
+ if (!el.id) {
19
+ el.id = el.textContent?.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') ?? '';
20
+ }
21
+ items.push({
22
+ id: el.id,
23
+ text: el.textContent ?? '',
24
+ level: el.tagName === 'H2' ? 2 : 3,
25
+ });
26
+ });
27
+
28
+ setHeadings(items);
29
+ }, []);
30
+
31
+ useEffect(() => {
32
+ if (headings.length === 0) return;
33
+
34
+ const observer = new IntersectionObserver(
35
+ (entries) => {
36
+ for (const entry of entries) {
37
+ if (entry.isIntersecting) {
38
+ setActiveId(entry.target.id);
39
+ break;
40
+ }
41
+ }
42
+ },
43
+ { rootMargin: '-80px 0px -80% 0px' }
44
+ );
45
+
46
+ headings.forEach(({ id }) => {
47
+ const el = document.getElementById(id);
48
+ if (el) observer.observe(el);
49
+ });
50
+
51
+ return () => observer.disconnect();
52
+ }, [headings]);
53
+
54
+ if (headings.length === 0) return null;
55
+
56
+ return (
57
+ <nav
58
+ style={{
59
+ width: 'var(--clearify-toc-width)',
60
+ flexShrink: 0,
61
+ padding: '2.5rem 1rem 2.5rem 0',
62
+ position: 'sticky',
63
+ top: 'var(--clearify-header-height)',
64
+ height: 'calc(100vh - var(--clearify-header-height))',
65
+ overflowY: 'auto',
66
+ fontSize: '0.8125rem',
67
+ }}
68
+ className="clearify-toc"
69
+ >
70
+ <div
71
+ style={{
72
+ fontWeight: 600,
73
+ fontSize: '0.6875rem',
74
+ textTransform: 'uppercase',
75
+ letterSpacing: '0.08em',
76
+ color: 'var(--clearify-text-tertiary)',
77
+ marginBottom: '0.875rem',
78
+ paddingLeft: '0.75rem',
79
+ }}
80
+ >
81
+ On this page
82
+ </div>
83
+ <ul style={{ listStyle: 'none', padding: 0, margin: 0, position: 'relative' }}>
84
+ {/* Vertical track line */}
85
+ <div
86
+ style={{
87
+ position: 'absolute',
88
+ left: 0,
89
+ top: 0,
90
+ bottom: 0,
91
+ width: 1,
92
+ backgroundColor: 'var(--clearify-border)',
93
+ borderRadius: 1,
94
+ }}
95
+ />
96
+ {headings.map((heading) => {
97
+ const isActive = activeId === heading.id;
98
+ return (
99
+ <li key={heading.id} style={{ position: 'relative' }}>
100
+ {isActive && (
101
+ <span
102
+ style={{
103
+ position: 'absolute',
104
+ left: 0,
105
+ top: 4,
106
+ bottom: 4,
107
+ width: 2,
108
+ borderRadius: 1,
109
+ background: 'var(--clearify-gradient)',
110
+ transition: 'opacity 0.15s',
111
+ }}
112
+ />
113
+ )}
114
+ <a
115
+ href={`#${heading.id}`}
116
+ onClick={(e) => {
117
+ e.preventDefault();
118
+ document.getElementById(heading.id)?.scrollIntoView({ behavior: 'smooth' });
119
+ setActiveId(heading.id);
120
+ }}
121
+ style={{
122
+ display: 'block',
123
+ padding: '0.25rem 0.75rem',
124
+ paddingLeft: heading.level === 3 ? '1.25rem' : '0.75rem',
125
+ color: isActive ? 'var(--clearify-primary)' : 'var(--clearify-text-tertiary)',
126
+ fontWeight: isActive ? 500 : 400,
127
+ textDecoration: 'none',
128
+ transition: 'color 0.15s',
129
+ fontSize: '0.8125rem',
130
+ lineHeight: 1.5,
131
+ }}
132
+ className="clearify-toc-link"
133
+ >
134
+ {heading.text}
135
+ </a>
136
+ </li>
137
+ );
138
+ })}
139
+ </ul>
140
+
141
+ <style>{`
142
+ .clearify-toc-link:hover {
143
+ color: var(--clearify-text) !important;
144
+ }
145
+ @media (max-width: 1024px) {
146
+ .clearify-toc {
147
+ display: none;
148
+ }
149
+ }
150
+ `}</style>
151
+ </nav>
152
+ );
153
+ }
@@ -0,0 +1,77 @@
1
+ import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
2
+
3
+ type Theme = 'light' | 'dark';
4
+
5
+ interface ThemeContextValue {
6
+ theme: Theme;
7
+ toggleTheme: () => void;
8
+ }
9
+
10
+ const ThemeContext = createContext<ThemeContextValue>({
11
+ theme: 'light',
12
+ toggleTheme: () => {},
13
+ });
14
+
15
+ export function useTheme() {
16
+ return useContext(ThemeContext);
17
+ }
18
+
19
+ function getSystemTheme(): Theme {
20
+ if (typeof window === 'undefined') return 'light';
21
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
22
+ }
23
+
24
+ function getStoredTheme(): Theme | null {
25
+ if (typeof window === 'undefined') return null;
26
+ return localStorage.getItem('clearify-theme') as Theme | null;
27
+ }
28
+
29
+ export function ThemeProvider({
30
+ mode,
31
+ children,
32
+ }: {
33
+ mode: 'light' | 'dark' | 'auto';
34
+ children: React.ReactNode;
35
+ }) {
36
+ const [theme, setTheme] = useState<Theme>(() => {
37
+ if (mode === 'auto') {
38
+ return getStoredTheme() ?? getSystemTheme();
39
+ }
40
+ return mode;
41
+ });
42
+
43
+ useEffect(() => {
44
+ const root = document.documentElement;
45
+ if (theme === 'dark') {
46
+ root.classList.add('dark');
47
+ } else {
48
+ root.classList.remove('dark');
49
+ }
50
+ }, [theme]);
51
+
52
+ useEffect(() => {
53
+ if (mode !== 'auto') return;
54
+ const media = window.matchMedia('(prefers-color-scheme: dark)');
55
+ const handler = (e: MediaQueryListEvent) => {
56
+ if (!getStoredTheme()) {
57
+ setTheme(e.matches ? 'dark' : 'light');
58
+ }
59
+ };
60
+ media.addEventListener('change', handler);
61
+ return () => media.removeEventListener('change', handler);
62
+ }, [mode]);
63
+
64
+ const toggleTheme = useCallback(() => {
65
+ setTheme((prev) => {
66
+ const next = prev === 'light' ? 'dark' : 'light';
67
+ localStorage.setItem('clearify-theme', next);
68
+ return next;
69
+ });
70
+ }, []);
71
+
72
+ return (
73
+ <ThemeContext.Provider value={{ theme, toggleTheme }}>
74
+ {children}
75
+ </ThemeContext.Provider>
76
+ );
77
+ }
@@ -0,0 +1,109 @@
1
+ import React, { useState, Children, isValidElement } from 'react';
2
+
3
+ interface AccordionProps {
4
+ title: string;
5
+ defaultOpen?: boolean;
6
+ icon?: string;
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ export function Accordion({ title, defaultOpen = false, icon, children }: AccordionProps) {
11
+ const [open, setOpen] = useState(defaultOpen);
12
+
13
+ return (
14
+ <div
15
+ style={{
16
+ border: '1px solid var(--clearify-border)',
17
+ borderRadius: 'var(--clearify-radius)',
18
+ marginBottom: '0.5rem',
19
+ overflow: 'hidden',
20
+ background: 'var(--clearify-bg)',
21
+ }}
22
+ className="clearify-accordion"
23
+ >
24
+ <button
25
+ onClick={() => setOpen(!open)}
26
+ style={{
27
+ display: 'flex',
28
+ alignItems: 'center',
29
+ gap: '0.5rem',
30
+ width: '100%',
31
+ padding: '0.75rem 1rem',
32
+ background: 'none',
33
+ border: 'none',
34
+ cursor: 'pointer',
35
+ fontSize: '0.9375rem',
36
+ fontWeight: 600,
37
+ color: 'var(--clearify-text)',
38
+ fontFamily: 'var(--font-sans)',
39
+ textAlign: 'left',
40
+ letterSpacing: '-0.01em',
41
+ }}
42
+ className="clearify-accordion-trigger"
43
+ >
44
+ <svg
45
+ width="16"
46
+ height="16"
47
+ viewBox="0 0 24 24"
48
+ fill="none"
49
+ stroke="currentColor"
50
+ strokeWidth="2"
51
+ strokeLinecap="round"
52
+ strokeLinejoin="round"
53
+ style={{
54
+ flexShrink: 0,
55
+ transform: open ? 'rotate(90deg)' : 'rotate(0deg)',
56
+ transition: 'transform 0.15s cubic-bezier(0.4, 0, 0.2, 1)',
57
+ color: 'var(--clearify-text-secondary)',
58
+ }}
59
+ >
60
+ <polyline points="9 18 15 12 9 6" />
61
+ </svg>
62
+ {icon && <span style={{ flexShrink: 0 }}>{icon}</span>}
63
+ <span style={{ flex: 1 }}>{title}</span>
64
+ </button>
65
+ <div
66
+ style={{
67
+ display: 'grid',
68
+ gridTemplateRows: open ? '1fr' : '0fr',
69
+ transition: 'grid-template-rows 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
70
+ }}
71
+ >
72
+ <div style={{ overflow: 'hidden' }}>
73
+ <div
74
+ style={{
75
+ padding: '0 1rem 1rem 2.5rem',
76
+ fontSize: '0.875rem',
77
+ color: 'var(--clearify-text-secondary)',
78
+ lineHeight: 1.6,
79
+ }}
80
+ >
81
+ {children}
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <style>{`
87
+ .clearify-accordion-trigger:hover {
88
+ background-color: var(--clearify-bg-secondary) !important;
89
+ }
90
+ `}</style>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ interface AccordionGroupProps {
96
+ children: React.ReactNode;
97
+ }
98
+
99
+ export function AccordionGroup({ children }: AccordionGroupProps) {
100
+ const items = Children.toArray(children).filter(isValidElement);
101
+
102
+ return (
103
+ <div style={{ marginBottom: '1.25rem' }}>
104
+ {items.map((child, i) => (
105
+ <React.Fragment key={i}>{child}</React.Fragment>
106
+ ))}
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+
3
+ const variants = {
4
+ default: {
5
+ bg: 'rgba(107, 114, 128, 0.08)',
6
+ bgDark: 'rgba(107, 114, 128, 0.15)',
7
+ color: '#4b5563',
8
+ colorDark: '#d1d5db',
9
+ },
10
+ success: {
11
+ bg: 'rgba(34, 197, 94, 0.08)',
12
+ bgDark: 'rgba(34, 197, 94, 0.15)',
13
+ color: '#16a34a',
14
+ colorDark: '#4ade80',
15
+ },
16
+ warning: {
17
+ bg: 'rgba(245, 158, 11, 0.08)',
18
+ bgDark: 'rgba(245, 158, 11, 0.15)',
19
+ color: '#d97706',
20
+ colorDark: '#fbbf24',
21
+ },
22
+ error: {
23
+ bg: 'rgba(239, 68, 68, 0.08)',
24
+ bgDark: 'rgba(239, 68, 68, 0.15)',
25
+ color: '#dc2626',
26
+ colorDark: '#f87171',
27
+ },
28
+ info: {
29
+ bg: 'rgba(99, 102, 241, 0.08)',
30
+ bgDark: 'rgba(99, 102, 241, 0.15)',
31
+ color: '#6366f1',
32
+ colorDark: '#818cf8',
33
+ },
34
+ };
35
+
36
+ interface BadgeProps {
37
+ children: React.ReactNode;
38
+ variant?: keyof typeof variants;
39
+ }
40
+
41
+ export function Badge({ children, variant = 'default' }: BadgeProps) {
42
+ const v = variants[variant];
43
+ const badgeClass = `clearify-badge-${variant}`;
44
+
45
+ return (
46
+ <span
47
+ style={{
48
+ display: 'inline-flex',
49
+ alignItems: 'center',
50
+ padding: '0.125rem 0.5rem',
51
+ borderRadius: '9999px',
52
+ fontSize: '0.75rem',
53
+ fontWeight: 500,
54
+ lineHeight: 1.5,
55
+ backgroundColor: v.bg,
56
+ color: v.color,
57
+ fontFamily: 'var(--font-sans)',
58
+ whiteSpace: 'nowrap',
59
+ }}
60
+ className={badgeClass}
61
+ >
62
+ {children}
63
+
64
+ <style>{`
65
+ .dark .${badgeClass} {
66
+ background-color: ${v.bgDark} !important;
67
+ color: ${v.colorDark} !important;
68
+ }
69
+ `}</style>
70
+ </span>
71
+ );
72
+ }
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import { Link, useLocation } from 'react-router-dom';
3
+
4
+ function toTitleCase(str: string): string {
5
+ return str
6
+ .replace(/[-_]/g, ' ')
7
+ .replace(/\b\w/g, (c) => c.toUpperCase());
8
+ }
9
+
10
+ export function Breadcrumbs() {
11
+ const location = useLocation();
12
+ const pathname = location.pathname;
13
+
14
+ // Don't show breadcrumbs on the root page
15
+ if (pathname === '/') return null;
16
+
17
+ const segments = pathname.split('/').filter(Boolean);
18
+ if (segments.length === 0) return null;
19
+
20
+ const crumbs = segments.map((segment, i) => {
21
+ const path = '/' + segments.slice(0, i + 1).join('/');
22
+ const label = toTitleCase(segment);
23
+ const isLast = i === segments.length - 1;
24
+ return { label, path, isLast };
25
+ });
26
+
27
+ return (
28
+ <nav
29
+ aria-label="Breadcrumb"
30
+ style={{
31
+ display: 'flex',
32
+ alignItems: 'center',
33
+ gap: '0.375rem',
34
+ fontSize: '0.8125rem',
35
+ marginBottom: '1rem',
36
+ flexWrap: 'wrap',
37
+ }}
38
+ >
39
+ <Link
40
+ to="/"
41
+ style={{
42
+ color: 'var(--clearify-text-secondary)',
43
+ textDecoration: 'none',
44
+ transition: 'color 0.15s',
45
+ }}
46
+ className="clearify-breadcrumb-link"
47
+ >
48
+ Home
49
+ </Link>
50
+ {crumbs.map((crumb) => (
51
+ <React.Fragment key={crumb.path}>
52
+ <span
53
+ style={{
54
+ color: 'var(--clearify-text-tertiary)',
55
+ userSelect: 'none',
56
+ fontSize: '0.75rem',
57
+ }}
58
+ >
59
+ /
60
+ </span>
61
+ {crumb.isLast ? (
62
+ <span style={{ color: 'var(--clearify-text)', fontWeight: 500 }}>
63
+ {crumb.label}
64
+ </span>
65
+ ) : (
66
+ <Link
67
+ to={crumb.path}
68
+ style={{
69
+ color: 'var(--clearify-text-secondary)',
70
+ textDecoration: 'none',
71
+ transition: 'color 0.15s',
72
+ }}
73
+ className="clearify-breadcrumb-link"
74
+ >
75
+ {crumb.label}
76
+ </Link>
77
+ )}
78
+ </React.Fragment>
79
+ ))}
80
+
81
+ <style>{`
82
+ .clearify-breadcrumb-link:hover {
83
+ color: var(--clearify-primary) !important;
84
+ }
85
+ `}</style>
86
+ </nav>
87
+ );
88
+ }
@@ -0,0 +1,115 @@
1
+ import React from 'react';
2
+
3
+ const variants = {
4
+ info: {
5
+ bg: 'rgba(99, 102, 241, 0.06)',
6
+ bgDark: 'rgba(99, 102, 241, 0.1)',
7
+ border: '#6366f1',
8
+ iconColor: '#6366f1',
9
+ iconColorDark: '#818cf8',
10
+ icon: (
11
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
12
+ <circle cx="12" cy="12" r="10" />
13
+ <line x1="12" y1="16" x2="12" y2="12" />
14
+ <line x1="12" y1="8" x2="12.01" y2="8" />
15
+ </svg>
16
+ ),
17
+ },
18
+ warning: {
19
+ bg: 'rgba(245, 158, 11, 0.06)',
20
+ bgDark: 'rgba(245, 158, 11, 0.1)',
21
+ border: '#f59e0b',
22
+ iconColor: '#d97706',
23
+ iconColorDark: '#fbbf24',
24
+ icon: (
25
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
26
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
27
+ <line x1="12" y1="9" x2="12" y2="13" />
28
+ <line x1="12" y1="17" x2="12.01" y2="17" />
29
+ </svg>
30
+ ),
31
+ },
32
+ error: {
33
+ bg: 'rgba(239, 68, 68, 0.06)',
34
+ bgDark: 'rgba(239, 68, 68, 0.1)',
35
+ border: '#ef4444',
36
+ iconColor: '#dc2626',
37
+ iconColorDark: '#f87171',
38
+ icon: (
39
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
40
+ <circle cx="12" cy="12" r="10" />
41
+ <line x1="15" y1="9" x2="9" y2="15" />
42
+ <line x1="9" y1="9" x2="15" y2="15" />
43
+ </svg>
44
+ ),
45
+ },
46
+ tip: {
47
+ bg: 'rgba(34, 197, 94, 0.06)',
48
+ bgDark: 'rgba(34, 197, 94, 0.1)',
49
+ border: '#22c55e',
50
+ iconColor: '#16a34a',
51
+ iconColorDark: '#4ade80',
52
+ icon: (
53
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
54
+ <path d="M9 18h6" />
55
+ <path d="M10 22h4" />
56
+ <path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14" />
57
+ </svg>
58
+ ),
59
+ },
60
+ };
61
+
62
+ interface CalloutProps {
63
+ type?: keyof typeof variants;
64
+ title?: string;
65
+ children: React.ReactNode;
66
+ }
67
+
68
+ export function Callout({ type = 'info', title, children }: CalloutProps) {
69
+ const v = variants[type];
70
+ const calloutId = `clearify-callout-${type}`;
71
+
72
+ return (
73
+ <div
74
+ style={{
75
+ borderLeft: `3px solid ${v.border}`,
76
+ borderRadius: `0 var(--clearify-radius) var(--clearify-radius) 0`,
77
+ padding: '0.875rem 1.25rem',
78
+ marginBottom: '1.25rem',
79
+ backgroundColor: v.bg,
80
+ fontSize: '0.9375rem',
81
+ }}
82
+ className={calloutId}
83
+ >
84
+ <div
85
+ style={{
86
+ display: 'flex',
87
+ alignItems: 'center',
88
+ gap: '0.5rem',
89
+ marginBottom: children ? '0.375rem' : 0,
90
+ fontWeight: 600,
91
+ fontSize: '0.875rem',
92
+ color: v.iconColor,
93
+ }}
94
+ >
95
+ <span style={{ display: 'flex', alignItems: 'center', flexShrink: 0 }}>{v.icon}</span>
96
+ {title && <span>{title}</span>}
97
+ {!title && <span style={{ textTransform: 'capitalize' }}>{type}</span>}
98
+ </div>
99
+ {children && (
100
+ <div style={{ fontSize: '0.875rem', color: 'var(--clearify-text-secondary)', lineHeight: 1.6 }}>
101
+ {children}
102
+ </div>
103
+ )}
104
+
105
+ <style>{`
106
+ .dark .${calloutId} {
107
+ background-color: ${v.bgDark} !important;
108
+ }
109
+ .dark .${calloutId} > div:first-child {
110
+ color: ${v.iconColorDark} !important;
111
+ }
112
+ `}</style>
113
+ </div>
114
+ );
115
+ }