@hypoth-ui/docs-renderer-next 0.1.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.
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Logo Component
5
+ *
6
+ * Displays the branding logo or falls back to text-based name.
7
+ */
8
+
9
+ import { useState } from "react";
10
+ import { useBranding } from "../../lib/branding-context";
11
+
12
+ export interface LogoProps {
13
+ /** Custom class name */
14
+ className?: string;
15
+ /** Logo size variant */
16
+ size?: "small" | "medium" | "large";
17
+ /** Whether to show text alongside logo */
18
+ showText?: boolean;
19
+ }
20
+
21
+ export function Logo({ className = "", size = "medium", showText = false }: LogoProps) {
22
+ const { name, logo, primaryColor } = useBranding();
23
+ const [imageError, setImageError] = useState(false);
24
+
25
+ const sizeClasses: Record<string, string> = {
26
+ small: "logo--small",
27
+ medium: "logo--medium",
28
+ large: "logo--large",
29
+ };
30
+
31
+ const handleImageError = () => {
32
+ setImageError(true);
33
+ };
34
+
35
+ // If logo URL provided and no error, show image
36
+ if (logo && !imageError) {
37
+ return (
38
+ <span className={`logo ${sizeClasses[size]} ${className}`}>
39
+ <img src={logo} alt={name} className="logo__image" onError={handleImageError} />
40
+ {showText && <span className="logo__text">{name}</span>}
41
+ </span>
42
+ );
43
+ }
44
+
45
+ // Fallback to text-based logo
46
+ return (
47
+ <span className={`logo logo--text ${sizeClasses[size]} ${className}`}>
48
+ <span className="logo__initial" style={{ backgroundColor: primaryColor }}>
49
+ {name.charAt(0).toUpperCase()}
50
+ </span>
51
+ <span className="logo__text">{name}</span>
52
+ </span>
53
+ );
54
+ }
@@ -0,0 +1,263 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Feedback Widget Component (Stub)
5
+ *
6
+ * Placeholder component for collecting user feedback.
7
+ * Full implementation will integrate with feedback API.
8
+ */
9
+
10
+ import { useState } from "react";
11
+
12
+ export interface FeedbackWidgetProps {
13
+ /** Current page URL or identifier */
14
+ pageId?: string;
15
+ /** Custom class name */
16
+ className?: string;
17
+ /** Position of the widget */
18
+ position?: "bottom-right" | "bottom-left";
19
+ }
20
+
21
+ export function FeedbackWidget({
22
+ pageId,
23
+ className = "",
24
+ position = "bottom-right",
25
+ }: FeedbackWidgetProps) {
26
+ const [isOpen, setIsOpen] = useState(false);
27
+ const [submitted, setSubmitted] = useState(false);
28
+
29
+ const handleSubmit = (helpful: boolean) => {
30
+ // Stub: In production, this would send feedback to an API
31
+ console.info(`Feedback submitted for ${pageId}: ${helpful ? "helpful" : "not helpful"}`);
32
+ setSubmitted(true);
33
+ setTimeout(() => {
34
+ setIsOpen(false);
35
+ setSubmitted(false);
36
+ }, 2000);
37
+ };
38
+
39
+ const positionClass =
40
+ position === "bottom-left" ? "feedback-widget--left" : "feedback-widget--right";
41
+
42
+ return (
43
+ <div className={`feedback-widget ${positionClass} ${className}`}>
44
+ {!isOpen ? (
45
+ <button
46
+ type="button"
47
+ className="feedback-widget__trigger"
48
+ onClick={() => setIsOpen(true)}
49
+ aria-label="Give feedback"
50
+ >
51
+ <svg
52
+ width="20"
53
+ height="20"
54
+ viewBox="0 0 20 20"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ strokeWidth="2"
58
+ aria-hidden="true"
59
+ >
60
+ <path d="M18 10c0 4.4-3.6 8-8 8a8 8 0 0 1-3.5-.8L2 18l.8-4.5A8 8 0 1 1 18 10Z" />
61
+ </svg>
62
+ <span>Feedback</span>
63
+ </button>
64
+ ) : (
65
+ <div className="feedback-widget__panel" aria-label="Feedback form">
66
+ {submitted ? (
67
+ <div className="feedback-widget__thanks">
68
+ <svg
69
+ width="24"
70
+ height="24"
71
+ viewBox="0 0 24 24"
72
+ fill="none"
73
+ stroke="currentColor"
74
+ strokeWidth="2"
75
+ aria-hidden="true"
76
+ >
77
+ <circle cx="12" cy="12" r="10" />
78
+ <path d="m9 12 2 2 4-4" />
79
+ </svg>
80
+ <span>Thank you for your feedback!</span>
81
+ </div>
82
+ ) : (
83
+ <>
84
+ <div className="feedback-widget__header">
85
+ <span>Was this page helpful?</span>
86
+ <button
87
+ type="button"
88
+ className="feedback-widget__close"
89
+ onClick={() => setIsOpen(false)}
90
+ aria-label="Close feedback"
91
+ >
92
+ <svg
93
+ width="16"
94
+ height="16"
95
+ viewBox="0 0 16 16"
96
+ fill="none"
97
+ stroke="currentColor"
98
+ strokeWidth="2"
99
+ aria-hidden="true"
100
+ >
101
+ <path d="M4 4l8 8M12 4l-8 8" />
102
+ </svg>
103
+ </button>
104
+ </div>
105
+ <div className="feedback-widget__buttons">
106
+ <button
107
+ type="button"
108
+ className="feedback-widget__btn feedback-widget__btn--yes"
109
+ onClick={() => handleSubmit(true)}
110
+ >
111
+ <svg
112
+ width="20"
113
+ height="20"
114
+ viewBox="0 0 20 20"
115
+ fill="none"
116
+ stroke="currentColor"
117
+ strokeWidth="2"
118
+ aria-hidden="true"
119
+ >
120
+ <path d="M6 10h.01M10 10h.01M14 10h.01M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z" />
121
+ <path d="M7 13s1.5 2 3 2 3-2 3-2" />
122
+ </svg>
123
+ Yes
124
+ </button>
125
+ <button
126
+ type="button"
127
+ className="feedback-widget__btn feedback-widget__btn--no"
128
+ onClick={() => handleSubmit(false)}
129
+ >
130
+ <svg
131
+ width="20"
132
+ height="20"
133
+ viewBox="0 0 20 20"
134
+ fill="none"
135
+ stroke="currentColor"
136
+ strokeWidth="2"
137
+ aria-hidden="true"
138
+ >
139
+ <path d="M6 10h.01M10 10h.01M14 10h.01M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z" />
140
+ <path d="M7 14s1.5-2 3-2 3 2 3 2" />
141
+ </svg>
142
+ No
143
+ </button>
144
+ </div>
145
+ </>
146
+ )}
147
+ </div>
148
+ )}
149
+
150
+ <style jsx>{`
151
+ .feedback-widget {
152
+ position: fixed;
153
+ bottom: 1.5rem;
154
+ z-index: 1000;
155
+ }
156
+
157
+ .feedback-widget--right {
158
+ right: 1.5rem;
159
+ }
160
+
161
+ .feedback-widget--left {
162
+ left: 1.5rem;
163
+ }
164
+
165
+ .feedback-widget__trigger {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.5rem;
169
+ padding: 0.75rem 1rem;
170
+ background: var(--ds-brand-primary, #0066cc);
171
+ color: white;
172
+ border: none;
173
+ border-radius: 9999px;
174
+ font-size: 0.875rem;
175
+ font-weight: 500;
176
+ cursor: pointer;
177
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
178
+ transition: transform 0.15s, box-shadow 0.15s;
179
+ }
180
+
181
+ .feedback-widget__trigger:hover {
182
+ transform: translateY(-2px);
183
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
184
+ }
185
+
186
+ .feedback-widget__panel {
187
+ background: var(--ds-color-background-surface, #fff);
188
+ border: 1px solid var(--ds-color-border-default, #e5e5e5);
189
+ border-radius: 12px;
190
+ padding: 1rem;
191
+ min-width: 240px;
192
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
193
+ }
194
+
195
+ .feedback-widget__header {
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: space-between;
199
+ margin-bottom: 0.75rem;
200
+ font-weight: 500;
201
+ color: var(--ds-color-foreground-default, #1a1a1a);
202
+ }
203
+
204
+ .feedback-widget__close {
205
+ padding: 0.25rem;
206
+ background: transparent;
207
+ border: none;
208
+ color: var(--ds-color-foreground-muted, #666);
209
+ cursor: pointer;
210
+ border-radius: 4px;
211
+ }
212
+
213
+ .feedback-widget__close:hover {
214
+ background: var(--ds-color-background-subtle, #f5f5f5);
215
+ }
216
+
217
+ .feedback-widget__buttons {
218
+ display: flex;
219
+ gap: 0.75rem;
220
+ }
221
+
222
+ .feedback-widget__btn {
223
+ flex: 1;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ gap: 0.5rem;
228
+ padding: 0.75rem;
229
+ background: var(--ds-color-background-subtle, #f5f5f5);
230
+ border: 1px solid var(--ds-color-border-default, #e5e5e5);
231
+ border-radius: 8px;
232
+ font-size: 0.875rem;
233
+ font-weight: 500;
234
+ cursor: pointer;
235
+ transition: background 0.15s, border-color 0.15s;
236
+ }
237
+
238
+ .feedback-widget__btn:hover {
239
+ background: var(--ds-color-background-surface, #fff);
240
+ border-color: var(--ds-brand-primary, #0066cc);
241
+ }
242
+
243
+ .feedback-widget__btn--yes:hover {
244
+ color: #16a34a;
245
+ border-color: #16a34a;
246
+ }
247
+
248
+ .feedback-widget__btn--no:hover {
249
+ color: #dc2626;
250
+ border-color: #dc2626;
251
+ }
252
+
253
+ .feedback-widget__thanks {
254
+ display: flex;
255
+ align-items: center;
256
+ gap: 0.75rem;
257
+ padding: 0.5rem;
258
+ color: #16a34a;
259
+ }
260
+ `}</style>
261
+ </div>
262
+ );
263
+ }