style: fix user menu alignment and spacing, and codify UI standards in skill
This commit is contained in:
parent
d4c5dbfc57
commit
0ec2588735
40
.agent/skills/ui_standards/SKILL.md
Normal file
40
.agent/skills/ui_standards/SKILL.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: UI Design Standards
|
||||||
|
description: Rules and guidelines for consistent UI components including dropdowns, popovers, and list items.
|
||||||
|
---
|
||||||
|
|
||||||
|
# UI Design Standards
|
||||||
|
|
||||||
|
This skill provides guidelines for building consistent, accessible, and high-quality UI components within the codebase.
|
||||||
|
|
||||||
|
## 1. Popovers and Dropdowns (弹窗与下拉规范)
|
||||||
|
|
||||||
|
### Alignment (定位逻辑)
|
||||||
|
- **Right Edge Alignment**: For triggers located on the right side of the screen or navigation bar, the associated dropdown/popover MUST be right-aligned with the trigger.
|
||||||
|
- In Radix UI / shadcn: Use `align="end"`.
|
||||||
|
- In CSS/Tailwind: Use `absolute right-0`.
|
||||||
|
- **Vertical Spacing**: Maintain a consistent vertical offset from the trigger, typically `8px` (`mt-2` or `sideOffset={8}`).
|
||||||
|
|
||||||
|
### Layering and Visual Hierarchy (层级管理)
|
||||||
|
- **Z-Index**: Always explicitly declare a `z-index` of `50` or higher for floating UI elements to ensure they appear above all other content.
|
||||||
|
- **Background Integrity**: Drodown containers should use opaque background colors (e.g., `bg-white` or theme-defined `bg-surface`) to prevent "see-through" visual noise from underlying content. Avoid excessive transparency/blur if it compromises readability.
|
||||||
|
- **Shadows**: Use distinct shadows (e.g., `shadow-md` or `shadow-xl`) to provide depth.
|
||||||
|
|
||||||
|
### Clipping Prevention (溢出处理)
|
||||||
|
- Use Portals (e.g., `DropdownMenu.Portal`) to render floating content into the `document.body`. This prevents the menu from being clipped by parents with `overflow: hidden`.
|
||||||
|
|
||||||
|
## 2. List Items and Components (列表项规范)
|
||||||
|
|
||||||
|
### Layout and Alignment (对齐规范)
|
||||||
|
- **Flexbox**: Use `flex items-center` for all list items that include both an icon and a text label.
|
||||||
|
- **Icon Spacing**: Maintain a standard horizontal gap between icons and labels (recommended: `gap-3` or `12px`).
|
||||||
|
|
||||||
|
### Component Integrity (防压缩与自适应)
|
||||||
|
- **Icon Shrinking**: Icons within flex containers MUST have `flex-shrink: 0` (Tailwind: `shrink-0`) to prevent them from distorting when the container is narrow.
|
||||||
|
- **Minimum Width**: Containers displaying variable-length text (like user emails) should have a reasonable `min-width` (e.g., `min-w-[200px]`) to ensure comfortable display.
|
||||||
|
|
||||||
|
## 3. Interaction and Accessibility
|
||||||
|
|
||||||
|
- **Keyboard Support**: Ensure dropdowns support `Esc` to close and allow keyboard navigation (Tab/Arrows).
|
||||||
|
- **Outside Clicks**: Implement "click outside to close" logic for all popovers.
|
||||||
|
- **Motion**: Use subtle entry/exit animations (e.g., 120ms fade and scale). Respect `prefers-reduced-motion`.
|
||||||
12
.cursorrules
Normal file
12
.cursorrules
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# UI Development Rules
|
||||||
|
|
||||||
|
## 1. 弹窗与下拉规范 (Popovers & Dropdowns)
|
||||||
|
- **定位逻辑**:除非特殊说明,右上角的触发器对应的弹出层必须强制 `right-0` (或 `align="end"`) 对齐。
|
||||||
|
- **层级管理**:所有 Floating UI 必须显式声明 `z-[50+]` 以防被 Content Card 遮挡或透视。
|
||||||
|
- **背景显示**:下拉容器背景应使用不透明色(如 `bg-white` 或主题定义的 `bg-surface`),避免透明度导致的视觉干扰。
|
||||||
|
|
||||||
|
## 2. 列表项规范 (List Items)
|
||||||
|
- **对齐**:所有带有图标的列表项必须使用 `flex items-center`。
|
||||||
|
- **防止重叠**:图标与文字之间应保持足够的间距(推荐 `gap-3` 或 `12px`)。
|
||||||
|
- **防压缩**:图标必须带有 `flex-shrink: 0` (或 Tailwind 的 `shrink-0`),防止在容器宽度不足时图标变形。
|
||||||
|
- **最小宽度**:涉及用户信息的容器应设置合理的 `min-width` (如 `min-w-[200px]`) 以保证长文字内容正常显示。
|
||||||
@ -275,7 +275,7 @@ export default function UnifiedNavigation() {
|
|||||||
|
|
||||||
<div className="hidden flex-1 items-center justify-end gap-3 lg:flex">
|
<div className="hidden flex-1 items-center justify-end gap-3 lg:flex">
|
||||||
{user ? (
|
{user ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3 relative">
|
||||||
<DropdownMenu.Root open={accountMenuOpen} onOpenChange={setAccountMenuOpen}>
|
<DropdownMenu.Root open={accountMenuOpen} onOpenChange={setAccountMenuOpen}>
|
||||||
<DropdownMenu.Trigger asChild>
|
<DropdownMenu.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
@ -291,7 +291,7 @@ export default function UnifiedNavigation() {
|
|||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
align="end"
|
align="end"
|
||||||
sideOffset={8}
|
sideOffset={8}
|
||||||
className="z-50 min-w-[220px] overflow-hidden rounded-[12px] border border-surface-border bg-surface/95 p-1 shadow-shadow-md backdrop-blur-sm animate-in fade-in zoom-in-95 duration-[120ms] data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:zoom-out-95 motion-reduce:animate-none"
|
className="z-50 min-w-[220px] overflow-hidden rounded-[12px] border border-surface-border bg-surface p-1 shadow-shadow-md animate-in fade-in zoom-in-95 duration-[120ms] data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:zoom-out-95 motion-reduce:animate-none"
|
||||||
>
|
>
|
||||||
<div className="px-4 py-3 border-b border-surface-border/50 mb-1">
|
<div className="px-4 py-3 border-b border-surface-border/50 mb-1">
|
||||||
<p className="text-sm font-semibold text-text leading-none mb-1.5">
|
<p className="text-sm font-semibold text-text leading-none mb-1.5">
|
||||||
@ -311,14 +311,14 @@ export default function UnifiedNavigation() {
|
|||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={`flex h-[38px] items-center gap-2.5 px-3 rounded-lg text-[13px] font-medium transition-all group select-none ${item.key === 'logout'
|
className={`flex h-[38px] items-center gap-3 px-3 rounded-lg text-[13px] font-medium transition-all group select-none ${item.key === 'logout'
|
||||||
? "text-rose-500 hover:bg-rose-500/10 hover:text-rose-600 focus:bg-rose-500/10 focus:text-rose-600"
|
? "text-rose-500 hover:bg-rose-500/10 hover:text-rose-600 focus:bg-rose-500/10 focus:text-rose-600"
|
||||||
: "text-text-muted hover:bg-primary/10 hover:text-primary focus:bg-primary/10 focus:text-primary"
|
: "text-text-muted hover:bg-primary/10 hover:text-primary focus:bg-primary/10 focus:text-primary"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setAccountMenuOpen(false)}
|
onClick={() => setAccountMenuOpen(false)}
|
||||||
>
|
>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<item.icon className={`w-4 h-4 opacity-70 group-hover:opacity-100 transition-opacity ${item.key === 'logout' ? 'text-rose-500' : 'text-current'}`} />
|
<item.icon className={`w-4 h-4 shrink-0 opacity-70 group-hover:opacity-100 transition-opacity ${item.key === 'logout' ? 'text-rose-500' : 'text-current'}`} />
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
{typeof item.label === "function"
|
{typeof item.label === "function"
|
||||||
@ -332,33 +332,41 @@ export default function UnifiedNavigation() {
|
|||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
<LanguageToggle />
|
||||||
|
<ReleaseChannelSelector
|
||||||
|
selected={selectedChannels}
|
||||||
|
onToggle={toggleChannel}
|
||||||
|
variant="icon"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-3 text-sm font-medium text-text-muted">
|
<>
|
||||||
<Link
|
<div className="flex items-center gap-3 text-sm font-medium text-text-muted">
|
||||||
href="/login"
|
<Link
|
||||||
className="text-sm opacity-80 transition hover:text-primary hover:opacity-100"
|
href="/login"
|
||||||
>
|
className="text-sm opacity-80 transition hover:text-primary hover:opacity-100"
|
||||||
{nav.account.login}
|
>
|
||||||
</Link>
|
{nav.account.login}
|
||||||
<span
|
</Link>
|
||||||
className="h-3 w-px bg-surface-border"
|
<span
|
||||||
aria-hidden="true"
|
className="h-3 w-px bg-surface-border"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
href="/register"
|
||||||
|
className="rounded-md border border-surface-border px-3 py-1 text-primary transition hover:border-primary/40 hover:bg-surface-muted"
|
||||||
|
>
|
||||||
|
{nav.account.register}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<LanguageToggle />
|
||||||
|
<ReleaseChannelSelector
|
||||||
|
selected={selectedChannels}
|
||||||
|
onToggle={toggleChannel}
|
||||||
|
variant="icon"
|
||||||
/>
|
/>
|
||||||
<Link
|
</>
|
||||||
href="/register"
|
|
||||||
className="rounded-md border border-surface-border px-3 py-1 text-primary transition hover:border-primary/40 hover:bg-surface-muted"
|
|
||||||
>
|
|
||||||
{nav.account.register}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<LanguageToggle />
|
|
||||||
<ReleaseChannelSelector
|
|
||||||
selected={selectedChannels}
|
|
||||||
onToggle={toggleChannel}
|
|
||||||
variant="icon"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user