@tanstack/devtools-a11y 0.0.1 → 0.1.1

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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/dist/esm/core/components/IssueCard.d.ts +10 -0
  3. package/dist/esm/core/components/IssueCard.js +83 -0
  4. package/dist/esm/core/components/IssueCard.js.map +1 -0
  5. package/dist/esm/core/components/IssueList.d.ts +6 -0
  6. package/dist/esm/core/components/IssueList.js +134 -0
  7. package/dist/esm/core/components/IssueList.js.map +1 -0
  8. package/dist/esm/core/components/Settings.d.ts +6 -0
  9. package/dist/esm/core/components/Settings.js +251 -0
  10. package/dist/esm/core/components/Settings.js.map +1 -0
  11. package/dist/esm/core/components/Shell.d.ts +2 -0
  12. package/dist/esm/core/components/Shell.js +214 -0
  13. package/dist/esm/core/components/Shell.js.map +1 -0
  14. package/dist/esm/core/components/index.d.ts +2 -0
  15. package/dist/esm/core/components/index.js +14 -0
  16. package/dist/esm/core/components/index.js.map +1 -0
  17. package/dist/esm/core/contexts/allyContext.d.ts +17 -0
  18. package/dist/esm/core/contexts/allyContext.js +66 -0
  19. package/dist/esm/core/contexts/allyContext.js.map +1 -0
  20. package/dist/esm/core/core.d.ts +19 -0
  21. package/dist/esm/core/core.js +8 -0
  22. package/dist/esm/core/core.js.map +1 -0
  23. package/dist/esm/core/index.d.ts +9 -0
  24. package/dist/esm/core/index.js +9 -0
  25. package/dist/esm/core/index.js.map +1 -0
  26. package/dist/esm/core/production.d.ts +2 -0
  27. package/dist/esm/core/production.js +4 -0
  28. package/dist/esm/core/styles/styles.d.ts +85 -0
  29. package/dist/esm/core/styles/styles.js +547 -0
  30. package/dist/esm/core/styles/styles.js.map +1 -0
  31. package/dist/esm/core/types/types.d.ts +141 -0
  32. package/dist/esm/core/utils/ally-audit.utils.d.ts +19 -0
  33. package/dist/esm/core/utils/ally-audit.utils.js +226 -0
  34. package/dist/esm/core/utils/ally-audit.utils.js.map +1 -0
  35. package/dist/esm/core/utils/config.utils.d.ts +17 -0
  36. package/dist/esm/core/utils/config.utils.js +63 -0
  37. package/dist/esm/core/utils/config.utils.js.map +1 -0
  38. package/dist/esm/core/utils/custom-audit.utils.d.ts +13 -0
  39. package/dist/esm/core/utils/custom-audit.utils.js +426 -0
  40. package/dist/esm/core/utils/custom-audit.utils.js.map +1 -0
  41. package/dist/esm/core/utils/export-audit.uitls.d.ts +17 -0
  42. package/dist/esm/core/utils/export-audit.uitls.js +83 -0
  43. package/dist/esm/core/utils/export-audit.uitls.js.map +1 -0
  44. package/dist/esm/core/utils/ui.utils.d.ts +24 -0
  45. package/dist/esm/core/utils/ui.utils.js +330 -0
  46. package/dist/esm/core/utils/ui.utils.js.map +1 -0
  47. package/dist/esm/react/A11yDevtools.d.ts +5 -0
  48. package/dist/esm/react/A11yDevtools.js +8 -0
  49. package/dist/esm/react/A11yDevtools.js.map +1 -0
  50. package/dist/esm/react/index.d.ts +8 -0
  51. package/dist/esm/react/index.js +11 -0
  52. package/dist/esm/react/index.js.map +1 -0
  53. package/dist/esm/react/plugin.d.ts +12 -0
  54. package/dist/esm/react/plugin.js +11 -0
  55. package/dist/esm/react/plugin.js.map +1 -0
  56. package/dist/esm/react/production/A11yDevtools.d.ts +5 -0
  57. package/dist/esm/react/production/A11yDevtools.js +8 -0
  58. package/dist/esm/react/production/A11yDevtools.js.map +1 -0
  59. package/dist/esm/react/production/plugin.d.ts +7 -0
  60. package/dist/esm/react/production/plugin.js +11 -0
  61. package/dist/esm/react/production/plugin.js.map +1 -0
  62. package/dist/esm/react/production.d.ts +3 -0
  63. package/dist/esm/react/production.js +5 -0
  64. package/dist/esm/solid/A11yDevtools.d.ts +5 -0
  65. package/dist/esm/solid/A11yDevtools.js +8 -0
  66. package/dist/esm/solid/A11yDevtools.js.map +1 -0
  67. package/dist/esm/solid/index.d.ts +8 -0
  68. package/dist/esm/solid/index.js +9 -0
  69. package/dist/esm/solid/index.js.map +1 -0
  70. package/dist/esm/solid/plugin.d.ts +12 -0
  71. package/dist/esm/solid/plugin.js +11 -0
  72. package/dist/esm/solid/plugin.js.map +1 -0
  73. package/dist/esm/solid/production/A11yDevtools.d.ts +5 -0
  74. package/dist/esm/solid/production/A11yDevtools.js +8 -0
  75. package/dist/esm/solid/production/A11yDevtools.js.map +1 -0
  76. package/dist/esm/solid/production/plugin.d.ts +7 -0
  77. package/dist/esm/solid/production/plugin.js +11 -0
  78. package/dist/esm/solid/production/plugin.js.map +1 -0
  79. package/dist/esm/solid/production.d.ts +3 -0
  80. package/dist/esm/solid/production.js +3 -0
  81. package/package.json +110 -7
  82. package/src/core/components/IssueCard.tsx +75 -0
  83. package/src/core/components/IssueList.tsx +155 -0
  84. package/src/core/components/Settings.tsx +221 -0
  85. package/src/core/components/Shell.tsx +154 -0
  86. package/src/core/components/index.tsx +12 -0
  87. package/src/core/contexts/allyContext.tsx +118 -0
  88. package/src/core/core.tsx +11 -0
  89. package/src/core/index.ts +10 -0
  90. package/src/core/production.ts +5 -0
  91. package/src/core/styles/styles.ts +556 -0
  92. package/src/core/types/types.ts +177 -0
  93. package/src/core/utils/ally-audit.utils.ts +345 -0
  94. package/src/core/utils/config.utils.ts +68 -0
  95. package/src/core/utils/custom-audit.utils.ts +643 -0
  96. package/src/core/utils/export-audit.uitls.ts +180 -0
  97. package/src/core/utils/ui.utils.ts +483 -0
  98. package/src/react/A11yDevtools.ts +12 -0
  99. package/src/react/index.ts +16 -0
  100. package/src/react/plugin.ts +9 -0
  101. package/src/react/production/A11yDevtools.ts +11 -0
  102. package/src/react/production/plugin.ts +9 -0
  103. package/src/react/production.ts +7 -0
  104. package/src/solid/A11yDevtools.ts +11 -0
  105. package/src/solid/index.ts +14 -0
  106. package/src/solid/plugin.ts +9 -0
  107. package/src/solid/production/A11yDevtools.ts +10 -0
  108. package/src/solid/production/plugin.ts +9 -0
  109. package/src/solid/production.ts +5 -0
  110. package/README.md +0 -45
package/package.json CHANGED
@@ -1,10 +1,113 @@
1
1
  {
2
2
  "name": "@tanstack/devtools-a11y",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @tanstack/devtools-a11y",
3
+ "version": "0.1.1",
4
+ "description": "Accessibility auditing plugin for TanStack Devtools powered by axe-core",
5
+ "author": "TanStack",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/devtools.git",
10
+ "directory": "packages/devtools-a11y"
11
+ },
12
+ "homepage": "https://tanstack.com/devtools",
13
+ "funding": {
14
+ "type": "github",
15
+ "url": "https://github.com/sponsors/tannerlinsley"
16
+ },
5
17
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
18
+ "devtools",
19
+ "accessibility",
20
+ "a11y",
21
+ "wcag",
22
+ "axe-core",
23
+ "audit"
24
+ ],
25
+ "type": "module",
26
+ "exports": {
27
+ "./core": {
28
+ "import": {
29
+ "types": "./dist/esm/core/index.d.ts",
30
+ "default": "./dist/esm/core/index.js"
31
+ }
32
+ },
33
+ "./core/production": {
34
+ "import": {
35
+ "types": "./dist/esm/core/production.d.ts",
36
+ "default": "./dist/esm/core/production.js"
37
+ }
38
+ },
39
+ "./react": {
40
+ "import": {
41
+ "types": "./dist/esm/react/index.d.ts",
42
+ "default": "./dist/esm/react/index.js"
43
+ }
44
+ },
45
+ "./react/production": {
46
+ "import": {
47
+ "types": "./dist/esm/react/production.d.ts",
48
+ "default": "./dist/esm/react/production.js"
49
+ }
50
+ },
51
+ "./solid": {
52
+ "import": {
53
+ "types": "./dist/esm/solid/index.d.ts",
54
+ "default": "./dist/esm/solid/index.js"
55
+ }
56
+ },
57
+ "./solid/production": {
58
+ "import": {
59
+ "types": "./dist/esm/solid/production.d.ts",
60
+ "default": "./dist/esm/solid/production.js"
61
+ }
62
+ },
63
+ "./package.json": "./package.json"
64
+ },
65
+ "sideEffects": false,
66
+ "engines": {
67
+ "node": ">=18"
68
+ },
69
+ "files": [
70
+ "dist/",
71
+ "src"
72
+ ],
73
+ "dependencies": {
74
+ "axe-core": "^4.10.0",
75
+ "goober": "^2.1.16",
76
+ "@tanstack/devtools-ui": "0.5.1",
77
+ "@tanstack/devtools-utils": "^0.4.0"
78
+ },
79
+ "devDependencies": {
80
+ "vite-plugin-solid": "^2.11.11"
81
+ },
82
+ "peerDependencies": {
83
+ "@types/react": ">=17.0.0",
84
+ "preact": ">=10.0.0",
85
+ "react": ">=17.0.0",
86
+ "solid-js": ">=1.9.7",
87
+ "vue": ">=3.2.0"
88
+ },
89
+ "peerDependenciesMeta": {
90
+ "preact": {
91
+ "optional": true
92
+ },
93
+ "react": {
94
+ "optional": true
95
+ },
96
+ "solid-js": {
97
+ "optional": true
98
+ },
99
+ "vue": {
100
+ "optional": true
101
+ }
102
+ },
103
+ "scripts": {
104
+ "clean": "premove ./build ./dist",
105
+ "lint:fix": "eslint ./src --fix",
106
+ "test:eslint": "eslint ./src",
107
+ "test:lib": "vitest",
108
+ "test:lib:dev": "pnpm test:lib --watch",
109
+ "test:types": "tsc",
110
+ "test:build": "publint --strict",
111
+ "build": "vite build"
112
+ }
113
+ }
@@ -0,0 +1,75 @@
1
+ /** @jsxImportSource solid-js */
2
+
3
+ import { For, Show } from 'solid-js'
4
+ import { Button } from '@tanstack/devtools-ui'
5
+ import { useStyles } from '../styles/styles'
6
+
7
+ // types
8
+ import type { A11yIssue, SeverityThreshold } from '../types/types'
9
+
10
+ interface A11yIssueCardProps {
11
+ issue: A11yIssue
12
+ impact: SeverityThreshold
13
+ selected: boolean
14
+ onSelect: () => void
15
+ onDisableRule: (ruleId: string) => void
16
+ }
17
+
18
+ export function A11yIssueCard(props: A11yIssueCardProps) {
19
+ const selector = () => props.issue.nodes[0]?.selector || 'unknown'
20
+ const styles = useStyles()
21
+
22
+ return (
23
+ <div
24
+ class={styles().issueCard}
25
+ classList={{
26
+ [styles().issueCardSelected]: props.selected,
27
+ }}
28
+ onClick={props.onSelect}
29
+ >
30
+ <div class={styles().issueRow}>
31
+ <div class={styles().issueMain}>
32
+ <div class={styles().issueTitleRow}>
33
+ <span class={styles().dot(props.impact)} />
34
+
35
+ <span>{props.issue.ruleId}</span>
36
+ </div>
37
+ <p class={styles().issueMessage}>{props.issue.message}</p>
38
+
39
+ <div class={styles().selector}>{selector()}</div>
40
+ </div>
41
+
42
+ <div class={styles().issueAside}>
43
+ <a
44
+ class={styles().helpLink}
45
+ href={props.issue.helpUrl}
46
+ target="_blank"
47
+ rel="noopener noreferrer"
48
+ onClick={(event) => event.stopPropagation()}
49
+ >
50
+ Learn more
51
+ </a>
52
+
53
+ <Button
54
+ variant="secondary"
55
+ ghost
56
+ onClick={(event: MouseEvent) => {
57
+ event.stopPropagation()
58
+ props.onDisableRule(props.issue.ruleId)
59
+ }}
60
+ >
61
+ Disable rule
62
+ </Button>
63
+ </div>
64
+ </div>
65
+
66
+ <Show when={props.issue.wcagTags.length > 0}>
67
+ <div class={styles().tags}>
68
+ <For each={props.issue.wcagTags.slice(0, 3)}>
69
+ {(tag) => <span class={styles().tag}>{tag}</span>}
70
+ </For>
71
+ </div>
72
+ </Show>
73
+ </div>
74
+ )
75
+ }
@@ -0,0 +1,155 @@
1
+ /** @jsxImportSource solid-js */
2
+
3
+ import { For, Show } from 'solid-js'
4
+ import { useAllyContext } from '../contexts/allyContext'
5
+ import {
6
+ SEVERITY_LABELS,
7
+ clearHighlights,
8
+ highlightAllIssues,
9
+ highlightElement,
10
+ scrollToElement,
11
+ } from '../utils/ui.utils'
12
+ import { IMPACTS } from '../utils/ally-audit.utils'
13
+ import { useStyles } from '../styles/styles'
14
+ import { A11yIssueCard } from './IssueCard'
15
+
16
+ // types
17
+ import type { Signal } from 'solid-js'
18
+
19
+ interface A11yIssueListProps {
20
+ selectedIssueSignal: Signal<string>
21
+ }
22
+
23
+ export function A11yIssueList(props: A11yIssueListProps) {
24
+ const [selectedIssueId, setSelectedIssueId] = props.selectedIssueSignal
25
+
26
+ // hooks
27
+ const styles = useStyles()
28
+ const ally = useAllyContext()
29
+
30
+ // handlers
31
+ const handleIssueClick = (issueId: string) => {
32
+ if (selectedIssueId() === issueId) {
33
+ setSelectedIssueId('')
34
+ clearHighlights()
35
+
36
+ if (
37
+ ally.config.showOverlays &&
38
+ ally.allyResult.audit &&
39
+ ally.filteredIssues().length > 0
40
+ ) {
41
+ highlightAllIssues(ally.filteredIssues())
42
+ }
43
+
44
+ return
45
+ }
46
+
47
+ setSelectedIssueId(issueId)
48
+ clearHighlights()
49
+
50
+ const issue = ally.allyResult.audit?.issues.find((i) => i.id === issueId)
51
+ if (!issue || issue.nodes.length === 0) return
52
+
53
+ let scrolled = false
54
+ for (const node of issue.nodes) {
55
+ const selector = node.selector
56
+ if (!selector) continue
57
+
58
+ try {
59
+ const el = document.querySelector(selector)
60
+ if (el) {
61
+ if (!scrolled) {
62
+ scrollToElement(selector)
63
+ scrolled = true
64
+ }
65
+
66
+ highlightElement(selector, issue.impact, {
67
+ showTooltip: true,
68
+ ruleId: issue.ruleId,
69
+ })
70
+ }
71
+ } catch (error) {
72
+ console.warn('[A11y Panel] Invalid selector:', selector, error)
73
+ }
74
+ }
75
+ }
76
+
77
+ return (
78
+ <div>
79
+ <div class={styles().summaryGrid}>
80
+ <For each={IMPACTS}>
81
+ {(impact) => {
82
+ // Count issues from the reactive filteredIssues memo so counts update when config.threshold changes
83
+ const issuesForImpact = () =>
84
+ ally.filteredIssues().filter((issue) => issue.impact === impact)
85
+ const count = () => issuesForImpact().length || 0
86
+
87
+ const active = () => ally.impactKey() === impact
88
+
89
+ return (
90
+ <button
91
+ class={styles().summaryButton}
92
+ classList={{
93
+ [styles().summaryButtonActive(impact)]: active(),
94
+ }}
95
+ onClick={() => {
96
+ ally.setImpactKey(
97
+ ally.impactKey() === impact ? 'all' : impact,
98
+ )
99
+
100
+ setSelectedIssueId('')
101
+ }}
102
+ >
103
+ <div class={styles().summaryCount(impact)}>{count()}</div>
104
+ <div class={styles().summaryLabel}>
105
+ {SEVERITY_LABELS[impact]}
106
+ </div>
107
+ </button>
108
+ )
109
+ }}
110
+ </For>
111
+ </div>
112
+
113
+ <For each={IMPACTS}>
114
+ {(impact) => {
115
+ const issues = () =>
116
+ ally.filteredIssues().filter((issue) => issue.impact === impact)
117
+
118
+ const shouldRender = () => {
119
+ if (ally.impactKey() !== 'all') {
120
+ return ally.impactKey() === impact
121
+ }
122
+ return issues().length > 0
123
+ }
124
+
125
+ return (
126
+ <Show when={shouldRender()}>
127
+ <div class={styles().section}>
128
+ <h3 class={styles().sectionTitle(impact)}>
129
+ {SEVERITY_LABELS[impact]} ({issues().length})
130
+ </h3>
131
+
132
+ <For each={issues()}>
133
+ {(issue) => (
134
+ <A11yIssueCard
135
+ issue={issue}
136
+ impact={impact}
137
+ selected={selectedIssueId() === issue.id}
138
+ onSelect={() => handleIssueClick(issue.id)}
139
+ onDisableRule={() =>
140
+ ally.setConfig('disabledRules', [
141
+ ...ally.config.disabledRules,
142
+ issue.ruleId,
143
+ ])
144
+ }
145
+ />
146
+ )}
147
+ </For>
148
+ </div>
149
+ </Show>
150
+ )
151
+ }}
152
+ </For>
153
+ </div>
154
+ )
155
+ }
@@ -0,0 +1,221 @@
1
+ /** @jsxImportSource solid-js */
2
+
3
+ import { For, Show, createMemo, createSignal } from 'solid-js'
4
+ import { Button, Input, Select } from '@tanstack/devtools-ui'
5
+ import { getAvailableRules } from '../utils/ally-audit.utils'
6
+ import { useAllyContext } from '../contexts/allyContext'
7
+ import { CATEGORIES, CATEGORY_LABELS, useStyles } from '../styles/styles'
8
+
9
+ // types
10
+ import type {
11
+ RuleCategory,
12
+ RuleSetPreset,
13
+ SeverityThreshold,
14
+ } from '../types/types'
15
+
16
+ interface A11ySettingsOverlayProps {
17
+ onClose: () => void
18
+ }
19
+
20
+ export function A11ySettingsOverlay(props: A11ySettingsOverlayProps) {
21
+ const { config, setConfig } = useAllyContext()
22
+ const styles = useStyles()
23
+
24
+ const disabledRulesSet = createMemo(() => new Set(config.disabledRules))
25
+ const availableRules = createMemo(() => getAvailableRules())
26
+
27
+ const [searchString, setSearchString] = createSignal('')
28
+ const [searchCategory, setSearchCategory] = createSignal<RuleCategory>('all')
29
+
30
+ const filteredRules = createMemo(() => {
31
+ const cat = searchCategory()
32
+ const query = searchString().toLowerCase()
33
+ return availableRules().filter((rule) => {
34
+ if (cat !== 'all' && !rule.tags.includes(cat)) {
35
+ return false
36
+ }
37
+
38
+ if (!query) return true
39
+ return (
40
+ rule.id.toLowerCase().includes(query) ||
41
+ rule.description.toLowerCase().includes(query)
42
+ )
43
+ })
44
+ })
45
+
46
+ return (
47
+ <div class={styles().settingsOverlay}>
48
+ <div class={styles().settingsHeader}>
49
+ <h3 class={styles().settingsTitle}>Settings</h3>
50
+ <Button variant="secondary" outline onClick={props.onClose}>
51
+ Done
52
+ </Button>
53
+ </div>
54
+
55
+ <div class={styles().settingsContent}>
56
+ <div class={styles().settingsSection}>
57
+ <h4 class={styles().settingsSectionLabel}>General</h4>
58
+ <div class={styles().settingsRowStack}>
59
+ <Select<SeverityThreshold>
60
+ label="Severity Threshold"
61
+ description="Only show issues at or above this level"
62
+ value={config.threshold}
63
+ options={[
64
+ { value: 'critical', label: 'Critical' },
65
+ { value: 'serious', label: 'Serious' },
66
+ { value: 'moderate', label: 'Moderate' },
67
+ { value: 'minor', label: 'Minor' },
68
+ ]}
69
+ onChange={(value: string) => {
70
+ setConfig('threshold', value as SeverityThreshold)
71
+ }}
72
+ />
73
+ <Select<RuleSetPreset>
74
+ label="Rule Set"
75
+ description="WCAG conformance level or standard"
76
+ value={config.ruleSet}
77
+ options={[
78
+ { value: 'wcag2a', label: 'WCAG 2.0 A' },
79
+ { value: 'wcag2aa', label: 'WCAG 2.0 AA' },
80
+ { value: 'wcag21aa', label: 'WCAG 2.1 AA' },
81
+ { value: 'wcag22aa', label: 'WCAG 2.2 AA' },
82
+ { value: 'section508', label: 'Section 508' },
83
+ { value: 'best-practice', label: 'Best Practice' },
84
+ { value: 'all', label: 'All Rules' },
85
+ ]}
86
+ onChange={(value: string) => {
87
+ setConfig('ruleSet', value as RuleSetPreset)
88
+ }}
89
+ />
90
+ </div>
91
+ </div>
92
+
93
+ <div>
94
+ <div class={styles().rulesHeaderRow}>
95
+ <h4 class={styles().settingsSectionLabel}>
96
+ Rules ({availableRules().length} total,{' '}
97
+ {config.disabledRules.length} disabled)
98
+ </h4>
99
+
100
+ <div class={styles().rulesHeaderActions}>
101
+ <Button
102
+ variant="success"
103
+ outline
104
+ onClick={() => setConfig('disabledRules', [])}
105
+ >
106
+ Enable All
107
+ </Button>
108
+
109
+ <Button
110
+ variant="danger"
111
+ outline
112
+ onClick={() =>
113
+ setConfig(
114
+ 'disabledRules',
115
+ availableRules().map((rule) => rule.id),
116
+ )
117
+ }
118
+ >
119
+ Disable All
120
+ </Button>
121
+ </div>
122
+ </div>
123
+
124
+ <div class={styles().filtersRow}>
125
+ <Select<RuleCategory>
126
+ label="Category"
127
+ value={searchCategory()}
128
+ options={CATEGORIES.map((cat) => ({
129
+ value: cat,
130
+ label: CATEGORY_LABELS[cat],
131
+ }))}
132
+ onChange={(value: string) =>
133
+ setSearchCategory(value as RuleCategory)
134
+ }
135
+ />
136
+
137
+ <Input
138
+ label="Search"
139
+ placeholder="Search rules..."
140
+ value={searchString()}
141
+ onChange={(value: string) => setSearchString(value)}
142
+ />
143
+ </div>
144
+
145
+ <div class={styles().rulesList}>
146
+ <For each={filteredRules()}>
147
+ {(rule, idx) => {
148
+ const isDisabled = () => disabledRulesSet().has(rule.id)
149
+ const isBestPracticeOnly = () =>
150
+ rule.tags.includes('best-practice') &&
151
+ !rule.tags.some(
152
+ (tag) =>
153
+ tag.startsWith('wcag') || tag.startsWith('section508'),
154
+ )
155
+ const categoryTag = () =>
156
+ rule.tags.find((tag) => tag.startsWith('cat.'))
157
+ const hasBorder = () => idx() < filteredRules().length - 1
158
+
159
+ return (
160
+ <label
161
+ class={styles().ruleRow}
162
+ classList={{
163
+ [styles().ruleRowDisabled]: isDisabled(),
164
+ [styles().ruleRowBorder]: hasBorder(),
165
+ }}
166
+ >
167
+ <input
168
+ class={styles().ruleCheckbox}
169
+ type="checkbox"
170
+ checked={!isDisabled()}
171
+ onChange={() =>
172
+ setConfig('disabledRules', (rules) => {
173
+ if (disabledRulesSet().has(rule.id)) {
174
+ return rules.filter((id) => id !== rule.id)
175
+ } else {
176
+ return [...rules, rule.id]
177
+ }
178
+ })
179
+ }
180
+ />
181
+ <div class={styles().ruleInfo}>
182
+ <div class={styles().ruleTop}>
183
+ <span
184
+ class={styles().ruleId}
185
+ classList={{
186
+ [styles().ruleIdDisabled]: isDisabled(),
187
+ }}
188
+ >
189
+ {rule.id}
190
+ </span>
191
+ <Show when={isBestPracticeOnly()}>
192
+ <span
193
+ class={styles().bpBadge}
194
+ title="Best Practice only"
195
+ >
196
+ BP
197
+ </span>
198
+ </Show>
199
+ </div>
200
+ <div class={styles().ruleDesc}>{rule.description}</div>
201
+ <Show when={categoryTag()}>
202
+ {(tag) => (
203
+ <div class={styles().catTagRow}>
204
+ <span class={styles().catTag}>
205
+ {CATEGORY_LABELS[tag() as RuleCategory] ||
206
+ tag().replace('cat.', '')}
207
+ </span>
208
+ </div>
209
+ )}
210
+ </Show>
211
+ </div>
212
+ </label>
213
+ )
214
+ }}
215
+ </For>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ )
221
+ }