@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.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/app/accessibility/CategoryFilter.tsx +123 -0
- package/app/accessibility/ConformanceTable.tsx +109 -0
- package/app/accessibility/StatusBadge.tsx +47 -0
- package/app/accessibility/[component]/page.tsx +166 -0
- package/app/accessibility/page.tsx +207 -0
- package/app/api/search/route.ts +241 -0
- package/app/components/[id]/page.tsx +316 -0
- package/app/edition-upgrade/page.tsx +76 -0
- package/app/guides/[id]/page.tsx +67 -0
- package/app/layout.tsx +93 -0
- package/app/page.tsx +29 -0
- package/components/branding/header.tsx +82 -0
- package/components/branding/logo.tsx +54 -0
- package/components/feedback/feedback-widget.tsx +263 -0
- package/components/live-example.tsx +477 -0
- package/components/mdx/edition.tsx +149 -0
- package/components/mdx-renderer.tsx +90 -0
- package/components/nav-sidebar.tsx +269 -0
- package/components/search/search-input.tsx +508 -0
- package/components/theme-init-script.tsx +35 -0
- package/components/theme-switcher.tsx +166 -0
- package/components/tokens-used.tsx +135 -0
- package/components/upgrade/upgrade-prompt.tsx +141 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +751 -0
- package/package.json +66 -0
- package/styles/globals.css +613 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 hypoth-org
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @hypoth-ui/docs-renderer-next
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Next.js-based documentation site renderer for the hypoth-ui design system. Renders MDX content packs with edition filtering, tenant branding, search, and live component previews.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @hypoth-ui/docs-renderer-next
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Note:** This is primarily an internal package used to build the documentation site.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Import Components
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { NavSidebar } from '@hypoth-ui/docs-renderer-next/components/nav-sidebar';
|
|
21
|
+
import { MdxRenderer } from '@hypoth-ui/docs-renderer-next/components/mdx-renderer';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Import Styles
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import '@hypoth-ui/docs-renderer-next/styles/globals.css';
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Run the Docs Site
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm --filter @ds/docs-app dev:core # Core edition
|
|
34
|
+
pnpm --filter @ds/docs-app dev:pro # Pro edition
|
|
35
|
+
pnpm --filter @ds/docs-app dev:enterprise # Enterprise edition
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
See the [main README](../../README.md) for full documentation and architecture overview.
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { CategoryInfo, ConformanceStatus } from "@hypoth-ui/docs-core";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
interface CategoryFilterProps {
|
|
7
|
+
categories: CategoryInfo[];
|
|
8
|
+
selectedCategory: string | null;
|
|
9
|
+
selectedStatus: ConformanceStatus | null;
|
|
10
|
+
onCategoryChange: (category: string | null) => void;
|
|
11
|
+
onStatusChange: (status: ConformanceStatus | null) => void;
|
|
12
|
+
onSearchChange: (query: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const STATUSES: Array<{ value: ConformanceStatus; label: string }> = [
|
|
16
|
+
{ value: "conformant", label: "Conformant" },
|
|
17
|
+
{ value: "partial", label: "Partial" },
|
|
18
|
+
{ value: "non-conformant", label: "Non-Conformant" },
|
|
19
|
+
{ value: "pending", label: "Pending" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export function CategoryFilter({
|
|
23
|
+
categories,
|
|
24
|
+
selectedCategory,
|
|
25
|
+
selectedStatus,
|
|
26
|
+
onCategoryChange,
|
|
27
|
+
onStatusChange,
|
|
28
|
+
onSearchChange,
|
|
29
|
+
}: CategoryFilterProps) {
|
|
30
|
+
const [searchValue, setSearchValue] = useState("");
|
|
31
|
+
|
|
32
|
+
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
33
|
+
const value = e.target.value;
|
|
34
|
+
setSearchValue(value);
|
|
35
|
+
onSearchChange(value);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="mb-6 space-y-4">
|
|
40
|
+
{/* Search */}
|
|
41
|
+
<div>
|
|
42
|
+
<label htmlFor="search" className="sr-only">
|
|
43
|
+
Search components
|
|
44
|
+
</label>
|
|
45
|
+
<input
|
|
46
|
+
type="search"
|
|
47
|
+
id="search"
|
|
48
|
+
placeholder="Search components..."
|
|
49
|
+
value={searchValue}
|
|
50
|
+
onChange={handleSearchChange}
|
|
51
|
+
className="w-full rounded-lg border border-gray-300 px-4 py-2 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{/* Filters */}
|
|
56
|
+
<div className="flex flex-wrap gap-4">
|
|
57
|
+
{/* Category filter */}
|
|
58
|
+
<div>
|
|
59
|
+
<label
|
|
60
|
+
htmlFor="category"
|
|
61
|
+
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
62
|
+
>
|
|
63
|
+
Category
|
|
64
|
+
</label>
|
|
65
|
+
<select
|
|
66
|
+
id="category"
|
|
67
|
+
value={selectedCategory ?? ""}
|
|
68
|
+
onChange={(e) => onCategoryChange(e.target.value || null)}
|
|
69
|
+
className="rounded-lg border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-800"
|
|
70
|
+
>
|
|
71
|
+
<option value="">All Categories</option>
|
|
72
|
+
{categories.map((cat) => (
|
|
73
|
+
<option key={cat.id} value={cat.id}>
|
|
74
|
+
{cat.name} ({cat.count})
|
|
75
|
+
</option>
|
|
76
|
+
))}
|
|
77
|
+
</select>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Status filter */}
|
|
81
|
+
<div>
|
|
82
|
+
<label
|
|
83
|
+
htmlFor="status"
|
|
84
|
+
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
85
|
+
>
|
|
86
|
+
Status
|
|
87
|
+
</label>
|
|
88
|
+
<select
|
|
89
|
+
id="status"
|
|
90
|
+
value={selectedStatus ?? ""}
|
|
91
|
+
onChange={(e) => onStatusChange((e.target.value as ConformanceStatus) || null)}
|
|
92
|
+
className="rounded-lg border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-800"
|
|
93
|
+
>
|
|
94
|
+
<option value="">All Statuses</option>
|
|
95
|
+
{STATUSES.map((status) => (
|
|
96
|
+
<option key={status.value} value={status.value}>
|
|
97
|
+
{status.label}
|
|
98
|
+
</option>
|
|
99
|
+
))}
|
|
100
|
+
</select>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* Clear filters */}
|
|
104
|
+
{(selectedCategory || selectedStatus || searchValue) && (
|
|
105
|
+
<div className="flex items-end">
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
onClick={() => {
|
|
109
|
+
onCategoryChange(null);
|
|
110
|
+
onStatusChange(null);
|
|
111
|
+
setSearchValue("");
|
|
112
|
+
onSearchChange("");
|
|
113
|
+
}}
|
|
114
|
+
className="rounded-lg px-3 py-2 text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
|
|
115
|
+
>
|
|
116
|
+
Clear filters
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentConformance } from "@hypoth-ui/docs-core";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { StatusBadge } from "./StatusBadge";
|
|
6
|
+
|
|
7
|
+
interface ConformanceTableProps {
|
|
8
|
+
components: ComponentConformance[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ConformanceTable({ components }: ConformanceTableProps) {
|
|
12
|
+
if (components.length === 0) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="rounded-lg border border-gray-200 bg-gray-50 p-8 text-center dark:border-gray-700 dark:bg-gray-800">
|
|
15
|
+
<p className="text-gray-600 dark:text-gray-400">No components match the current filters.</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700">
|
|
22
|
+
<table className="w-full">
|
|
23
|
+
<thead className="bg-gray-50 dark:bg-gray-800">
|
|
24
|
+
<tr>
|
|
25
|
+
<th
|
|
26
|
+
scope="col"
|
|
27
|
+
className="px-4 py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100"
|
|
28
|
+
>
|
|
29
|
+
Component
|
|
30
|
+
</th>
|
|
31
|
+
<th
|
|
32
|
+
scope="col"
|
|
33
|
+
className="px-4 py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100"
|
|
34
|
+
>
|
|
35
|
+
Category
|
|
36
|
+
</th>
|
|
37
|
+
<th
|
|
38
|
+
scope="col"
|
|
39
|
+
className="px-4 py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100"
|
|
40
|
+
>
|
|
41
|
+
Status
|
|
42
|
+
</th>
|
|
43
|
+
<th
|
|
44
|
+
scope="col"
|
|
45
|
+
className="px-4 py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100"
|
|
46
|
+
>
|
|
47
|
+
Automated
|
|
48
|
+
</th>
|
|
49
|
+
<th
|
|
50
|
+
scope="col"
|
|
51
|
+
className="px-4 py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100"
|
|
52
|
+
>
|
|
53
|
+
Manual Audit
|
|
54
|
+
</th>
|
|
55
|
+
<th
|
|
56
|
+
scope="col"
|
|
57
|
+
className="px-4 py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100"
|
|
58
|
+
>
|
|
59
|
+
Last Updated
|
|
60
|
+
</th>
|
|
61
|
+
</tr>
|
|
62
|
+
</thead>
|
|
63
|
+
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
64
|
+
{components.map((component) => (
|
|
65
|
+
<tr key={component.id} className="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
|
66
|
+
<td className="px-4 py-3">
|
|
67
|
+
<Link
|
|
68
|
+
href={`/accessibility/${component.id}`}
|
|
69
|
+
className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
|
70
|
+
>
|
|
71
|
+
{component.name}
|
|
72
|
+
</Link>
|
|
73
|
+
<div className="text-xs text-gray-500">{component.id}</div>
|
|
74
|
+
</td>
|
|
75
|
+
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
76
|
+
{component.category}
|
|
77
|
+
</td>
|
|
78
|
+
<td className="px-4 py-3">
|
|
79
|
+
<StatusBadge status={component.status} size="sm" />
|
|
80
|
+
</td>
|
|
81
|
+
<td className="px-4 py-3 text-sm">
|
|
82
|
+
{component.automatedPassed ? (
|
|
83
|
+
<span className="text-green-600 dark:text-green-400">✓ Passed</span>
|
|
84
|
+
) : (
|
|
85
|
+
<span className="text-red-600 dark:text-red-400">✗ Failed</span>
|
|
86
|
+
)}
|
|
87
|
+
</td>
|
|
88
|
+
<td className="px-4 py-3 text-sm">
|
|
89
|
+
{component.manualAuditComplete ? (
|
|
90
|
+
<span className="text-green-600 dark:text-green-400">
|
|
91
|
+
{component.passCount}/{(component.passCount ?? 0) + (component.failCount ?? 0)}{" "}
|
|
92
|
+
passed
|
|
93
|
+
</span>
|
|
94
|
+
) : (
|
|
95
|
+
<span className="text-gray-500 dark:text-gray-400">Not audited</span>
|
|
96
|
+
)}
|
|
97
|
+
</td>
|
|
98
|
+
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
|
99
|
+
{component.lastAuditDate
|
|
100
|
+
? new Date(component.lastAuditDate).toLocaleDateString()
|
|
101
|
+
: "—"}
|
|
102
|
+
</td>
|
|
103
|
+
</tr>
|
|
104
|
+
))}
|
|
105
|
+
</tbody>
|
|
106
|
+
</table>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ConformanceStatus } from "@hypoth-ui/docs-core";
|
|
4
|
+
|
|
5
|
+
interface StatusBadgeProps {
|
|
6
|
+
status: ConformanceStatus;
|
|
7
|
+
size?: "sm" | "md";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const STATUS_CONFIG: Record<ConformanceStatus, { icon: string; label: string; className: string }> =
|
|
11
|
+
{
|
|
12
|
+
conformant: {
|
|
13
|
+
icon: "✅",
|
|
14
|
+
label: "Conformant",
|
|
15
|
+
className: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
|
|
16
|
+
},
|
|
17
|
+
partial: {
|
|
18
|
+
icon: "⚠️",
|
|
19
|
+
label: "Partial",
|
|
20
|
+
className: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
|
|
21
|
+
},
|
|
22
|
+
"non-conformant": {
|
|
23
|
+
icon: "❌",
|
|
24
|
+
label: "Non-Conformant",
|
|
25
|
+
className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
|
|
26
|
+
},
|
|
27
|
+
pending: {
|
|
28
|
+
icon: "⏳",
|
|
29
|
+
label: "Pending Audit",
|
|
30
|
+
className: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function StatusBadge({ status, size = "md" }: StatusBadgeProps) {
|
|
35
|
+
const config = STATUS_CONFIG[status] ?? STATUS_CONFIG.pending;
|
|
36
|
+
|
|
37
|
+
const sizeClasses = size === "sm" ? "px-2 py-0.5 text-xs" : "px-3 py-1 text-sm";
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<span
|
|
41
|
+
className={`inline-flex items-center gap-1.5 rounded-full font-medium ${config.className} ${sizeClasses}`}
|
|
42
|
+
>
|
|
43
|
+
<span aria-hidden="true">{config.icon}</span>
|
|
44
|
+
<span>{config.label}</span>
|
|
45
|
+
</span>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentConformance } from "@hypoth-ui/docs-core";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { use } from "react";
|
|
6
|
+
import { StatusBadge } from "../StatusBadge";
|
|
7
|
+
|
|
8
|
+
// Placeholder component data
|
|
9
|
+
const COMPONENT_DATA: Record<string, ComponentConformance> = {
|
|
10
|
+
"ds-button": {
|
|
11
|
+
id: "ds-button",
|
|
12
|
+
name: "Button",
|
|
13
|
+
category: "form-controls",
|
|
14
|
+
status: "conformant",
|
|
15
|
+
wcagLevel: "AA",
|
|
16
|
+
lastAuditDate: "2026-01-04T10:00:00Z",
|
|
17
|
+
lastAuditor: "auditor@example.com",
|
|
18
|
+
automatedPassed: true,
|
|
19
|
+
manualAuditComplete: true,
|
|
20
|
+
passCount: 10,
|
|
21
|
+
failCount: 0,
|
|
22
|
+
exceptionCount: 0,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface ComponentPageProps {
|
|
27
|
+
params: Promise<{
|
|
28
|
+
component: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function ComponentDetailPage({ params }: ComponentPageProps) {
|
|
33
|
+
const { component: componentId } = use(params);
|
|
34
|
+
const component = COMPONENT_DATA[componentId];
|
|
35
|
+
|
|
36
|
+
if (!component) {
|
|
37
|
+
return (
|
|
38
|
+
<div className="max-w-4xl mx-auto px-4 py-8">
|
|
39
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">
|
|
40
|
+
Component Not Found
|
|
41
|
+
</h1>
|
|
42
|
+
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
43
|
+
No accessibility data found for component: {componentId}
|
|
44
|
+
</p>
|
|
45
|
+
<Link
|
|
46
|
+
href="/accessibility"
|
|
47
|
+
className="text-blue-600 hover:text-blue-800 dark:text-blue-400"
|
|
48
|
+
>
|
|
49
|
+
← Back to Accessibility Conformance
|
|
50
|
+
</Link>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="max-w-4xl mx-auto px-4 py-8">
|
|
57
|
+
{/* Breadcrumb */}
|
|
58
|
+
<nav className="mb-6 text-sm">
|
|
59
|
+
<Link
|
|
60
|
+
href="/accessibility"
|
|
61
|
+
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
|
62
|
+
>
|
|
63
|
+
Accessibility Conformance
|
|
64
|
+
</Link>
|
|
65
|
+
<span className="mx-2 text-gray-400">/</span>
|
|
66
|
+
<span className="text-gray-900 dark:text-gray-100">{component.name}</span>
|
|
67
|
+
</nav>
|
|
68
|
+
|
|
69
|
+
{/* Header */}
|
|
70
|
+
<header className="mb-8">
|
|
71
|
+
<div className="flex items-center gap-4 mb-2">
|
|
72
|
+
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">{component.name}</h1>
|
|
73
|
+
<StatusBadge status={component.status} />
|
|
74
|
+
</div>
|
|
75
|
+
<p className="text-gray-600 dark:text-gray-400">
|
|
76
|
+
{component.id} • {component.category}
|
|
77
|
+
</p>
|
|
78
|
+
</header>
|
|
79
|
+
|
|
80
|
+
{/* Status summary */}
|
|
81
|
+
<section className="mb-8 grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
82
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
|
83
|
+
<div className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
84
|
+
{component.automatedPassed ? "✓ Passed" : "✗ Failed"}
|
|
85
|
+
</div>
|
|
86
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Automated Tests</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
|
89
|
+
<div className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
90
|
+
{component.manualAuditComplete ? "Complete" : "Pending"}
|
|
91
|
+
</div>
|
|
92
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Manual Audit</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
|
95
|
+
<div className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
96
|
+
WCAG {component.wcagLevel}
|
|
97
|
+
</div>
|
|
98
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Target Level</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
|
101
|
+
<div className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
102
|
+
{component.lastAuditDate ? new Date(component.lastAuditDate).toLocaleDateString() : "—"}
|
|
103
|
+
</div>
|
|
104
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Last Audit</div>
|
|
105
|
+
</div>
|
|
106
|
+
</section>
|
|
107
|
+
|
|
108
|
+
{/* Manual audit results */}
|
|
109
|
+
{component.manualAuditComplete && (
|
|
110
|
+
<section className="mb-8">
|
|
111
|
+
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
112
|
+
Manual Audit Results
|
|
113
|
+
</h2>
|
|
114
|
+
<div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
|
|
115
|
+
<div className="grid grid-cols-3 gap-4 text-center">
|
|
116
|
+
<div>
|
|
117
|
+
<div className="text-2xl font-bold text-green-600 dark:text-green-400">
|
|
118
|
+
{component.passCount ?? 0}
|
|
119
|
+
</div>
|
|
120
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Passed</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div>
|
|
123
|
+
<div className="text-2xl font-bold text-red-600 dark:text-red-400">
|
|
124
|
+
{component.failCount ?? 0}
|
|
125
|
+
</div>
|
|
126
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Failed</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
<div className="text-2xl font-bold text-yellow-600 dark:text-yellow-400">
|
|
130
|
+
{component.exceptionCount ?? 0}
|
|
131
|
+
</div>
|
|
132
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">Exceptions</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
{component.lastAuditor && (
|
|
136
|
+
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-400">
|
|
137
|
+
Audited by: {component.lastAuditor}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</section>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{/* Component documentation link */}
|
|
145
|
+
<section className="mb-8">
|
|
146
|
+
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
147
|
+
Documentation
|
|
148
|
+
</h2>
|
|
149
|
+
<Link
|
|
150
|
+
href={`/components/${component.id.replace("ds-", "")}`}
|
|
151
|
+
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 dark:text-blue-400"
|
|
152
|
+
>
|
|
153
|
+
View {component.name} component documentation →
|
|
154
|
+
</Link>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
{/* Back link */}
|
|
158
|
+
<Link
|
|
159
|
+
href="/accessibility"
|
|
160
|
+
className="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
|
|
161
|
+
>
|
|
162
|
+
← Back to Accessibility Conformance
|
|
163
|
+
</Link>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|