polish: icon sizing hardened for cross-browser (review I-1/M2/M3/M4)
- .sf-tp-icon CSS width/height (Safari doesn't support var() in SVG presentation attributes); --sm variant class replaces size= call sites - search icon centered via translateY, decoupled from input height - bare-arrow test: full charset (→←↗↘⌕▾) + line-level › whitelist - new test: site.html tp_arrow must stay in sync with macro library path Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -204,10 +204,26 @@ def test_trade_pro_templates_have_no_bare_text_arrows():
|
|||||||
for f in root.rglob('*.html'):
|
for f in root.rglob('*.html'):
|
||||||
t = f.read_text(encoding='utf-8')
|
t = f.read_text(encoding='utf-8')
|
||||||
body = re.sub(r'\{#.*?#\}', '', t, flags=re.S)
|
body = re.sub(r'\{#.*?#\}', '', t, flags=re.S)
|
||||||
if '→' in body or ('›' in body and 'chevron' not in body):
|
if any(ch in body for ch in '→←↗↘⌕▾'):
|
||||||
offenders.append(str(f.relative_to(root)))
|
offenders.append(str(f.relative_to(root)))
|
||||||
|
# › 豁免通道收紧为行级:同行的 chevron 语义才放行
|
||||||
|
for line in body.splitlines():
|
||||||
|
if '›' in line and 'chevron' not in line:
|
||||||
|
offenders.append(f'{f.relative_to(root)}:›')
|
||||||
assert offenders == []
|
assert offenders == []
|
||||||
|
|
||||||
|
def test_site_html_arrow_matches_macro_library():
|
||||||
|
# 双源防护:site.html 的自包含 tp_arrow 必须与宏库 arrow-right 的 path 一致
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
root = Path(__file__).resolve().parent.parent / 'themes' / 'trade-pro'
|
||||||
|
macro = (root / '_professional_components.html').read_text(encoding='utf-8')
|
||||||
|
site = (root / 'site.html').read_text(encoding='utf-8')
|
||||||
|
m = re.search(r"'arrow-right'\s*:\s*'([^']+)'", macro)
|
||||||
|
s = re.search(r"tp_arrow.*?d=\"([^\"]+)\"", site, flags=re.S)
|
||||||
|
assert m and s, 'icon path sources not found'
|
||||||
|
assert m.group(1) == s.group(1), 'site.html tp_arrow drifted from macro library arrow-right'
|
||||||
|
|
||||||
def test_theme_blocks_css_overrides_platform(tmp_path):
|
def test_theme_blocks_css_overrides_platform(tmp_path):
|
||||||
from app import builder
|
from app import builder
|
||||||
dist = tmp_path / 'assets'; dist.mkdir()
|
dist = tmp_path / 'assets'; dist.mkdir()
|
||||||
|
|||||||
@@ -39,5 +39,5 @@
|
|||||||
{% macro grid(items,kind='product') %}<div class="pro-grid pro-grid-{{ kind }}">{% for item in items %}{{ card(item,kind,loop.index0) }}{% else %}<div class="pro-empty">No published content in this section yet.</div>{% endfor %}</div>{% endmacro %}
|
{% macro grid(items,kind='product') %}<div class="pro-grid pro-grid-{{ kind }}">{% for item in items %}{{ card(item,kind,loop.index0) }}{% else %}<div class="pro-empty">No published content in this section yet.</div>{% endfor %}</div>{% endmacro %}
|
||||||
{% macro section_title(eyebrow,heading,href='') %}<div class="pro-section-head"><div><span class="pro-eyebrow">{{ eyebrow }}</span><h2>{{ heading }}</h2></div>{% if href %}<a class="pro-text-link" href="{{ href }}">Browse all {{ icon('arrow-right') }}</a>{% endif %}</div>{% endmacro %}
|
{% macro section_title(eyebrow,heading,href='') %}<div class="pro-section-head"><div><span class="pro-eyebrow">{{ eyebrow }}</span><h2>{{ heading }}</h2></div>{% if href %}<a class="pro-text-link" href="{{ href }}">Browse all {{ icon('arrow-right') }}</a>{% endif %}</div>{% endmacro %}
|
||||||
{% macro filters() %}<div class="pro-filter-panel"><label><span>Search</span><span class="pro-filter-search-box">{{ icon('search') }}<input type="search" data-pro-filter-search placeholder="Filter by name or content"></span></label><label><span>Brand</span><select data-pro-filter-brand><option value="">All brands</option></select></label><label><span>Category</span><select data-pro-filter-category><option value="">All categories</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div>{% endmacro %}
|
{% macro filters() %}<div class="pro-filter-panel"><label><span>Search</span><span class="pro-filter-search-box">{{ icon('search') }}<input type="search" data-pro-filter-search placeholder="Filter by name or content"></span></label><label><span>Brand</span><select data-pro-filter-brand><option value="">All brands</option></select></label><label><span>Category</span><select data-pro-filter-category><option value="">All categories</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div>{% endmacro %}
|
||||||
{% macro footer_links(nav) %}<ul>{% for item in nav[:7] %}<li><a href="{{item.route}}">{{item.title}} {{ icon('arrow-right', size='var(--sf-tp-icon-size-sm, 16px)') }}</a></li>{% endfor %}</ul>{% endmacro %}
|
{% macro footer_links(nav) %}<ul>{% for item in nav[:7] %}<li><a href="{{item.route}}">{{item.title}} {{ icon('arrow-right', cls='sf-tp-icon--sm') }}</a></li>{% endfor %}</ul>{% endmacro %}
|
||||||
{% macro body_copy(content,page) %}{% if content %}<div class="pro-rich">{{ content }}</div>{% elif page.summary %}<p class="pro-lead">{{ page.summary }}</p>{% else %}<p class="pro-muted">Complete this page's content in the admin console.</p>{% endif %}{% endmacro %}
|
{% macro body_copy(content,page) %}{% if content %}<div class="pro-rich">{{ content }}</div>{% elif page.summary %}<p class="pro-lead">{{ page.summary }}</p>{% else %}<p class="pro-muted">Complete this page's content in the admin console.</p>{% endif %}{% endmacro %}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
keeping all 7 default sections so only the labels change, never coverage. #}
|
keeping all 7 default sections so only the labels change, never coverage. #}
|
||||||
{% set tp_nav=[{'title':'Home','route':'/'},{'title':'Products','route':'/products/'},{'title':'Brands','route':'/brands/'},{'title':'News','route':'/news/'},{'title':'Cases','route':'/cases/'},{'title':'About','route':'/about/'},{'title':'Contact','route':'/contact/'}] if tp_needs_en_nav else nav %}
|
{% set tp_nav=[{'title':'Home','route':'/'},{'title':'Products','route':'/products/'},{'title':'Brands','route':'/brands/'},{'title':'News','route':'/news/'},{'title':'Cases','route':'/cases/'},{'title':'About','route':'/about/'},{'title':'Contact','route':'/contact/'}] if tp_needs_en_nav else nav %}
|
||||||
{% if chrome_is_custom %}<div class="pro-utility"{% if not chrome.header.show_announcement %} hidden{% endif %}><div class="pro-wrap"><span>{{ chrome.header.announcement }}</span><span>{{ site.tagline or site.name }}</span></div></div>{% else %}<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL SOURCING · QUALITY · DELIVERY</span><span>INTERNATIONAL B2B SOLUTIONS</span></div></div>{% endif %}
|
{% if chrome_is_custom %}<div class="pro-utility"{% if not chrome.header.show_announcement %} hidden{% endif %}><div class="pro-wrap"><span>{{ chrome.header.announcement }}</span><span>{{ site.tagline or site.name }}</span></div></div>{% else %}<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL SOURCING · QUALITY · DELIVERY</span><span>INTERNATIONAL B2B SOLUTIONS</span></div></div>{% endif %}
|
||||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="{{site.name}} home">{% if safe_image(site.logo) %}<img class="pro-brand-logo" src="{{safe_image(site.logo)}}" alt="{{site.name}} Logo">{% else %}<span class="pro-logo-symbol">▣</span>{% endif %}<span class="pro-logo-name">{{site.name}}<small> GLOBAL B2B</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="Open navigation">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="Main navigation">{% for link in tp_nav %}{% if link.children %}<div class="pro-nav-group"><a href="{{link.route}}">{{link.title}}</a><button type="button" class="pro-nav-disclose" aria-expanded="false" aria-label="Expand {{link.title}} submenu">{{ icon('chevron-down', size='var(--sf-tp-icon-size-sm, 16px)') }}</button><div class="pro-nav-children" hidden>{% for child in link.children %}<a href="{{child.route}}">{{child.title}}</a>{% for leaf in child.children %}<a class="pro-nav-leaf" href="{{leaf.route}}">— {{leaf.title}}</a>{% endfor %}{% endfor %}</div></div>{% else %}<a href="{{link.route}}">{{link.title}}</a>{% endif %}{% endfor %}{% if chrome.header.show_search %}<a class="pro-search-link" href="/search/">Search {{ icon('search', size='var(--sf-tp-icon-size-sm, 16px)') }}</a>{% endif %}</nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">Products {{ icon('chevron-down', size='var(--sf-tp-icon-size-sm, 16px)') }}</button>{% if chrome_is_custom %}{% if chrome.header.show_cta %}<a href="{{chrome.header.cta_href}}" class="sf-tp-btn pro-header-cta">{{chrome.header.cta_text}} <span>{{ icon('arrow-right') }}</span></a>{% endif %}{% else %}<a href="/contact/" class="sf-tp-btn pro-header-cta">Request a Quote <span>{{ icon('arrow-right') }}</span></a>{% endif %}</div>{% if categories %}<div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>Product categories</span><div>{% for cat in categories[:12] %}<a href="{{cat.url}}">{{cat.name}} {{ icon('arrow-right') }}</a>{% endfor %}</div></div>{% endif %}</header>
|
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="{{site.name}} home">{% if safe_image(site.logo) %}<img class="pro-brand-logo" src="{{safe_image(site.logo)}}" alt="{{site.name}} Logo">{% else %}<span class="pro-logo-symbol">▣</span>{% endif %}<span class="pro-logo-name">{{site.name}}<small> GLOBAL B2B</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="Open navigation">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="Main navigation">{% for link in tp_nav %}{% if link.children %}<div class="pro-nav-group"><a href="{{link.route}}">{{link.title}}</a><button type="button" class="pro-nav-disclose" aria-expanded="false" aria-label="Expand {{link.title}} submenu">{{ icon('chevron-down', cls='sf-tp-icon--sm') }}</button><div class="pro-nav-children" hidden>{% for child in link.children %}<a href="{{child.route}}">{{child.title}}</a>{% for leaf in child.children %}<a class="pro-nav-leaf" href="{{leaf.route}}">— {{leaf.title}}</a>{% endfor %}{% endfor %}</div></div>{% else %}<a href="{{link.route}}">{{link.title}}</a>{% endif %}{% endfor %}{% if chrome.header.show_search %}<a class="pro-search-link" href="/search/">Search {{ icon('search', cls='sf-tp-icon--sm') }}</a>{% endif %}</nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">Products {{ icon('chevron-down', cls='sf-tp-icon--sm') }}</button>{% if chrome_is_custom %}{% if chrome.header.show_cta %}<a href="{{chrome.header.cta_href}}" class="sf-tp-btn pro-header-cta">{{chrome.header.cta_text}} <span>{{ icon('arrow-right') }}</span></a>{% endif %}{% else %}<a href="/contact/" class="sf-tp-btn pro-header-cta">Request a Quote <span>{{ icon('arrow-right') }}</span></a>{% endif %}</div>{% if categories %}<div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>Product categories</span><div>{% for cat in categories[:12] %}<a href="{{cat.url}}">{{cat.name}} {{ icon('arrow-right') }}</a>{% endfor %}</div></div>{% endif %}</header>
|
||||||
{% if not designer_replace %}{% include 'trade-pro/pages/' ~ kind ~ '.html' %}{% endif %}
|
{% if not designer_replace %}{% include 'trade-pro/pages/' ~ kind ~ '.html' %}{% endif %}
|
||||||
|
|
||||||
{% if has_custom and kind!='notfound' %}<main id="main-content" class="sf-canvas pro-custom-content">{{custom_blocks}}</main>{% else %}<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>{% endif %}
|
{% if has_custom and kind!='notfound' %}<main id="main-content" class="sf-canvas pro-custom-content">{{custom_blocks}}</main>{% else %}<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>{% endif %}
|
||||||
|
|||||||
@@ -587,11 +587,20 @@ body.sf-theme-trade-pro {
|
|||||||
|
|
||||||
/* ── 图标(Task 9 SVG 宏库:内联线条图标的对齐与间距)── */
|
/* ── 图标(Task 9 SVG 宏库:内联线条图标的对齐与间距)── */
|
||||||
|
|
||||||
/* 基线对齐:inline 上下文垂直居中,flex 上下文不被压缩换行 */
|
/* 基线对齐:inline 上下文垂直居中,flex 上下文不被压缩换行。
|
||||||
|
尺寸在 CSS 层固定(Safari 等引擎不支持 width/height 属性中的 var(),
|
||||||
|
属性值仅作无 CSS 场景的渐进增强;CSS 覆盖属性保证跨引擎一致) */
|
||||||
.sf-tp-icon {
|
.sf-tp-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
width: var(--sf-tp-icon-size-md, 20px);
|
||||||
|
height: var(--sf-tp-icon-size-md, 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-tp-icon--sm {
|
||||||
|
width: var(--sf-tp-icon-size-sm, 16px);
|
||||||
|
height: var(--sf-tp-icon-size-sm, 16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 按钮内紧跟文字的图标留出呼吸空间(flex gap 之外的显式间距,
|
/* 按钮内紧跟文字的图标留出呼吸空间(flex gap 之外的显式间距,
|
||||||
@@ -617,7 +626,8 @@ body.sf-theme-trade-pro {
|
|||||||
.sf-theme-trade-pro .pro-filter-search-box .sf-tp-icon {
|
.sf-theme-trade-pro .pro-filter-search-box .sf-tp-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset-inline-start: var(--sf-tp-space-3);
|
inset-inline-start: var(--sf-tp-space-3);
|
||||||
inset-block-end: var(--sf-tp-space-3);
|
inset-block-start: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
color: var(--sf-tp-color-text-muted);
|
color: var(--sf-tp-color-text-muted);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user