@marlinjai/clearify 1.11.2 → 1.13.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.
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
  import { useTheme } from './ThemeProvider.js';
4
4
  import { Search } from './Search.js';
5
+ // @ts-expect-error virtual module
6
+ import adminEnabled from 'virtual:clearify/admin-enabled';
5
7
 
6
8
  interface HeaderProps {
7
9
  name: string;
@@ -158,6 +160,36 @@ export function Header({ name, links, logo, onMenuToggle }: HeaderProps) {
158
160
  </a>
159
161
  )}
160
162
 
163
+ {adminEnabled && (
164
+ <Link
165
+ to="/admin"
166
+ aria-label="Admin panel"
167
+ style={{
168
+ display: 'inline-flex',
169
+ alignItems: 'center',
170
+ justifyContent: 'center',
171
+ width: 36,
172
+ height: 36,
173
+ borderRadius: 'var(--clearify-radius-sm)',
174
+ color: 'var(--clearify-text-secondary)',
175
+ transition: 'color 0.15s, background-color 0.15s',
176
+ }}
177
+ onMouseEnter={(e) => {
178
+ e.currentTarget.style.color = 'var(--clearify-text)';
179
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
180
+ }}
181
+ onMouseLeave={(e) => {
182
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
183
+ e.currentTarget.style.backgroundColor = 'transparent';
184
+ }}
185
+ >
186
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
187
+ <circle cx="12" cy="12" r="3" />
188
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
189
+ </svg>
190
+ </Link>
191
+ )}
192
+
161
193
  <button
162
194
  onClick={toggleTheme}
163
195
  style={{
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+
4
+ const cardStyle: React.CSSProperties = {
5
+ backgroundColor: 'var(--clearify-surface-elevated)',
6
+ border: '1px solid var(--clearify-border)',
7
+ borderRadius: 'var(--clearify-radius)',
8
+ padding: '1.5rem',
9
+ boxShadow: 'var(--clearify-shadow)',
10
+ textDecoration: 'none',
11
+ color: 'inherit',
12
+ transition: 'box-shadow 0.15s, border-color 0.15s',
13
+ };
14
+
15
+ interface DashboardCardProps {
16
+ title: string;
17
+ description: string;
18
+ to: string;
19
+ icon: React.ReactNode;
20
+ }
21
+
22
+ function DashboardCard({ title, description, to, icon }: DashboardCardProps) {
23
+ return (
24
+ <Link
25
+ to={to}
26
+ style={cardStyle}
27
+ onMouseEnter={(e) => {
28
+ e.currentTarget.style.boxShadow = 'var(--clearify-shadow-glow)';
29
+ e.currentTarget.style.borderColor = 'var(--clearify-primary)';
30
+ }}
31
+ onMouseLeave={(e) => {
32
+ e.currentTarget.style.boxShadow = 'var(--clearify-shadow)';
33
+ e.currentTarget.style.borderColor = 'var(--clearify-border)';
34
+ }}
35
+ >
36
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.75rem' }}>
37
+ <div
38
+ style={{
39
+ width: 40,
40
+ height: 40,
41
+ borderRadius: 'var(--clearify-radius-sm)',
42
+ background: 'var(--clearify-primary-soft)',
43
+ display: 'flex',
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ color: 'var(--clearify-primary)',
47
+ }}
48
+ >
49
+ {icon}
50
+ </div>
51
+ <h3 style={{ fontSize: '1.125rem', fontWeight: 650, color: 'var(--clearify-text)' }}>
52
+ {title}
53
+ </h3>
54
+ </div>
55
+ <p style={{ fontSize: '0.875rem', color: 'var(--clearify-text-secondary)', lineHeight: 1.5 }}>
56
+ {description}
57
+ </p>
58
+ </Link>
59
+ );
60
+ }
61
+
62
+ export function AdminDashboard() {
63
+ return (
64
+ <div style={{ padding: '2rem', maxWidth: '64rem', margin: '0 auto' }}>
65
+ <h1
66
+ style={{
67
+ fontSize: '2rem',
68
+ fontWeight: 800,
69
+ letterSpacing: '-0.035em',
70
+ color: 'var(--clearify-text)',
71
+ marginBottom: '0.5rem',
72
+ }}
73
+ >
74
+ Admin Dashboard
75
+ </h1>
76
+ <p style={{ color: 'var(--clearify-text-secondary)', marginBottom: '2rem' }}>
77
+ Manage your Clearify documentation site.
78
+ </p>
79
+
80
+ <div
81
+ style={{
82
+ display: 'grid',
83
+ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
84
+ gap: '1.25rem',
85
+ }}
86
+ >
87
+ <DashboardCard
88
+ title="Projects"
89
+ description="Manage hub projects, add or remove project entries."
90
+ to="/admin/projects"
91
+ icon={
92
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
93
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
94
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
95
+ </svg>
96
+ }
97
+ />
98
+ <DashboardCard
99
+ title="Sections"
100
+ description="Organize documentation sections and navigation structure."
101
+ to="/admin/sections"
102
+ icon={
103
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
104
+ <line x1="8" y1="6" x2="21" y2="6" />
105
+ <line x1="8" y1="12" x2="21" y2="12" />
106
+ <line x1="8" y1="18" x2="21" y2="18" />
107
+ <line x1="3" y1="6" x2="3.01" y2="6" />
108
+ <line x1="3" y1="12" x2="3.01" y2="12" />
109
+ <line x1="3" y1="18" x2="3.01" y2="18" />
110
+ </svg>
111
+ }
112
+ />
113
+ <DashboardCard
114
+ title="Site Settings"
115
+ description="Configure site name, colors, links, and other settings."
116
+ to="/admin/settings"
117
+ icon={
118
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
119
+ <circle cx="12" cy="12" r="3" />
120
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
121
+ </svg>
122
+ }
123
+ />
124
+ </div>
125
+ </div>
126
+ );
127
+ }
128
+
129
+ export default AdminDashboard;
@@ -0,0 +1,240 @@
1
+ import React from 'react';
2
+ import { Link, Outlet, useLocation } from 'react-router-dom';
3
+ import { ThemeProvider } from '../ThemeProvider.js';
4
+ import { useTheme } from '../ThemeProvider.js';
5
+
6
+ const navItems = [
7
+ {
8
+ label: 'Projects',
9
+ to: '/admin/projects',
10
+ icon: (
11
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
12
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
13
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
14
+ </svg>
15
+ ),
16
+ },
17
+ {
18
+ label: 'Sections',
19
+ to: '/admin/sections',
20
+ icon: (
21
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
22
+ <line x1="8" y1="6" x2="21" y2="6" />
23
+ <line x1="8" y1="12" x2="21" y2="12" />
24
+ <line x1="8" y1="18" x2="21" y2="18" />
25
+ <line x1="3" y1="6" x2="3.01" y2="6" />
26
+ <line x1="3" y1="12" x2="3.01" y2="12" />
27
+ <line x1="3" y1="18" x2="3.01" y2="18" />
28
+ </svg>
29
+ ),
30
+ },
31
+ {
32
+ label: 'Settings',
33
+ to: '/admin/settings',
34
+ icon: (
35
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
36
+ <circle cx="12" cy="12" r="3" />
37
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
38
+ </svg>
39
+ ),
40
+ },
41
+ ];
42
+
43
+ function AdminLayoutInner() {
44
+ const location = useLocation();
45
+ const { theme, toggleTheme } = useTheme();
46
+
47
+ return (
48
+ <>
49
+ <div className="clearify-bg-mesh" aria-hidden="true" />
50
+ <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
51
+ {/* Admin Header */}
52
+ <header
53
+ style={{
54
+ height: 'var(--clearify-header-height)',
55
+ borderBottom: '1px solid var(--clearify-border)',
56
+ backgroundColor: 'var(--clearify-surface)',
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ padding: '0 1.5rem',
60
+ position: 'sticky',
61
+ top: 0,
62
+ zIndex: 50,
63
+ backdropFilter: 'blur(16px) saturate(180%)',
64
+ WebkitBackdropFilter: 'blur(16px) saturate(180%)',
65
+ }}
66
+ >
67
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginRight: 'auto' }}>
68
+ <div
69
+ style={{
70
+ width: 28,
71
+ height: 28,
72
+ borderRadius: 'var(--clearify-radius-sm)',
73
+ background: 'var(--clearify-gradient)',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ color: '#fff',
78
+ }}
79
+ >
80
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
81
+ <circle cx="12" cy="12" r="3" />
82
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
83
+ </svg>
84
+ </div>
85
+ <span style={{ fontWeight: 700, fontSize: '1rem', color: 'var(--clearify-text)', letterSpacing: '-0.02em' }}>
86
+ Clearify Admin
87
+ </span>
88
+ </div>
89
+
90
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }}>
91
+ <Link
92
+ to="/"
93
+ style={{
94
+ display: 'inline-flex',
95
+ alignItems: 'center',
96
+ gap: '0.375rem',
97
+ padding: '0.375rem 0.75rem',
98
+ borderRadius: 'var(--clearify-radius-sm)',
99
+ fontSize: '0.8125rem',
100
+ fontWeight: 500,
101
+ color: 'var(--clearify-text-secondary)',
102
+ textDecoration: 'none',
103
+ transition: 'color 0.15s, background-color 0.15s',
104
+ }}
105
+ onMouseEnter={(e) => {
106
+ e.currentTarget.style.color = 'var(--clearify-text)';
107
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
108
+ }}
109
+ onMouseLeave={(e) => {
110
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
111
+ e.currentTarget.style.backgroundColor = 'transparent';
112
+ }}
113
+ >
114
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
115
+ <line x1="19" y1="12" x2="5" y2="12" />
116
+ <polyline points="12 19 5 12 12 5" />
117
+ </svg>
118
+ Back to docs
119
+ </Link>
120
+
121
+ <button
122
+ onClick={toggleTheme}
123
+ style={{
124
+ display: 'inline-flex',
125
+ alignItems: 'center',
126
+ justifyContent: 'center',
127
+ width: 36,
128
+ height: 36,
129
+ background: 'none',
130
+ border: 'none',
131
+ cursor: 'pointer',
132
+ borderRadius: 'var(--clearify-radius-sm)',
133
+ color: 'var(--clearify-text-secondary)',
134
+ transition: 'color 0.15s, background-color 0.15s',
135
+ }}
136
+ aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
137
+ onMouseEnter={(e) => {
138
+ e.currentTarget.style.color = 'var(--clearify-text)';
139
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
140
+ }}
141
+ onMouseLeave={(e) => {
142
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
143
+ e.currentTarget.style.backgroundColor = 'transparent';
144
+ }}
145
+ >
146
+ {theme === 'light' ? (
147
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
148
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
149
+ </svg>
150
+ ) : (
151
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
152
+ <circle cx="12" cy="12" r="5" />
153
+ <line x1="12" y1="1" x2="12" y2="3" />
154
+ <line x1="12" y1="21" x2="12" y2="23" />
155
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
156
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
157
+ <line x1="1" y1="12" x2="3" y2="12" />
158
+ <line x1="21" y1="12" x2="23" y2="12" />
159
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
160
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
161
+ </svg>
162
+ )}
163
+ </button>
164
+ </div>
165
+ </header>
166
+
167
+ {/* Body: sidebar + content */}
168
+ <div style={{ display: 'flex', flex: 1 }}>
169
+ {/* Sidebar */}
170
+ <nav
171
+ style={{
172
+ width: 'var(--clearify-sidebar-width)',
173
+ borderRight: '1px solid var(--clearify-border)',
174
+ padding: '1rem 0.75rem',
175
+ flexShrink: 0,
176
+ backgroundColor: 'var(--clearify-bg)',
177
+ }}
178
+ >
179
+ <div style={{ fontSize: '0.6875rem', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--clearify-text-tertiary)', padding: '0.5rem 0.75rem', marginBottom: '0.25rem' }}>
180
+ Navigation
181
+ </div>
182
+ {navItems.map((item) => {
183
+ const isActive = location.pathname === item.to;
184
+ return (
185
+ <Link
186
+ key={item.to}
187
+ to={item.to}
188
+ style={{
189
+ display: 'flex',
190
+ alignItems: 'center',
191
+ gap: '0.625rem',
192
+ padding: '0.5rem 0.75rem',
193
+ borderRadius: 'var(--clearify-radius-sm)',
194
+ fontSize: '0.875rem',
195
+ fontWeight: isActive ? 600 : 450,
196
+ color: isActive ? 'var(--clearify-primary)' : 'var(--clearify-text-secondary)',
197
+ backgroundColor: isActive ? 'var(--clearify-primary-soft)' : 'transparent',
198
+ textDecoration: 'none',
199
+ transition: 'background-color 0.15s, color 0.15s',
200
+ marginBottom: '2px',
201
+ }}
202
+ onMouseEnter={(e) => {
203
+ if (!isActive) {
204
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
205
+ e.currentTarget.style.color = 'var(--clearify-text)';
206
+ }
207
+ }}
208
+ onMouseLeave={(e) => {
209
+ if (!isActive) {
210
+ e.currentTarget.style.backgroundColor = 'transparent';
211
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
212
+ }
213
+ }}
214
+ >
215
+ {item.icon}
216
+ {item.label}
217
+ </Link>
218
+ );
219
+ })}
220
+ </nav>
221
+
222
+ {/* Main content */}
223
+ <main style={{ flex: 1, overflow: 'auto' }}>
224
+ <Outlet />
225
+ </main>
226
+ </div>
227
+ </div>
228
+ </>
229
+ );
230
+ }
231
+
232
+ export function AdminLayout() {
233
+ return (
234
+ <ThemeProvider mode="auto">
235
+ <AdminLayoutInner />
236
+ </ThemeProvider>
237
+ );
238
+ }
239
+
240
+ export default AdminLayout;