@semiont/react-ui 0.4.12 → 0.4.14
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/index.d.mts +12 -5
- package/dist/index.mjs +60 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Toolbar.tsx +13 -13
- package/src/components/navigation/Footer.tsx +34 -37
- package/src/components/navigation/__tests__/Footer.test.tsx +0 -33
- package/src/components/resource/panels/CollaborationPanel.tsx +9 -1
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -33
- package/src/features/auth/components/SignInForm.tsx +0 -13
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +7 -0
- package/src/styles/patterns/panels-base.css +12 -0
package/package.json
CHANGED
|
@@ -77,19 +77,6 @@ export function Toolbar<T extends string = string>({
|
|
|
77
77
|
<span className="semiont-toolbar-icon" aria-hidden="true">📒</span>
|
|
78
78
|
</button>
|
|
79
79
|
|
|
80
|
-
{/* Collaboration Icon */}
|
|
81
|
-
<button
|
|
82
|
-
onClick={() => handlePanelToggle('collaboration')}
|
|
83
|
-
className="semiont-toolbar-button"
|
|
84
|
-
data-active={activePanel === 'collaboration'}
|
|
85
|
-
data-panel="collaboration"
|
|
86
|
-
aria-label={t('collaboration')}
|
|
87
|
-
aria-pressed={activePanel === 'collaboration'}
|
|
88
|
-
title={t('collaboration')}
|
|
89
|
-
>
|
|
90
|
-
<span className="semiont-toolbar-icon" aria-hidden="true">👥</span>
|
|
91
|
-
</button>
|
|
92
|
-
|
|
93
80
|
{/* JSON-LD Icon */}
|
|
94
81
|
<button
|
|
95
82
|
onClick={() => handlePanelToggle('jsonld')}
|
|
@@ -102,6 +89,19 @@ export function Toolbar<T extends string = string>({
|
|
|
102
89
|
>
|
|
103
90
|
<span className="semiont-toolbar-icon" aria-hidden="true">🌐</span>
|
|
104
91
|
</button>
|
|
92
|
+
|
|
93
|
+
{/* Collaboration Icon */}
|
|
94
|
+
<button
|
|
95
|
+
onClick={() => handlePanelToggle('collaboration')}
|
|
96
|
+
className="semiont-toolbar-button"
|
|
97
|
+
data-active={activePanel === 'collaboration'}
|
|
98
|
+
data-panel="collaboration"
|
|
99
|
+
aria-label={t('collaboration')}
|
|
100
|
+
aria-pressed={activePanel === 'collaboration'}
|
|
101
|
+
title={t('collaboration')}
|
|
102
|
+
>
|
|
103
|
+
<span className="semiont-toolbar-icon" aria-hidden="true">👥</span>
|
|
104
|
+
</button>
|
|
105
105
|
</>
|
|
106
106
|
)}
|
|
107
107
|
|
|
@@ -12,8 +12,9 @@ interface FooterProps {
|
|
|
12
12
|
t: TranslateFn;
|
|
13
13
|
CookiePreferences?: React.ComponentType<{ isOpen: boolean; onClose: () => void }>;
|
|
14
14
|
onOpenKeyboardHelp?: () => void;
|
|
15
|
-
apiDocsUrl?: string;
|
|
16
15
|
sourceCodeUrl?: string;
|
|
16
|
+
/** Show About, Privacy Policy, Terms of Service, Cookie Preferences links. False for desktop apps. */
|
|
17
|
+
showPolicyLinks?: boolean;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function Footer({
|
|
@@ -22,8 +23,8 @@ export function Footer({
|
|
|
22
23
|
t,
|
|
23
24
|
CookiePreferences,
|
|
24
25
|
onOpenKeyboardHelp,
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
sourceCodeUrl = 'https://github.com/The-AI-Alliance/semiont',
|
|
27
|
+
showPolicyLinks = true,
|
|
27
28
|
}: FooterProps) {
|
|
28
29
|
const [showCookiePreferences, setShowCookiePreferences] = useState(false);
|
|
29
30
|
|
|
@@ -37,25 +38,35 @@ export function Footer({
|
|
|
37
38
|
</div>
|
|
38
39
|
|
|
39
40
|
<div className="semiont-footer__links">
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
{showPolicyLinks && (
|
|
42
|
+
<>
|
|
43
|
+
<Link
|
|
44
|
+
href={routes.about?.() || '/about'}
|
|
45
|
+
className="semiont-footer__link"
|
|
46
|
+
>
|
|
47
|
+
{t('about')}
|
|
48
|
+
</Link>
|
|
49
|
+
<Link
|
|
50
|
+
href={routes.privacy?.() || '/privacy'}
|
|
51
|
+
className="semiont-footer__link"
|
|
52
|
+
>
|
|
53
|
+
{t('privacyPolicy')}
|
|
54
|
+
</Link>
|
|
55
|
+
{CookiePreferences && (
|
|
56
|
+
<button
|
|
57
|
+
onClick={() => setShowCookiePreferences(true)}
|
|
58
|
+
className="semiont-footer__link"
|
|
59
|
+
>
|
|
60
|
+
{t('cookiePreferences')}
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
<Link
|
|
64
|
+
href={routes.terms?.() || '/terms'}
|
|
65
|
+
className="semiont-footer__link"
|
|
66
|
+
>
|
|
67
|
+
{t('termsOfService')}
|
|
68
|
+
</Link>
|
|
69
|
+
</>
|
|
59
70
|
)}
|
|
60
71
|
{onOpenKeyboardHelp && (
|
|
61
72
|
<button
|
|
@@ -68,20 +79,6 @@ export function Footer({
|
|
|
68
79
|
</kbd>
|
|
69
80
|
</button>
|
|
70
81
|
)}
|
|
71
|
-
<Link
|
|
72
|
-
href={routes.terms?.() || '/terms'}
|
|
73
|
-
className="semiont-footer__link"
|
|
74
|
-
>
|
|
75
|
-
{t('termsOfService')}
|
|
76
|
-
</Link>
|
|
77
|
-
<a
|
|
78
|
-
href={apiDocsUrl}
|
|
79
|
-
target="_blank"
|
|
80
|
-
rel="noopener noreferrer"
|
|
81
|
-
className="semiont-footer__link"
|
|
82
|
-
>
|
|
83
|
-
{t('apiDocs')}
|
|
84
|
-
</a>
|
|
85
82
|
<a
|
|
86
83
|
href={sourceCodeUrl}
|
|
87
84
|
target="_blank"
|
|
@@ -95,7 +92,7 @@ export function Footer({
|
|
|
95
92
|
</div>
|
|
96
93
|
</footer>
|
|
97
94
|
|
|
98
|
-
{CookiePreferences && (
|
|
95
|
+
{showPolicyLinks && CookiePreferences && (
|
|
99
96
|
<CookiePreferences
|
|
100
97
|
isOpen={showCookiePreferences}
|
|
101
98
|
onClose={() => setShowCookiePreferences(false)}
|
|
@@ -93,22 +93,6 @@ describe('Footer Component', () => {
|
|
|
93
93
|
expect(termsLink).toHaveAttribute('href', '/terms');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
it('should render API Docs link with default URL', () => {
|
|
97
|
-
render(
|
|
98
|
-
<Footer
|
|
99
|
-
Link={MockLink}
|
|
100
|
-
routes={mockRoutes}
|
|
101
|
-
t={mockT}
|
|
102
|
-
/>
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const apiDocsLink = screen.getByText('footer.apiDocs');
|
|
106
|
-
expect(apiDocsLink).toBeInTheDocument();
|
|
107
|
-
expect(apiDocsLink).toHaveAttribute('href', '/api/docs');
|
|
108
|
-
expect(apiDocsLink).toHaveAttribute('target', '_blank');
|
|
109
|
-
expect(apiDocsLink).toHaveAttribute('rel', 'noopener noreferrer');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
96
|
it('should render Source Code link with default URL', () => {
|
|
113
97
|
render(
|
|
114
98
|
<Footer
|
|
@@ -272,20 +256,6 @@ describe('Footer Component', () => {
|
|
|
272
256
|
});
|
|
273
257
|
|
|
274
258
|
describe('Custom URLs', () => {
|
|
275
|
-
it('should use custom API Docs URL', () => {
|
|
276
|
-
render(
|
|
277
|
-
<Footer
|
|
278
|
-
Link={MockLink}
|
|
279
|
-
routes={mockRoutes}
|
|
280
|
-
t={mockT}
|
|
281
|
-
apiDocsUrl="/custom/api/docs"
|
|
282
|
-
/>
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const apiDocsLink = screen.getByText('footer.apiDocs');
|
|
286
|
-
expect(apiDocsLink).toHaveAttribute('href', '/custom/api/docs');
|
|
287
|
-
});
|
|
288
|
-
|
|
289
259
|
it('should use custom Source Code URL', () => {
|
|
290
260
|
render(
|
|
291
261
|
<Footer
|
|
@@ -411,9 +381,6 @@ describe('Footer Component', () => {
|
|
|
411
381
|
/>
|
|
412
382
|
);
|
|
413
383
|
|
|
414
|
-
const apiDocsLink = screen.getByText('footer.apiDocs');
|
|
415
|
-
expect(apiDocsLink).toHaveAttribute('rel', 'noopener noreferrer');
|
|
416
|
-
|
|
417
384
|
const sourceLink = screen.getByText('footer.sourceCode');
|
|
418
385
|
expect(sourceLink).toHaveAttribute('rel', 'noopener noreferrer');
|
|
419
386
|
});
|
|
@@ -7,12 +7,14 @@ interface Props {
|
|
|
7
7
|
isConnected: boolean;
|
|
8
8
|
eventCount: number;
|
|
9
9
|
lastEventTimestamp?: string;
|
|
10
|
+
knowledgeBaseName?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function CollaborationPanel({
|
|
13
14
|
isConnected,
|
|
14
15
|
eventCount,
|
|
15
|
-
lastEventTimestamp
|
|
16
|
+
lastEventTimestamp,
|
|
17
|
+
knowledgeBaseName
|
|
16
18
|
}: Props) {
|
|
17
19
|
const t = useTranslations('CollaborationPanel');
|
|
18
20
|
|
|
@@ -52,6 +54,12 @@ export function CollaborationPanel({
|
|
|
52
54
|
{t('title')}
|
|
53
55
|
</h3>
|
|
54
56
|
|
|
57
|
+
{knowledgeBaseName && (
|
|
58
|
+
<div style={{ padding: '0 0.75rem 0.5rem', fontSize: '0.8rem', color: 'var(--semiont-color-neutral-400)' }}>
|
|
59
|
+
{knowledgeBaseName}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
55
63
|
{/* Connection Status Section */}
|
|
56
64
|
<div className="semiont-collaboration-panel__section">
|
|
57
65
|
<h3 className="semiont-collaboration-panel__heading">
|
|
@@ -30,7 +30,7 @@ const mockTranslations = {
|
|
|
30
30
|
signInPrompt: 'Sign in to your knowledge workspace',
|
|
31
31
|
continueWithGoogle: 'Continue with Google',
|
|
32
32
|
backendUrlLabel: 'Backend URL',
|
|
33
|
-
backendUrlPlaceholder: '
|
|
33
|
+
backendUrlPlaceholder: 'http://localhost:4000',
|
|
34
34
|
emailLabel: 'Email',
|
|
35
35
|
emailPlaceholder: 'your@email.com',
|
|
36
36
|
passwordLabel: 'Password',
|
|
@@ -215,26 +215,6 @@ describe('SignInForm - Accessibility', () => {
|
|
|
215
215
|
expect(button).not.toHaveAttribute('disabled');
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
it('should have keyboard-accessible navigation links', () => {
|
|
219
|
-
const onGoogleSignIn = vi.fn();
|
|
220
|
-
|
|
221
|
-
render(
|
|
222
|
-
<SignInForm
|
|
223
|
-
onGoogleSignIn={onGoogleSignIn}
|
|
224
|
-
Link={MockLink}
|
|
225
|
-
translations={mockTranslations}
|
|
226
|
-
/>
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
const homeLink = screen.getByRole('link', { name: 'Back to Home' });
|
|
230
|
-
const learnMoreLink = screen.getByRole('link', { name: 'Learn More' });
|
|
231
|
-
const signUpLink = screen.getByRole('link', { name: 'Sign Up Instead' });
|
|
232
|
-
|
|
233
|
-
expect(homeLink).toHaveAttribute('href', '/');
|
|
234
|
-
expect(learnMoreLink).toHaveAttribute('href', '/about');
|
|
235
|
-
expect(signUpLink).toHaveAttribute('href', '/auth/signup');
|
|
236
|
-
});
|
|
237
|
-
|
|
238
218
|
it('should have keyboard-accessible credentials submit button', () => {
|
|
239
219
|
const onGoogleSignIn = vi.fn();
|
|
240
220
|
const onCredentialsSignIn = vi.fn();
|
|
@@ -273,22 +253,21 @@ describe('SignInForm - Accessibility', () => {
|
|
|
273
253
|
expect(button).toHaveAccessibleName(/Continue with Google/i);
|
|
274
254
|
});
|
|
275
255
|
|
|
276
|
-
it('should have accessible
|
|
256
|
+
it('should have accessible button names', () => {
|
|
277
257
|
const onGoogleSignIn = vi.fn();
|
|
278
258
|
|
|
279
259
|
render(
|
|
280
260
|
<SignInForm
|
|
281
261
|
onGoogleSignIn={onGoogleSignIn}
|
|
262
|
+
backendUrl="http://localhost:4000"
|
|
282
263
|
Link={MockLink}
|
|
283
264
|
translations={mockTranslations}
|
|
284
265
|
/>
|
|
285
266
|
);
|
|
286
267
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
links.forEach(link => {
|
|
291
|
-
expect(link).toHaveAccessibleName();
|
|
268
|
+
const buttons = screen.getAllByRole('button');
|
|
269
|
+
buttons.forEach(button => {
|
|
270
|
+
expect(button).toHaveAccessibleName();
|
|
292
271
|
});
|
|
293
272
|
});
|
|
294
273
|
});
|
|
@@ -410,23 +389,23 @@ describe('SignInForm - Accessibility', () => {
|
|
|
410
389
|
expect(button?.className).toBeTruthy();
|
|
411
390
|
});
|
|
412
391
|
|
|
413
|
-
it('should have visible focus indicators on
|
|
392
|
+
it('should have visible focus indicators on buttons', () => {
|
|
414
393
|
const onGoogleSignIn = vi.fn();
|
|
415
394
|
|
|
416
395
|
const { container } = render(
|
|
417
396
|
<SignInForm
|
|
418
397
|
onGoogleSignIn={onGoogleSignIn}
|
|
398
|
+
backendUrl="http://localhost:4000"
|
|
419
399
|
Link={MockLink}
|
|
420
400
|
translations={mockTranslations}
|
|
421
401
|
/>
|
|
422
402
|
);
|
|
423
403
|
|
|
424
|
-
const
|
|
404
|
+
const buttons = container.querySelectorAll('button');
|
|
425
405
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
expect(link.className).toBeTruthy();
|
|
406
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
407
|
+
buttons.forEach(button => {
|
|
408
|
+
expect(button.className).toBeTruthy();
|
|
430
409
|
});
|
|
431
410
|
});
|
|
432
411
|
});
|
|
@@ -305,7 +305,6 @@ export function SignInForm({
|
|
|
305
305
|
error,
|
|
306
306
|
showCredentialsAuth = false,
|
|
307
307
|
isLoading = false,
|
|
308
|
-
Link,
|
|
309
308
|
translations: t,
|
|
310
309
|
}: SignInFormProps) {
|
|
311
310
|
const handleGoogleClick = () => {
|
|
@@ -374,18 +373,6 @@ export function SignInForm({
|
|
|
374
373
|
)}
|
|
375
374
|
</div>
|
|
376
375
|
|
|
377
|
-
{/* Navigation Links */}
|
|
378
|
-
<div className="semiont-auth__links">
|
|
379
|
-
<Link href="/" className={buttonStyles.secondary.base}>
|
|
380
|
-
{t.backToHome}
|
|
381
|
-
</Link>
|
|
382
|
-
<Link href="/about" className={buttonStyles.secondary.base}>
|
|
383
|
-
{t.learnMore}
|
|
384
|
-
</Link>
|
|
385
|
-
<Link href="/auth/signup" className={buttonStyles.primary.base}>
|
|
386
|
-
{t.signUpInstead}
|
|
387
|
-
</Link>
|
|
388
|
-
</div>
|
|
389
376
|
</div>
|
|
390
377
|
</div>
|
|
391
378
|
</main>
|
|
@@ -90,6 +90,11 @@ export interface ResourceViewerPageProps {
|
|
|
90
90
|
* SSE attention stream connection status for the active workspace
|
|
91
91
|
*/
|
|
92
92
|
streamStatus: StreamStatus;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Name of the active knowledge base (for display in panels)
|
|
96
|
+
*/
|
|
97
|
+
knowledgeBaseName?: string | undefined;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
/**
|
|
@@ -128,6 +133,7 @@ export function ResourceViewerPage({
|
|
|
128
133
|
ToolbarPanels,
|
|
129
134
|
refetchDocument,
|
|
130
135
|
streamStatus,
|
|
136
|
+
knowledgeBaseName,
|
|
131
137
|
}: ResourceViewerPageProps) {
|
|
132
138
|
// Translations
|
|
133
139
|
const tw = useTranslations('ReferenceWizard');
|
|
@@ -628,6 +634,7 @@ export function ResourceViewerPage({
|
|
|
628
634
|
<CollaborationPanel
|
|
629
635
|
isConnected={streamStatus === 'connected'}
|
|
630
636
|
eventCount={0}
|
|
637
|
+
knowledgeBaseName={knowledgeBaseName}
|
|
631
638
|
/>
|
|
632
639
|
)}
|
|
633
640
|
|
|
@@ -401,4 +401,16 @@
|
|
|
401
401
|
|
|
402
402
|
.semiont-panel-actions--between {
|
|
403
403
|
justify-content: space-between;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Login form pulsing border for first-launch nudge */
|
|
407
|
+
@keyframes semiont-pulse-border {
|
|
408
|
+
0%, 100% { border-color: var(--semiont-color-primary-300, #93c5fd); }
|
|
409
|
+
50% { border-color: var(--semiont-color-primary-600, #2563eb); }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.semiont-panel__login-form--pulsing {
|
|
413
|
+
border: 2px solid var(--semiont-color-primary-400, #60a5fa);
|
|
414
|
+
border-radius: var(--semiont-panel-border-radius, 0.5rem);
|
|
415
|
+
animation: semiont-pulse-border 2s ease-in-out infinite;
|
|
404
416
|
}
|