Skip to content

Commit 23e0197

Browse files
committed
feat(devtools): enhance SEO tab with new styles and structured issue reporting
This commit introduces new styles for the SEO tab components, improving the visual presentation of SEO analysis results. It adds structured issue reporting for SEO elements, including headings, JSON-LD, and links, utilizing a consistent design for severity indicators. Additionally, it refactors existing components to enhance readability and maintainability, ensuring a cohesive user experience across the SEO tab.
1 parent e52378c commit 23e0197

6 files changed

Lines changed: 576 additions & 173 deletions

File tree

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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,81 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
364364
color: ${t(colors.red[700], colors.red[400])};
365365
font-size: 0.875rem;
366366
`,
367+
/**
368+
* SEO validation rows: message uses default panel foreground; keep
369+
* `seoSeverityColor` only on the bullet and severity label.
370+
*/
371+
seoIssueList: css`
372+
margin: 0;
373+
padding: 0;
374+
list-style: none;
375+
display: flex;
376+
flex-direction: column;
377+
gap: 6px;
378+
`,
379+
seoIssueListNested: css`
380+
margin: 6px 0 0 0;
381+
padding: 0;
382+
list-style: none;
383+
display: flex;
384+
flex-direction: column;
385+
gap: 3px;
386+
`,
387+
seoIssueRow: css`
388+
display: flex;
389+
gap: 8px;
390+
align-items: flex-start;
391+
font-size: 0.875rem;
392+
line-height: 1.45;
393+
`,
394+
seoIssueRowCompact: css`
395+
display: flex;
396+
gap: 6px;
397+
align-items: flex-start;
398+
font-size: 0.6875rem;
399+
line-height: 1.45;
400+
`,
401+
seoIssueBullet: css`
402+
flex-shrink: 0;
403+
padding-top: 1px;
404+
`,
405+
/** Default foreground for SEO issue copy (no layout). */
406+
seoIssueText: css`
407+
color: ${t(colors.gray[900], colors.gray[100])};
408+
line-height: 1.45;
409+
`,
410+
seoIssueMessage: css`
411+
flex: 1;
412+
min-width: 0;
413+
color: ${t(colors.gray[900], colors.gray[100])};
414+
line-height: 1.45;
415+
`,
416+
seoIssueSeverityBadge: css`
417+
flex-shrink: 0;
418+
font-size: 10px;
419+
text-transform: uppercase;
420+
letter-spacing: 0.04em;
421+
font-weight: 600;
422+
padding-top: 2px;
423+
`,
424+
seoMetaRow: css`
425+
display: flex;
426+
gap: 8px;
427+
align-items: flex-start;
428+
font-size: 0.75rem;
429+
padding: 5px 0;
430+
border-bottom: 1px solid ${t(colors.gray[200], colors.gray[800])};
431+
`,
432+
seoMetaRowLabel: css`
433+
min-width: 130px;
434+
flex-shrink: 0;
435+
color: ${t(colors.gray[500], colors.gray[400])};
436+
`,
437+
seoMetaRowValue: css`
438+
word-break: break-all;
439+
color: ${t(colors.gray[900], colors.gray[100])};
440+
line-height: 1.45;
441+
`,
367442
devtoolsPanelContainer: (
368443
panelLocation: TanStackDevtoolsConfig['panelLocation'],
369444
isDetached: boolean,

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

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@ function extractHeadings(): Array<HeadingItem> {
3636

3737
function validateHeadings(headings: Array<HeadingItem>): Array<HeadingIssue> {
3838
if (headings.length === 0) {
39-
return [{ severity: 'error', message: 'No heading tags found on this page.' }]
39+
return [
40+
{ severity: 'error', message: 'No heading tags found on this page.' },
41+
]
4042
}
4143

4244
const issues: Array<HeadingIssue> = []
4345
const h1Count = headings.filter((h) => h.level === 1).length
4446
if (h1Count === 0) {
45-
issues.push({ severity: 'error', message: 'No H1 heading found on this page.' })
47+
issues.push({
48+
severity: 'error',
49+
message: 'No H1 heading found on this page.',
50+
})
4651
} else if (h1Count > 1) {
4752
issues.push({
4853
severity: 'warning',
@@ -91,6 +96,15 @@ export function getHeadingStructureSummary(): SeoSectionSummary {
9196
}
9297
}
9398

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',
106+
}
107+
94108
export function HeadingStructurePreviewSection() {
95109
const styles = useStyles()
96110
const headings = extractHeadings()
@@ -102,9 +116,26 @@ export function HeadingStructurePreviewSection() {
102116
Visualizes heading structure (`h1`-`h6`) in DOM order and highlights
103117
common hierarchy issues. This section scans once when opened.
104118
</SectionDescription>
119+
120+
{/* Heading tree */}
105121
<div class={styles().serpPreviewBlock}>
106-
<div class={styles().serpPreviewLabel}>
107-
Total headings: {headings.length}
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' }}>
137+
{headings.length} heading{headings.length === 1 ? '' : 's'}
138+
</span>
108139
</div>
109140
<Show
110141
when={headings.length > 0}
@@ -114,35 +145,86 @@ export function HeadingStructurePreviewSection() {
114145
</div>
115146
}
116147
>
117-
<ul class={styles().serpErrorList} style={{ 'list-style': 'none', padding: '0' }}>
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+
>
118158
<For each={headings}>
119-
{(heading) => (
120-
<li
121-
style={{
122-
display: 'flex',
123-
gap: '8px',
124-
'align-items': 'baseline',
125-
'margin-top': '6px',
126-
'padding-left': `${(heading.level - 1) * 12}px`,
127-
}}
128-
>
129-
<strong>{heading.tag.toUpperCase()}</strong>
130-
<span>{heading.text || '(empty heading)'}</span>
131-
</li>
132-
)}
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+
}}
169+
>
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+
}}
133202
</For>
134203
</ul>
135204
</Show>
136205
</div>
137206

207+
{/* Structure issues */}
138208
<Show when={issues.length > 0}>
139209
<div class={styles().serpPreviewBlock}>
140210
<div class={styles().serpPreviewLabel}>Structure issues</div>
141-
<ul class={styles().serpErrorList}>
211+
<ul class={styles().seoIssueList}>
142212
<For each={issues}>
143213
{(issue) => (
144-
<li style={{ color: seoSeverityColor(issue.severity), 'margin-top': '4px' }}>
145-
[{issue.severity}] {issue.message}
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>
146228
</li>
147229
)}
148230
</For>

0 commit comments

Comments
 (0)