Skip to content

Commit 10e295e

Browse files
committed
feat(devtools): enhance SEO tab with new styles and severity handling
This commit introduces new styles for the SEO tab components, including enhanced visual presentation for SEO analysis results. It refactors the handling of severity indicators across various sections, such as headings, JSON-LD, and links, utilizing a consistent design approach. Additionally, it improves the structure and readability of the code, ensuring a cohesive user experience throughout the SEO tab.
1 parent 23e0197 commit 10e295e

8 files changed

Lines changed: 917 additions & 562 deletions

File tree

packages/devtools/src/styles/use-styles.ts

Lines changed: 507 additions & 4 deletions
Large diffs are not rendered by default.

packages/devtools/src/tabs/seo-tab/heading-structure-preview.tsx

Lines changed: 87 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { For, Show } from 'solid-js'
22
import { Section, SectionDescription } from '@tanstack/devtools-ui'
33
import { useStyles } from '../../styles/use-styles'
4-
import { seoSeverityColor, type SeoSeverity } from './seo-severity'
4+
import { pickSeverityClass, type SeoSeverity } from './seo-severity'
55
import type { SeoSectionSummary } from './seo-section-summary'
66

77
type HeadingItem = {
@@ -50,14 +50,14 @@ function validateHeadings(headings: Array<HeadingItem>): Array<HeadingIssue> {
5050
})
5151
} else if (h1Count > 1) {
5252
issues.push({
53-
severity: 'warning',
53+
severity: 'error',
5454
message: `Multiple H1 headings detected (${h1Count}).`,
5555
})
5656
}
5757

5858
if (headings[0] && headings[0].level !== 1) {
5959
issues.push({
60-
severity: 'warning',
60+
severity: 'error',
6161
message: `First heading is ${headings[0].tag.toUpperCase()} instead of H1.`,
6262
})
6363
}
@@ -66,15 +66,15 @@ function validateHeadings(headings: Array<HeadingItem>): Array<HeadingIssue> {
6666
const current = headings[index]!
6767
if (!current.text) {
6868
issues.push({
69-
severity: 'warning',
69+
severity: 'error',
7070
message: `${current.tag.toUpperCase()} is empty.`,
7171
})
7272
}
7373
if (index > 0) {
7474
const previous = headings[index - 1]!
7575
if (current.level - previous.level > 1) {
7676
issues.push({
77-
severity: 'warning',
77+
severity: 'error',
7878
message: `Skipped heading level from ${previous.tag.toUpperCase()} to ${current.tag.toUpperCase()}.`,
7979
})
8080
}
@@ -96,19 +96,66 @@ export function getHeadingStructureSummary(): SeoSectionSummary {
9696
}
9797
}
9898

99-
const HEADING_LEVEL_COLORS: Record<number, string> = {
100-
1: '#60a5fa',
101-
2: '#34d399',
102-
3: '#a78bfa',
103-
4: '#f59e0b',
104-
5: '#f87171',
105-
6: '#94a3b8',
99+
function headingIndentClass(
100+
s: ReturnType<ReturnType<typeof useStyles>>,
101+
level: HeadingItem['level'],
102+
): string {
103+
switch (level) {
104+
case 1:
105+
return s.seoHeadingTreeIndent1
106+
case 2:
107+
return s.seoHeadingTreeIndent2
108+
case 3:
109+
return s.seoHeadingTreeIndent3
110+
case 4:
111+
return s.seoHeadingTreeIndent4
112+
case 5:
113+
return s.seoHeadingTreeIndent5
114+
case 6:
115+
return s.seoHeadingTreeIndent6
116+
}
117+
}
118+
119+
function headingTagClass(
120+
s: ReturnType<ReturnType<typeof useStyles>>,
121+
level: HeadingItem['level'],
122+
): string {
123+
const base = s.seoHeadingTag
124+
switch (level) {
125+
case 1:
126+
return `${base} ${s.seoHeadingTagL1}`
127+
case 2:
128+
return `${base} ${s.seoHeadingTagL2}`
129+
case 3:
130+
return `${base} ${s.seoHeadingTagL3}`
131+
case 4:
132+
return `${base} ${s.seoHeadingTagL4}`
133+
case 5:
134+
return `${base} ${s.seoHeadingTagL5}`
135+
case 6:
136+
return `${base} ${s.seoHeadingTagL6}`
137+
}
106138
}
107139

108140
export function HeadingStructurePreviewSection() {
109141
const styles = useStyles()
110142
const headings = extractHeadings()
111143
const issues = validateHeadings(headings)
144+
const s = styles()
145+
146+
const issueBulletClass = (sev: SeoSeverity) =>
147+
`${s.seoIssueBullet} ${pickSeverityClass(sev, {
148+
error: s.seoIssueBulletError,
149+
warning: s.seoIssueBulletWarning,
150+
info: s.seoIssueBulletInfo,
151+
})}`
152+
153+
const issueBadgeClass = (sev: SeoSeverity) =>
154+
`${s.seoIssueSeverityBadge} ${pickSeverityClass(sev, {
155+
error: s.seoIssueSeverityBadgeError,
156+
warning: s.seoIssueSeverityBadgeWarning,
157+
info: s.seoIssueSeverityBadgeInfo,
158+
})}`
112159

113160
return (
114161
<Section>
@@ -117,114 +164,54 @@ export function HeadingStructurePreviewSection() {
117164
common hierarchy issues. This section scans once when opened.
118165
</SectionDescription>
119166

120-
{/* Heading tree */}
121-
<div class={styles().serpPreviewBlock}>
122-
<div
123-
style={{
124-
display: 'flex',
125-
'align-items': 'center',
126-
'justify-content': 'space-between',
127-
'margin-bottom': '10px',
128-
}}
129-
>
130-
<div
131-
class={styles().serpPreviewLabel}
132-
style={{ 'margin-bottom': '0' }}
133-
>
134-
Heading tree
135-
</div>
136-
<span style={{ 'font-size': '11px', color: '#6b7280' }}>
167+
<div class={s.serpPreviewBlock}>
168+
<div class={s.seoHeadingTreeHeaderRow}>
169+
<div class={s.serpPreviewLabelFlat}>Heading tree</div>
170+
<span class={s.seoHeadingTreeCount}>
137171
{headings.length} heading{headings.length === 1 ? '' : 's'}
138172
</span>
139173
</div>
140174
<Show
141175
when={headings.length > 0}
142176
fallback={
143-
<div class={styles().seoMissingTagsSection}>
177+
<div class={s.seoMissingTagsSection}>
144178
No headings found on this page.
145179
</div>
146180
}
147181
>
148-
<ul
149-
style={{
150-
margin: '0',
151-
padding: '0',
152-
'list-style': 'none',
153-
display: 'flex',
154-
'flex-direction': 'column',
155-
gap: '3px',
156-
}}
157-
>
182+
<ul class={s.seoHeadingTreeList}>
158183
<For each={headings}>
159-
{(heading) => {
160-
const color = HEADING_LEVEL_COLORS[heading.level] ?? '#94a3b8'
161-
return (
162-
<li
163-
style={{
164-
display: 'flex',
165-
gap: '8px',
166-
'align-items': 'baseline',
167-
'padding-left': `${(heading.level - 1) * 14}px`,
168-
}}
184+
{(heading) => (
185+
<li
186+
class={`${s.seoHeadingTreeItem} ${headingIndentClass(s, heading.level)}`}
187+
>
188+
<span class={headingTagClass(s, heading.level)}>
189+
{heading.tag.toUpperCase()}
190+
</span>
191+
<span
192+
class={
193+
heading.text ? s.seoHeadingLineText : s.seoHeadingLineTextEmpty
194+
}
169195
>
170-
<span
171-
style={{
172-
display: 'inline-flex',
173-
'align-items': 'center',
174-
'justify-content': 'center',
175-
'min-width': '26px',
176-
height: '16px',
177-
'border-radius': '3px',
178-
'font-size': '10px',
179-
'font-weight': '700',
180-
'letter-spacing': '0.03em',
181-
background: `${color}18`,
182-
color,
183-
'flex-shrink': '0',
184-
'font-family': 'monospace',
185-
}}
186-
>
187-
{heading.tag.toUpperCase()}
188-
</span>
189-
<span
190-
class={styles().seoIssueText}
191-
style={{
192-
'font-size': '12px',
193-
'font-style': heading.text ? 'normal' : 'italic',
194-
opacity: heading.text ? 1 : 0.65,
195-
}}
196-
>
197-
{heading.text || '(empty)'}
198-
</span>
199-
</li>
200-
)
201-
}}
196+
{heading.text || '(empty)'}
197+
</span>
198+
</li>
199+
)}
202200
</For>
203201
</ul>
204202
</Show>
205203
</div>
206204

207-
{/* Structure issues */}
208205
<Show when={issues.length > 0}>
209-
<div class={styles().serpPreviewBlock}>
210-
<div class={styles().serpPreviewLabel}>Structure issues</div>
211-
<ul class={styles().seoIssueList}>
206+
<div class={s.serpPreviewBlock}>
207+
<div class={s.serpPreviewLabel}>Structure issues</div>
208+
<ul class={s.seoIssueList}>
212209
<For each={issues}>
213210
{(issue) => (
214-
<li class={styles().seoIssueRow}>
215-
<span
216-
class={styles().seoIssueBullet}
217-
style={{ color: seoSeverityColor(issue.severity) }}
218-
>
219-
220-
</span>
221-
<span class={styles().seoIssueMessage}>{issue.message}</span>
222-
<span
223-
class={styles().seoIssueSeverityBadge}
224-
style={{ color: seoSeverityColor(issue.severity) }}
225-
>
226-
{issue.severity}
227-
</span>
211+
<li class={s.seoIssueRow}>
212+
<span class={issueBulletClass(issue.severity)}></span>
213+
<span class={s.seoIssueMessage}>{issue.message}</span>
214+
<span class={issueBadgeClass(issue.severity)}>{issue.severity}</span>
228215
</li>
229216
)}
230217
</For>

0 commit comments

Comments
 (0)