@fragments-sdk/cli 0.9.0 → 0.10.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 (166) hide show
  1. package/dist/bin.d.ts +1 -0
  2. package/dist/bin.js +502 -84
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-CJEGT3WD.js → chunk-566BNPQZ.js} +21 -6
  5. package/dist/chunk-566BNPQZ.js.map +1 -0
  6. package/dist/{chunk-WI6SLMSO.js → chunk-CAMXG5HJ.js} +5 -5
  7. package/dist/chunk-D2CDBRNU.js +2 -0
  8. package/dist/{chunk-YMPGYEWK.js → chunk-D5PYOXEI.js} +2 -2
  9. package/dist/{chunk-NGIMCIK2.js → chunk-OQO55NKV.js} +405 -34
  10. package/dist/chunk-OQO55NKV.js.map +1 -0
  11. package/dist/{chunk-TOIE7VXF.js → chunk-PW7QTQA6.js} +2 -2
  12. package/dist/{chunk-AWYCDRPG.js → chunk-WXSR2II7.js} +2 -2
  13. package/dist/chunk-WXSR2II7.js.map +1 -0
  14. package/dist/{chunk-2JIKCJX3.js → chunk-ZDA3PLQ6.js} +17 -14
  15. package/dist/chunk-ZDA3PLQ6.js.map +1 -0
  16. package/dist/core/index.d.ts +1 -2092
  17. package/dist/core/index.js +26 -21
  18. package/dist/{discovery-Z4RDDFVR.js → discovery-NEOY4MPN.js} +3 -3
  19. package/dist/generate-BGKTKO6E.js +459 -0
  20. package/dist/generate-BGKTKO6E.js.map +1 -0
  21. package/dist/index.d.ts +3 -5
  22. package/dist/index.js +7 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/{init-KSAAS7X3.js → init-Q53R5Q2T.js} +66 -76
  25. package/dist/init-Q53R5Q2T.js.map +1 -0
  26. package/dist/mcp-bin.js +5 -7
  27. package/dist/mcp-bin.js.map +1 -1
  28. package/dist/scan-OQU7M4GH.js +14 -0
  29. package/dist/scan-generate-T5QNUG7N.js +691 -0
  30. package/dist/scan-generate-T5QNUG7N.js.map +1 -0
  31. package/dist/{service-A5GIGGGK.js → service-TQYWY65E.js} +4 -5
  32. package/dist/{static-viewer-NSODM5VX.js → static-viewer-NUBFPKWH.js} +4 -5
  33. package/dist/static-viewer-NUBFPKWH.js.map +1 -0
  34. package/dist/{test-RPWZAYSJ.js → test-2CSOSS3B.js} +4 -5
  35. package/dist/{test-RPWZAYSJ.js.map → test-2CSOSS3B.js.map} +1 -1
  36. package/dist/{tokens-NIXSZRX7.js → tokens-DXEGYTOJ.js} +6 -7
  37. package/dist/{tokens-NIXSZRX7.js.map → tokens-DXEGYTOJ.js.map} +1 -1
  38. package/dist/{viewer-SBTJDMP7.js → viewer-DBEPYM3G.js} +245 -23
  39. package/dist/viewer-DBEPYM3G.js.map +1 -0
  40. package/package.json +2 -1
  41. package/src/bin.ts +33 -1
  42. package/src/build.ts +13 -3
  43. package/src/commands/__tests__/scan-generate.test.ts +308 -0
  44. package/src/commands/build.ts +16 -2
  45. package/src/commands/generate.ts +383 -68
  46. package/src/commands/init.ts +81 -56
  47. package/src/commands/perf.ts +1 -1
  48. package/src/commands/scan-generate.ts +1013 -0
  49. package/src/commands/setup.ts +499 -0
  50. package/src/core/auto-props.ts +1 -1
  51. package/src/core/bundle-measurer.ts +2 -2
  52. package/src/core/config.ts +16 -4
  53. package/src/core/discovery.ts +2 -2
  54. package/src/core/generators/context.ts +1 -1
  55. package/src/core/generators/registry.ts +3 -3
  56. package/src/core/generators/typescript-extractor.ts +11 -1
  57. package/src/core/graph-extractor.ts +1 -1
  58. package/src/core/index.ts +3 -190
  59. package/src/core/loader.ts +2 -2
  60. package/src/core/parser.ts +1 -1
  61. package/src/core/previewLoader.ts +1 -1
  62. package/src/index.ts +2 -2
  63. package/src/migrate/converter.ts +9 -1
  64. package/src/migrate/parser.ts +2 -0
  65. package/src/migrate/types.ts +2 -0
  66. package/src/service/snippet-validation.test.ts +1 -1
  67. package/src/service/snippet-validation.ts +2 -2
  68. package/src/setup.ts +69 -24
  69. package/src/viewer/__tests__/viewer-integration.test.ts +4 -10
  70. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  71. package/src/viewer/components/ActionsPanel.tsx +31 -29
  72. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  73. package/src/viewer/components/App.tsx +187 -740
  74. package/src/viewer/components/BottomPanel.tsx +228 -132
  75. package/src/viewer/components/CodePanel.tsx +1 -1
  76. package/src/viewer/components/CommandPalette.tsx +7 -10
  77. package/src/viewer/components/ComponentDocView.tsx +164 -0
  78. package/src/viewer/components/ComponentGraph.tsx +111 -142
  79. package/src/viewer/components/ContractPanel.tsx +6 -6
  80. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  81. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  82. package/src/viewer/components/FragmentEditor.tsx +92 -115
  83. package/src/viewer/components/HeaderSearch.tsx +24 -0
  84. package/src/viewer/components/HealthDashboard.tsx +16 -2
  85. package/src/viewer/components/Icons.tsx +9 -0
  86. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  87. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  88. package/src/viewer/components/LandingPage.tsx +3 -3
  89. package/src/viewer/components/LeftSidebar.tsx +141 -63
  90. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  91. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  92. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  93. package/src/viewer/components/PanelShell.tsx +161 -0
  94. package/src/viewer/components/PerformancePanel.tsx +31 -28
  95. package/src/viewer/components/PreviewArea.tsx +1 -1
  96. package/src/viewer/components/PreviewAside.tsx +168 -0
  97. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  98. package/src/viewer/components/PropsEditor.tsx +70 -156
  99. package/src/viewer/components/ResizablePanel.tsx +103 -263
  100. package/src/viewer/components/RightSidebar.tsx +3 -9
  101. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  102. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  103. package/src/viewer/components/TopToolbar.tsx +159 -0
  104. package/src/viewer/components/VariantMatrix.tsx +42 -86
  105. package/src/viewer/components/VariantTabs.tsx +3 -3
  106. package/src/viewer/components/ViewerHeader.tsx +69 -0
  107. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  108. package/src/viewer/components/viewer-utils.ts +16 -0
  109. package/src/viewer/entry.tsx +5 -0
  110. package/src/viewer/hooks/useAppState.ts +27 -4
  111. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  112. package/src/viewer/preview-frame.html +6 -12
  113. package/src/viewer/server.ts +169 -2
  114. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  115. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  116. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  117. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
  118. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  119. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
  120. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  121. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  122. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  123. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +114 -0
  124. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  125. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  126. package/src/viewer/vendor/shared/src/index.ts +8 -0
  127. package/src/viewer/vendor/shared/src/types.ts +12 -0
  128. package/src/viewer/vite-plugin.ts +109 -4
  129. package/dist/chunk-2JIKCJX3.js.map +0 -1
  130. package/dist/chunk-AWYCDRPG.js.map +0 -1
  131. package/dist/chunk-CJEGT3WD.js.map +0 -1
  132. package/dist/chunk-EKLMXTWU.js +0 -80
  133. package/dist/chunk-EKLMXTWU.js.map +0 -1
  134. package/dist/chunk-GOVI6COW.js +0 -195
  135. package/dist/chunk-GOVI6COW.js.map +0 -1
  136. package/dist/chunk-NGIMCIK2.js.map +0 -1
  137. package/dist/defineFragment-D0UTve-I.d.ts +0 -665
  138. package/dist/generate-35OIMW4Y.js +0 -252
  139. package/dist/generate-35OIMW4Y.js.map +0 -1
  140. package/dist/init-KSAAS7X3.js.map +0 -1
  141. package/dist/scan-65RH3QMM.js +0 -15
  142. package/dist/viewer-SBTJDMP7.js.map +0 -1
  143. package/src/core/__tests__/preview-runtime.test.tsx +0 -111
  144. package/src/core/composition.test.ts +0 -262
  145. package/src/core/composition.ts +0 -318
  146. package/src/core/constants.ts +0 -114
  147. package/src/core/context.ts +0 -2
  148. package/src/core/defineFragment.ts +0 -141
  149. package/src/core/figma.ts +0 -263
  150. package/src/core/fragment-types.ts +0 -214
  151. package/src/core/performance-presets.ts +0 -142
  152. package/src/core/preview-runtime.tsx +0 -144
  153. package/src/core/schema.ts +0 -221
  154. package/src/core/storyAdapter.test.ts +0 -571
  155. package/src/core/storyAdapter.ts +0 -761
  156. package/src/core/storybook-csf.ts +0 -11
  157. package/src/core/token-parser.ts +0 -321
  158. package/src/core/token-types.ts +0 -287
  159. package/src/core/types.ts +0 -762
  160. /package/dist/{chunk-WI6SLMSO.js.map → chunk-CAMXG5HJ.js.map} +0 -0
  161. /package/dist/{discovery-Z4RDDFVR.js.map → chunk-D2CDBRNU.js.map} +0 -0
  162. /package/dist/{chunk-YMPGYEWK.js.map → chunk-D5PYOXEI.js.map} +0 -0
  163. /package/dist/{chunk-TOIE7VXF.js.map → chunk-PW7QTQA6.js.map} +0 -0
  164. /package/dist/{scan-65RH3QMM.js.map → discovery-NEOY4MPN.js.map} +0 -0
  165. /package/dist/{service-A5GIGGGK.js.map → scan-OQU7M4GH.js.map} +0 -0
  166. /package/dist/{static-viewer-NSODM5VX.js.map → service-TQYWY65E.js.map} +0 -0
@@ -11,7 +11,9 @@
11
11
 
12
12
  import { useState, useEffect, useCallback, useMemo, useRef } from "react";
13
13
  import type { Result, NodeResult, ImpactValue, RunOptions } from "axe-core";
14
- import { Badge, Tabs, Dialog, Card, Alert, Text, Stack, Box, Button, Chip, EmptyState } from "@fragments-sdk/ui";
14
+ import { Badge, Tabs, Dialog, Card, Alert, Text, Stack, Box, Button, Chip, EmptyState, Input } from "@fragments-sdk/ui";
15
+ import { ShieldCheck, XCircle, Wheelchair } from "@phosphor-icons/react";
16
+ import { PanelShell } from "./PanelShell.js";
15
17
  import { BRAND } from "../../core/index.js";
16
18
  import {
17
19
  updateComponentA11yResult,
@@ -577,163 +579,178 @@ export function AccessibilityPanel({
577
579
 
578
580
  // Check if currently re-scanning due to HMR
579
581
  const isReScanning = isScanning && results !== null;
582
+ const isInitialScan = isScanning && !results;
583
+
584
+ // Determine empty config for error / no-results states
585
+ const emptyConfig = (() => {
586
+ if (!isInitialScan && error) {
587
+ return {
588
+ icon: <XCircle size={24} weight="regular" style={{ color: 'var(--color-danger)' }} />,
589
+ title: "Scan failed",
590
+ description: error,
591
+ action: <Button variant="secondary" size="sm" onClick={runScan}>Retry Scan</Button>,
592
+ };
593
+ }
594
+ if (!isInitialScan && !error && !results) {
595
+ return {
596
+ icon: <Wheelchair size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
597
+ title: "No scan results",
598
+ description: "Run an accessibility scan to check this component for WCAG violations.",
599
+ action: <Button size="sm" onClick={runScan}>Run Scan</Button>,
600
+ };
601
+ }
602
+ return undefined;
603
+ })();
580
604
 
581
- // Render loading state
582
- if (isScanning && !results) {
583
- return (
584
- <Stack align="center" justify="center" style={{ padding: 32 }}>
585
- <LoadingIcon style={{ width: 24, height: 24, animation: 'spin 1s linear infinite', marginBottom: 8 }} />
605
+ // Custom loading skeleton for accessibility scan
606
+ const loadingSkeleton = (
607
+ <Stack gap="md">
608
+ <Box paddingX="md" paddingY="xs" background="secondary" rounded="md">
609
+ <Stack direction="row" align="center" gap="md">
610
+ {[0, 1, 2].map((i) => (
611
+ <div key={i} style={{ width: '90px', height: '28px', borderRadius: '6px', background: 'var(--bg-hover)', animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', animationDelay: `${i * 100}ms` }} />
612
+ ))}
613
+ </Stack>
614
+ </Box>
615
+ {[0, 1, 2].map((i) => (
616
+ <Card key={i}>
617
+ <Box padding="sm">
618
+ <Stack direction="row" align="center" gap="sm">
619
+ <div style={{ width: '16px', height: '16px', borderRadius: '4px', background: 'var(--bg-hover)', animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', animationDelay: `${i * 150}ms` }} />
620
+ <div style={{ width: '50px', height: '18px', borderRadius: '9px', background: 'var(--bg-hover)', animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', animationDelay: `${i * 150 + 50}ms` }} />
621
+ <div style={{ flex: 1, height: '14px', borderRadius: '4px', background: 'var(--bg-hover)', animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', animationDelay: `${i * 150 + 100}ms` }} />
622
+ <div style={{ width: '60px', height: '18px', borderRadius: '9px', background: 'var(--bg-hover)', animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', animationDelay: `${i * 150 + 150}ms` }} />
623
+ </Stack>
624
+ </Box>
625
+ </Card>
626
+ ))}
627
+ <Stack direction="row" align="center" justify="center" gap="xs">
628
+ <LoadingIcon style={{ width: 14, height: 14, animation: 'spin 1s linear infinite' }} />
586
629
  <Text size="xs" color="tertiary">Running accessibility checks...</Text>
587
630
  </Stack>
588
- );
589
- }
590
-
591
- // Render error state
592
- if (error) {
593
- return (
594
- <Stack align="center" justify="center" style={{ padding: 32 }}>
595
- <XIcon style={{ width: 24, height: 24, color: 'var(--color-danger)', marginBottom: 8 }} />
596
- <Text size="xs" color="secondary" style={{ color: 'var(--color-danger)', marginBottom: 8 }}>{error}</Text>
597
- <Button variant="ghost" size="sm" onClick={runScan}>Retry Scan</Button>
598
- </Stack>
599
- );
600
- }
601
-
602
- // Render no results state
603
- if (!results) {
604
- return (
605
- <Stack align="center" justify="center" style={{ padding: 32 }}>
606
- <AccessibilityIcon style={{ width: 32, height: 32, marginBottom: 8, opacity: 0.5, color: 'var(--text-tertiary)' }} />
607
- <Text size="xs" color="tertiary" style={{ marginBottom: 8 }}>No accessibility scan results</Text>
608
- <Button size="sm" onClick={runScan}>Run Scan</Button>
609
- </Stack>
610
- );
611
- }
631
+ </Stack>
632
+ );
612
633
 
613
- return (
614
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
615
- {/* Tabs header with actions */}
616
- <Stack direction="row" align="center" gap="xs" style={{
617
- padding: '8px 16px',
618
- borderBottom: '1px solid var(--border)',
619
- background: 'var(--bg-secondary)',
620
- flexShrink: 0,
621
- }}>
622
- <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as TabType)}>
623
- <Tabs.List>
624
- <Tabs.Tab value="violations">
625
- <span style={{ color: 'var(--color-danger)' }}>{counts.violations}</span> Violations
626
- </Tabs.Tab>
627
- <Tabs.Tab value="passes">
628
- <span style={{ color: 'var(--color-success)' }}>{counts.passes}</span> Passes
629
- </Tabs.Tab>
630
- <Tabs.Tab value="incomplete">
631
- <span style={{ color: 'var(--color-warning)' }}>{counts.incomplete}</span> Incomplete
632
- </Tabs.Tab>
633
- </Tabs.List>
634
- </Tabs>
635
-
636
- <div style={{ flex: 1 }} />
637
-
638
- {/* Re-scanning indicator */}
639
- {isReScanning && (
640
- <Badge variant="warning" size="sm">
641
- <Stack direction="row" align="center" gap="xs">
642
- <LoadingIcon style={{ width: 12, height: 12, animation: 'spin 1s linear infinite' }} />
643
- Re-scanning...
644
- </Stack>
645
- </Badge>
646
- )}
647
-
648
- {/* Rescan button */}
649
- <Button
650
- variant="ghost"
651
- size="sm"
652
- onClick={runScan}
653
- disabled={isScanning}
654
- title="Re-run accessibility scan"
655
- >
656
- <LoadingIcon
657
- style={{
658
- width: 12,
659
- height: 12,
660
- ...(isScanning ? { animation: 'spin 1s linear infinite' } : {}),
661
- }}
662
- />
663
- {isScanning ? "Scanning..." : "Rescan"}
664
- </Button>
665
-
666
- {/* AI Setup button */}
667
- <Button
668
- variant={aiConfig ? "secondary" : "ghost"}
669
- size="sm"
670
- onClick={() => setShowAISetup(true)}
671
- title={aiConfig ? "AI fixes enabled" : "Configure AI for fix suggestions"}
672
- >
673
- <WandIcon style={{ width: 12, height: 12 }} />
674
- {aiConfig ? "AI Ready" : "Setup AI"}
675
- </Button>
676
- </Stack>
634
+ // Toolbar with tabs, rescan badge, and action buttons (only when results exist)
635
+ const toolbar = results && !error ? (
636
+ <Stack direction="row" align="center" gap="xs" style={{ width: '100%' }}>
637
+ <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as TabType)}>
638
+ <Tabs.List>
639
+ <Tabs.Tab value="violations">
640
+ <span style={{ color: 'var(--color-danger)' }}>{counts.violations}</span> Violations
641
+ </Tabs.Tab>
642
+ <Tabs.Tab value="passes">
643
+ <span style={{ color: 'var(--color-success)' }}>{counts.passes}</span> Passes
644
+ </Tabs.Tab>
645
+ <Tabs.Tab value="incomplete">
646
+ <span style={{ color: 'var(--color-warning)' }}>{counts.incomplete}</span> Incomplete
647
+ </Tabs.Tab>
648
+ </Tabs.List>
649
+ </Tabs>
650
+
651
+ <Box style={{ flex: 1 }} />
652
+
653
+ {isReScanning && (
654
+ <Badge variant="warning" size="sm">
655
+ <Stack direction="row" align="center" gap="xs">
656
+ <LoadingIcon style={{ width: 12, height: 12, animation: 'spin 1s linear infinite' }} />
657
+ Re-scanning...
658
+ </Stack>
659
+ </Badge>
660
+ )}
661
+
662
+ <Button
663
+ variant="ghost"
664
+ size="sm"
665
+ onClick={runScan}
666
+ disabled={isScanning}
667
+ title="Re-run accessibility scan"
668
+ >
669
+ <LoadingIcon
670
+ style={{
671
+ width: 12,
672
+ height: 12,
673
+ ...(isScanning ? { animation: 'spin 1s linear infinite' } : {}),
674
+ }}
675
+ />
676
+ {isScanning ? "Scanning..." : "Rescan"}
677
+ </Button>
678
+
679
+ <Button
680
+ variant={aiConfig ? "secondary" : "ghost"}
681
+ size="sm"
682
+ onClick={() => setShowAISetup(true)}
683
+ title={aiConfig ? "AI fixes enabled" : "Configure AI for fix suggestions"}
684
+ >
685
+ <WandIcon style={{ width: 12, height: 12 }} />
686
+ {aiConfig ? "AI Ready" : "Setup AI"}
687
+ </Button>
688
+ </Stack>
689
+ ) : undefined;
677
690
 
678
- {/* Content */}
679
- <div style={{ flex: 1, overflowY: 'auto', padding: 16 }}>
680
- {activeTab === "violations" && (
681
- <>
682
- {counts.violations === 0 ? (
683
- <SuccessMessage message="No accessibility violations found." />
684
- ) : (
685
- <EnhancedRuleList
686
- rules={sortedViolations}
687
- expandedRules={expandedRules}
688
- onToggleRule={toggleRule}
689
- onHighlight={highlightElement}
690
- highlightedElement={highlightedElement}
691
- onGenerateFix={generateAIFix}
692
- generatingFix={generatingFix}
693
- aiFixes={aiFixes}
694
- aiEnabled={!!aiConfig}
695
- onCopyFix={copyFix}
696
- copiedFix={copiedFix}
697
- type="violation"
698
- />
699
- )}
700
- </>
701
- )}
702
-
703
- {activeTab === "passes" && (
704
- <>
705
- {counts.passes === 0 ? (
706
- <EmptyMessage message="No passing checks to display." />
707
- ) : (
708
- <RuleList
709
- rules={results.passes}
710
- expandedRules={expandedRules}
711
- onToggleRule={toggleRule}
712
- onHighlight={highlightElement}
713
- highlightedElement={highlightedElement}
714
- type="pass"
715
- />
716
- )}
717
- </>
718
- )}
719
-
720
- {activeTab === "incomplete" && (
721
- <>
722
- {counts.incomplete === 0 ? (
723
- <EmptyMessage message="No incomplete checks requiring manual review." />
724
- ) : (
725
- <RuleList
726
- rules={results.incomplete}
727
- expandedRules={expandedRules}
728
- onToggleRule={toggleRule}
729
- onHighlight={highlightElement}
730
- highlightedElement={highlightedElement}
731
- type="incomplete"
732
- />
733
- )}
734
- </>
735
- )}
736
- </div>
691
+ return (
692
+ <PanelShell
693
+ loading={isInitialScan}
694
+ loadingContent={loadingSkeleton}
695
+ toolbar={toolbar}
696
+ empty={emptyConfig}
697
+ >
698
+ {activeTab === "violations" && (
699
+ <>
700
+ {counts.violations === 0 ? (
701
+ <SuccessMessage message="No accessibility violations found." />
702
+ ) : (
703
+ <EnhancedRuleList
704
+ rules={sortedViolations}
705
+ expandedRules={expandedRules}
706
+ onToggleRule={toggleRule}
707
+ onHighlight={highlightElement}
708
+ highlightedElement={highlightedElement}
709
+ onGenerateFix={generateAIFix}
710
+ generatingFix={generatingFix}
711
+ aiFixes={aiFixes}
712
+ aiEnabled={!!aiConfig}
713
+ onCopyFix={copyFix}
714
+ copiedFix={copiedFix}
715
+ type="violation"
716
+ />
717
+ )}
718
+ </>
719
+ )}
720
+
721
+ {activeTab === "passes" && (
722
+ <>
723
+ {counts.passes === 0 ? (
724
+ <EmptyMessage message="No passing checks to display." />
725
+ ) : (
726
+ <RuleList
727
+ rules={results.passes}
728
+ expandedRules={expandedRules}
729
+ onToggleRule={toggleRule}
730
+ onHighlight={highlightElement}
731
+ highlightedElement={highlightedElement}
732
+ type="pass"
733
+ />
734
+ )}
735
+ </>
736
+ )}
737
+
738
+ {activeTab === "incomplete" && (
739
+ <>
740
+ {counts.incomplete === 0 ? (
741
+ <EmptyMessage message="No incomplete checks requiring manual review." />
742
+ ) : (
743
+ <RuleList
744
+ rules={results.incomplete}
745
+ expandedRules={expandedRules}
746
+ onToggleRule={toggleRule}
747
+ onHighlight={highlightElement}
748
+ highlightedElement={highlightedElement}
749
+ type="incomplete"
750
+ />
751
+ )}
752
+ </>
753
+ )}
737
754
 
738
755
  {/* AI Setup Dialog */}
739
756
  <Dialog open={showAISetup} onOpenChange={(open) => !open && setShowAISetup(false)}>
@@ -745,7 +762,7 @@ export function AccessibilityPanel({
745
762
  />
746
763
  </Dialog.Content>
747
764
  </Dialog>
748
- </div>
765
+ </PanelShell>
749
766
  );
750
767
  }
751
768
 
@@ -790,56 +807,49 @@ function EnhancedRuleList({
790
807
  return (
791
808
  <Card key={rule.id} style={{ borderLeft: `4px solid ${impactColors.borderLeft}` }}>
792
809
  {/* Rule Header */}
793
- <button
810
+ <Button
811
+ variant="ghost"
812
+ fullWidth
794
813
  onClick={() => onToggleRule(rule.id)}
795
- style={{
796
- width: '100%',
797
- display: 'flex',
798
- alignItems: 'center',
799
- gap: 8,
800
- padding: '10px 12px',
801
- textAlign: 'left',
802
- background: 'none',
803
- border: 'none',
804
- cursor: 'pointer',
805
- transition: 'background var(--transition-fast)',
806
- }}
814
+ style={{ justifyContent: 'flex-start', padding: '10px 12px' }}
807
815
  >
808
- {isExpanded ? (
809
- <ChevronDownIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
810
- ) : (
811
- <ChevronRightIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
812
- )}
813
-
814
- {/* Impact badge */}
815
- {rule.impact && (
816
- <Badge variant={impactColors.variant} size="sm">
817
- {rule.impact}
818
- </Badge>
819
- )}
820
-
821
- {/* Rule ID */}
822
- <Text font="mono" size="xs" color="tertiary">{rule.id}</Text>
823
-
824
- {/* Rule description */}
825
- <Text size="xs" style={{
826
- flex: 1,
827
- overflow: 'hidden',
828
- textOverflow: 'ellipsis',
829
- whiteSpace: 'nowrap',
830
- }}>
831
- {rule.description}
832
- </Text>
816
+ <Stack direction="row" align="center" gap="sm" style={{ width: '100%' }}>
817
+ {isExpanded ? (
818
+ <ChevronDownIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
819
+ ) : (
820
+ <ChevronRightIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
821
+ )}
833
822
 
834
- {/* Element count */}
835
- <Badge size="sm">
836
- {rule.nodes.length} element{rule.nodes.length !== 1 ? "s" : ""}
837
- </Badge>
838
- </button>
823
+ {/* Impact badge */}
824
+ {rule.impact && (
825
+ <Badge variant={impactColors.variant} size="sm">
826
+ {rule.impact}
827
+ </Badge>
828
+ )}
829
+
830
+ {/* Rule ID */}
831
+ <Text font="mono" size="xs" color="tertiary">{rule.id}</Text>
832
+
833
+ {/* Rule description */}
834
+ <Text size="xs" style={{
835
+ flex: 1,
836
+ overflow: 'hidden',
837
+ textOverflow: 'ellipsis',
838
+ whiteSpace: 'nowrap',
839
+ }}>
840
+ {rule.description}
841
+ </Text>
842
+
843
+ {/* Element count */}
844
+ <Badge size="sm">
845
+ {rule.nodes.length} element{rule.nodes.length !== 1 ? "s" : ""}
846
+ </Badge>
847
+ </Stack>
848
+ </Button>
839
849
 
840
850
  {/* Expanded content */}
841
851
  {isExpanded && (
842
- <div style={{ borderTop: '1px solid var(--border)', background: 'var(--bg-secondary)' }}>
852
+ <Box borderTop background="secondary">
843
853
  {/* Why it matters + WCAG reference */}
844
854
  {staticFix && (
845
855
  <Box padding="sm" style={{ borderBottom: '1px solid var(--border-subtle)' }}>
@@ -863,40 +873,31 @@ function EnhancedRuleList({
863
873
  WCAG {staticFix.wcagCriterion.id} Level {staticFix.wcagCriterion.level}
864
874
  </Badge>
865
875
  <Text size="xs" color="secondary">{staticFix.wcagCriterion.name}</Text>
866
- <a
876
+ <Button
877
+ as="a"
878
+ variant="ghost"
879
+ size="sm"
867
880
  href={staticFix.wcagCriterion.url}
868
881
  target="_blank"
869
882
  rel="noopener noreferrer"
870
- style={{
871
- fontSize: 12,
872
- color: 'var(--color-accent)',
873
- marginLeft: 'auto',
874
- textDecoration: 'none',
875
- }}
883
+ style={{ marginLeft: 'auto', fontSize: 12 }}
876
884
  >
877
885
  Learn more
878
- </a>
886
+ </Button>
879
887
  </Stack>
880
888
  )}
881
889
 
882
890
  {/* Before/After examples */}
883
891
  {(staticFix.badExample || staticFix.goodExample) && (
884
- <div style={{
885
- display: 'grid',
886
- gridTemplateColumns: '1fr 1fr',
887
- gap: 8,
888
- marginTop: 8,
889
- }}>
892
+ <Box display="grid" style={{ gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 8 }}>
890
893
  {staticFix.badExample && (
891
894
  <div>
892
895
  <Text size="xs" weight="medium" style={{ color: 'var(--color-danger)', marginBottom: 4 }}>
893
896
  Before
894
897
  </Text>
895
- <pre style={{
898
+ <Box as="pre" padding="xs" rounded="sm" style={{
896
899
  fontSize: 10,
897
900
  fontFamily: 'monospace',
898
- padding: 8,
899
- borderRadius: 'var(--radius-sm)',
900
901
  background: 'var(--color-danger-bg)',
901
902
  color: 'var(--color-danger)',
902
903
  overflowX: 'auto',
@@ -904,7 +905,7 @@ function EnhancedRuleList({
904
905
  margin: 0,
905
906
  }}>
906
907
  {staticFix.badExample}
907
- </pre>
908
+ </Box>
908
909
  </div>
909
910
  )}
910
911
  {staticFix.goodExample && (
@@ -912,11 +913,9 @@ function EnhancedRuleList({
912
913
  <Text size="xs" weight="medium" style={{ color: 'var(--color-success)', marginBottom: 4 }}>
913
914
  After
914
915
  </Text>
915
- <pre style={{
916
+ <Box as="pre" padding="xs" rounded="sm" style={{
916
917
  fontSize: 10,
917
918
  fontFamily: 'monospace',
918
- padding: 8,
919
- borderRadius: 'var(--radius-sm)',
920
919
  background: 'var(--color-success-bg)',
921
920
  color: 'var(--color-success)',
922
921
  overflowX: 'auto',
@@ -924,10 +923,10 @@ function EnhancedRuleList({
924
923
  margin: 0,
925
924
  }}>
926
925
  {staticFix.goodExample}
927
- </pre>
926
+ </Box>
928
927
  </div>
929
928
  )}
930
- </div>
929
+ </Box>
931
930
  )}
932
931
  </Stack>
933
932
  </Box>
@@ -938,14 +937,17 @@ function EnhancedRuleList({
938
937
  <Box padding="sm" style={{ borderBottom: '1px solid var(--border-subtle)' }}>
939
938
  <Text size="xs" color="secondary" style={{ marginBottom: 4 }}>{rule.help}</Text>
940
939
  {rule.helpUrl && (
941
- <a
940
+ <Button
941
+ as="a"
942
+ variant="ghost"
943
+ size="sm"
942
944
  href={rule.helpUrl}
943
945
  target="_blank"
944
946
  rel="noopener noreferrer"
945
- style={{ fontSize: 12, color: 'var(--color-accent)', textDecoration: 'none' }}
947
+ style={{ fontSize: 12 }}
946
948
  >
947
949
  Learn more about {rule.id}
948
- </a>
950
+ </Button>
949
951
  )}
950
952
  </Box>
951
953
  )}
@@ -1011,20 +1013,18 @@ function EnhancedRuleList({
1011
1013
  </Stack>
1012
1014
 
1013
1015
  {/* HTML snippet */}
1014
- <pre style={{
1016
+ <Box as="pre" padding="xs" rounded="sm" style={{
1015
1017
  fontSize: 10,
1016
1018
  fontFamily: 'monospace',
1017
1019
  color: 'var(--text-tertiary)',
1018
1020
  background: 'var(--bg-primary)',
1019
- borderRadius: 'var(--radius-sm)',
1020
- padding: 8,
1021
1021
  overflowX: 'auto',
1022
1022
  whiteSpace: 'pre-wrap',
1023
1023
  border: '1px solid var(--border-subtle)',
1024
1024
  margin: 0,
1025
1025
  }}>
1026
1026
  {node.html}
1027
- </pre>
1027
+ </Box>
1028
1028
 
1029
1029
  {/* Failure summary */}
1030
1030
  {node.failureSummary && (
@@ -1054,19 +1054,17 @@ function EnhancedRuleList({
1054
1054
  <Text size="xs" style={{ color: 'var(--color-info)', marginBottom: 8 }}>
1055
1055
  {elementFix.explanation}
1056
1056
  </Text>
1057
- <pre style={{
1057
+ <Box as="pre" padding="xs" rounded="sm" style={{
1058
1058
  fontSize: 10,
1059
1059
  fontFamily: 'monospace',
1060
1060
  color: 'var(--color-info)',
1061
1061
  background: 'var(--bg-secondary)',
1062
- borderRadius: 'var(--radius-sm)',
1063
- padding: 8,
1064
1062
  overflowX: 'auto',
1065
1063
  whiteSpace: 'pre-wrap',
1066
1064
  margin: 0,
1067
1065
  }}>
1068
1066
  {elementFix.fixedHtml}
1069
- </pre>
1067
+ </Box>
1070
1068
  </Alert.Content>
1071
1069
  </Alert.Body>
1072
1070
  </Alert>
@@ -1074,10 +1072,8 @@ function EnhancedRuleList({
1074
1072
 
1075
1073
  {/* AI fix suggestion */}
1076
1074
  {aiFixes?.[fixKey] && (
1077
- <div style={{
1075
+ <Box padding="xs" rounded="sm" style={{
1078
1076
  marginTop: 8,
1079
- padding: 8,
1080
- borderRadius: 'var(--radius-sm)',
1081
1077
  background: 'var(--color-accent-subtle)',
1082
1078
  border: '1px solid var(--color-accent)',
1083
1079
  }}>
@@ -1087,7 +1083,7 @@ function EnhancedRuleList({
1087
1083
  AI Fix Suggestion
1088
1084
  </Text>
1089
1085
  </Stack>
1090
- <pre style={{
1086
+ <Box as="pre" style={{
1091
1087
  fontSize: 10,
1092
1088
  fontFamily: 'monospace',
1093
1089
  color: 'var(--color-accent)',
@@ -1095,8 +1091,8 @@ function EnhancedRuleList({
1095
1091
  margin: 0,
1096
1092
  }}>
1097
1093
  {aiFixes[fixKey]}
1098
- </pre>
1099
- </div>
1094
+ </Box>
1095
+ </Box>
1100
1096
  )}
1101
1097
  </Box>
1102
1098
  );
@@ -1118,7 +1114,7 @@ function EnhancedRuleList({
1118
1114
  </Stack>
1119
1115
  </Box>
1120
1116
  )}
1121
- </div>
1117
+ </Box>
1122
1118
  )}
1123
1119
  </Card>
1124
1120
  );
@@ -1152,63 +1148,59 @@ function RuleList({
1152
1148
  return (
1153
1149
  <Card key={rule.id}>
1154
1150
  {/* Rule Header */}
1155
- <button
1151
+ <Button
1152
+ variant="ghost"
1153
+ fullWidth
1156
1154
  onClick={() => onToggleRule(rule.id)}
1157
- style={{
1158
- width: '100%',
1159
- display: 'flex',
1160
- alignItems: 'center',
1161
- gap: 8,
1162
- padding: '8px 12px',
1163
- textAlign: 'left',
1164
- background: 'none',
1165
- border: 'none',
1166
- cursor: 'pointer',
1167
- transition: 'background var(--transition-fast)',
1168
- }}
1155
+ style={{ justifyContent: 'flex-start', padding: '8px 12px' }}
1169
1156
  >
1170
- {isExpanded ? (
1171
- <ChevronDownIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
1172
- ) : (
1173
- <ChevronRightIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
1174
- )}
1175
-
1176
- {type === "pass" && (
1177
- <CheckIcon style={{ width: 16, height: 16, color: 'var(--color-success)', flexShrink: 0 }} />
1178
- )}
1179
-
1180
- {type === "incomplete" && (
1181
- <Badge variant="warning" size="sm">Review</Badge>
1182
- )}
1183
-
1184
- <Text size="xs" style={{
1185
- flex: 1,
1186
- overflow: 'hidden',
1187
- textOverflow: 'ellipsis',
1188
- whiteSpace: 'nowrap',
1189
- }}>
1190
- {rule.description}
1191
- </Text>
1157
+ <Stack direction="row" align="center" gap="sm" style={{ width: '100%' }}>
1158
+ {isExpanded ? (
1159
+ <ChevronDownIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
1160
+ ) : (
1161
+ <ChevronRightIcon style={{ width: 16, height: 16, color: 'var(--text-tertiary)', flexShrink: 0 }} />
1162
+ )}
1192
1163
 
1193
- <Text size="xs" color="tertiary" style={{ flexShrink: 0 }}>
1194
- {rule.nodes.length} element{rule.nodes.length !== 1 ? "s" : ""}
1195
- </Text>
1196
- </button>
1164
+ {type === "pass" && (
1165
+ <CheckIcon style={{ width: 16, height: 16, color: 'var(--color-success)', flexShrink: 0 }} />
1166
+ )}
1167
+
1168
+ {type === "incomplete" && (
1169
+ <Badge variant="warning" size="sm">Review</Badge>
1170
+ )}
1171
+
1172
+ <Text size="xs" style={{
1173
+ flex: 1,
1174
+ overflow: 'hidden',
1175
+ textOverflow: 'ellipsis',
1176
+ whiteSpace: 'nowrap',
1177
+ }}>
1178
+ {rule.description}
1179
+ </Text>
1180
+
1181
+ <Text size="xs" color="tertiary" style={{ flexShrink: 0 }}>
1182
+ {rule.nodes.length} element{rule.nodes.length !== 1 ? "s" : ""}
1183
+ </Text>
1184
+ </Stack>
1185
+ </Button>
1197
1186
 
1198
1187
  {/* Expanded content */}
1199
1188
  {isExpanded && (
1200
- <div style={{ borderTop: '1px solid var(--border)', background: 'var(--bg-secondary)' }}>
1189
+ <Box borderTop background="secondary">
1201
1190
  <Box padding="sm" style={{ borderBottom: '1px solid var(--border-subtle)' }}>
1202
1191
  <Text size="xs" color="secondary" style={{ marginBottom: 4 }}>{rule.help}</Text>
1203
1192
  {rule.helpUrl && (
1204
- <a
1193
+ <Button
1194
+ as="a"
1195
+ variant="ghost"
1196
+ size="sm"
1205
1197
  href={rule.helpUrl}
1206
1198
  target="_blank"
1207
1199
  rel="noopener noreferrer"
1208
- style={{ fontSize: 12, color: 'var(--color-accent)', textDecoration: 'none' }}
1200
+ style={{ fontSize: 12 }}
1209
1201
  >
1210
1202
  Learn more about {rule.id}
1211
- </a>
1203
+ </Button>
1212
1204
  )}
1213
1205
  </Box>
1214
1206
 
@@ -1248,24 +1240,22 @@ function RuleList({
1248
1240
  {node.target[node.target.length - 1]}
1249
1241
  </Button>
1250
1242
 
1251
- <pre style={{
1243
+ <Box as="pre" padding="xs" rounded="sm" style={{
1252
1244
  fontSize: 10,
1253
1245
  fontFamily: 'monospace',
1254
1246
  color: 'var(--text-tertiary)',
1255
1247
  background: 'var(--bg-primary)',
1256
- borderRadius: 'var(--radius-sm)',
1257
- padding: 8,
1258
1248
  overflowX: 'auto',
1259
1249
  whiteSpace: 'pre-wrap',
1260
1250
  margin: 0,
1261
1251
  }}>
1262
1252
  {node.html}
1263
- </pre>
1253
+ </Box>
1264
1254
  </Box>
1265
1255
  );
1266
1256
  })}
1267
1257
  </div>
1268
- </div>
1258
+ </Box>
1269
1259
  )}
1270
1260
  </Card>
1271
1261
  );
@@ -1278,17 +1268,15 @@ function SuccessMessage({ message }: { message: string }) {
1278
1268
  return (
1279
1269
  <EmptyState>
1280
1270
  <EmptyState.Icon>
1281
- <div style={{
1271
+ <Box rounded="full" display="flex" style={{
1282
1272
  width: 48,
1283
1273
  height: 48,
1284
- borderRadius: '50%',
1285
- background: 'var(--color-success-bg)',
1286
- display: 'flex',
1287
1274
  alignItems: 'center',
1288
1275
  justifyContent: 'center',
1276
+ background: 'var(--color-success-bg)',
1289
1277
  }}>
1290
- <CheckIcon style={{ width: 24, height: 24, color: 'var(--color-success)' }} />
1291
- </div>
1278
+ <ShieldCheck size={24} weight="regular" style={{ color: 'var(--color-success)' }} />
1279
+ </Box>
1292
1280
  </EmptyState.Icon>
1293
1281
  <EmptyState.Title style={{ color: 'var(--color-success)' }}>{message}</EmptyState.Title>
1294
1282
  <EmptyState.Description>
@@ -1302,7 +1290,15 @@ function EmptyMessage({ message }: { message: string }) {
1302
1290
  return (
1303
1291
  <EmptyState>
1304
1292
  <EmptyState.Icon>
1305
- <AccessibilityIcon style={{ width: 32, height: 32, opacity: 0.5 }} />
1293
+ <Box rounded="full" display="flex" style={{
1294
+ width: 48,
1295
+ height: 48,
1296
+ alignItems: 'center',
1297
+ justifyContent: 'center',
1298
+ background: 'var(--bg-hover)',
1299
+ }}>
1300
+ <Wheelchair size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />
1301
+ </Box>
1306
1302
  </EmptyState.Icon>
1307
1303
  <EmptyState.Description>{message}</EmptyState.Description>
1308
1304
  </EmptyState>
@@ -1368,7 +1364,7 @@ function AISetupModalContent({ onSave, onClose, currentConfig }: AISetupModalCon
1368
1364
 
1369
1365
  <div>
1370
1366
  <Text size="xs" weight="medium" style={{ marginBottom: 4 }}>API Key</Text>
1371
- <input
1367
+ <Input
1372
1368
  type="password"
1373
1369
  value={apiKey}
1374
1370
  onChange={(e) => setApiKey(e.target.value)}
@@ -1377,38 +1373,35 @@ function AISetupModalContent({ onSave, onClose, currentConfig }: AISetupModalCon
1377
1373
  ? "sk-ant-api03-..."
1378
1374
  : "sk-..."
1379
1375
  }
1380
- style={{
1381
- width: '100%',
1382
- padding: '8px 12px',
1383
- fontSize: 12,
1384
- borderRadius: 'var(--radius-sm)',
1385
- border: '1px solid var(--border)',
1386
- background: 'var(--bg-secondary)',
1387
- color: 'var(--text-primary)',
1388
- outline: 'none',
1389
- boxSizing: 'border-box',
1390
- }}
1376
+ size="sm"
1377
+ style={{ width: '100%' }}
1391
1378
  />
1392
1379
  <Text size="xs" color="tertiary" style={{ marginTop: 4 }}>
1393
1380
  Get your API key from{" "}
1394
1381
  {provider === "anthropic" ? (
1395
- <a
1382
+ <Button
1383
+ as="a"
1384
+ variant="ghost"
1385
+ size="sm"
1396
1386
  href="https://console.anthropic.com/settings/keys"
1397
1387
  target="_blank"
1398
1388
  rel="noopener noreferrer"
1399
- style={{ color: 'var(--color-accent)', textDecoration: 'none' }}
1389
+ style={{ display: 'inline', padding: 0, fontSize: 'inherit' }}
1400
1390
  >
1401
1391
  console.anthropic.com
1402
- </a>
1392
+ </Button>
1403
1393
  ) : (
1404
- <a
1394
+ <Button
1395
+ as="a"
1396
+ variant="ghost"
1397
+ size="sm"
1405
1398
  href="https://platform.openai.com/api-keys"
1406
1399
  target="_blank"
1407
1400
  rel="noopener noreferrer"
1408
- style={{ color: 'var(--color-accent)', textDecoration: 'none' }}
1401
+ style={{ display: 'inline', padding: 0, fontSize: 'inherit' }}
1409
1402
  >
1410
1403
  platform.openai.com
1411
- </a>
1404
+ </Button>
1412
1405
  )}
1413
1406
  </Text>
1414
1407
  </div>