The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

modernize-icon-system.md 18 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. ---
  2. sessionId: session-261002-165538-pjxq
  3. ---
  4. # Requirements
  5. ### Overview & Goals
  6. The objective of this initiative is to replace the fragmented, ad-hoc SVG icon implementations in `src/components/icons/` with a high-utility, predictable, and performant Vue 3 icon architecture.
  7. Currently, icons in the project are implemented with substantial inconsistencies:
  8. - Several icons wrap raw SVG markup inside container `<div>` tags with hardcoded layout properties (e.g., `class="h-4 w-4 cursor-pointer mx-2"`).
  9. - Other icons render raw SVG tags with inconsistent prop contracts (e.g., `:box="96"` vs `:viewBox="36"` vs `:width="20"` vs hardcoded inline styles).
  10. - Fills, strokes, and hover behaviors are frequently hardcoded in scoped CSS or inline SVG attributes, preventing utility classes (Tailwind CSS `text-*`, `fill-current`, `stroke-current`, `size-*`) from controlling colors and dimensions naturally.
  11. This proposal establishes a standardized, tree-shakeable, and type-safe icon system designed to maximize developer throughput, eliminate styling friction, reduce bundle waste, and ensure complete accessibility compliance.
  12. ### Scope
  13. #### In Scope
  14. - **Foundation Layer**: Create an extensible, typed `IconBase.vue` component and `types.ts` defining standard icon contracts (`IconName`, `IconSize`, sizing scales, accessibility attributes).
  15. - **Icon Normalization**: Refactor all 14 existing icon components (`Abstain`, `Arrow`, `Cancel`, `Confirm`, `DownArrow`, `Download`, `Filter`, `Identification`, `Menu`, `Plus`, `SpeechBubbles`, `Stack`, `ThumbsDown`, `ThumbsUp`) into pure SVG components using `currentColor` and consistent viewBox geometries.
  16. - **Dynamic Registry**: Create a unified `AppIcon.vue` for dynamic runtime icon rendering and `src/components/icons/index.ts` for clean tree-shakeable named exports.
  17. - **Call-Site Migration**: Refactor all 8 consuming components and views (`Sidebar.vue`, `ProposalContainer.vue`, `ProposalDetail.vue`, `ProposalExportCard.vue`, `ProposalVotingCard.vue`, `VotingControls.vue`, `AmendmentCarousel.vue`, `HomeView.vue`) to use the new icon interfaces and standard Tailwind classes.
  18. - **Build & Quality Assurance**: Ensure zero TypeScript errors (`vue-tsc`), clean Vite builds, and consistent rendering across light/dark backgrounds and responsive viewport sizes.
  19. #### Out of Scope
  20. - Introducing external heavyweight icon packages or third-party web font libraries that would increase bundle size or external network dependencies.
  21. - Modifying non-icon business logic, wallet integrations, or backend API service definitions.
  22. ### User Stories
  23. - **As a Developer**, I want all icons to behave as first-class Vue components with uniform props and `currentColor` support so that I can style their size, color, and hover states directly using Tailwind CSS without fighting inner scoped CSS or arbitrary wrapper `<div>`s.
  24. - **As a Developer**, I want autocompletion and type checking when specifying icon names so that runtime icon typos and missing assets are caught at compile time.
  25. - **As an End User**, I want all icons to render crisply, scale smoothly across devices, and support screen readers properly so that UI interactions are fast, intuitive, and accessible.
  26. ### Functional Requirements
  27. - **FR-1: Standardized Root Element**: Every individual icon component must render a semantic `<svg>` root element without superfluous wrapper `<div>` elements.
  28. - **FR-2: Sizing Flexibility**: Icons must accept standardized size presets (`'xs' | 'sm' | 'md' | 'lg' | 'xl'`), numerical/pixel dimensions, or inherit sizing directly from standard Tailwind classes (`size-4`, `w-5 h-5`).
  29. - **FR-3: Color & Theme Inheritance**: Icons must inherit foreground colors from their parent container by default via `fill="currentColor"` or `stroke="currentColor"`, allowing instant theme adaptations (`text-slate-500`, `hover:text-green-500`, `fill-amber-500`).
  30. - **FR-4: Dynamic & Direct Imports**: The system must support direct named component imports (e.g., `import { IconFilter } from '@/components/icons'`) and dynamic component resolution (e.g., `<AppIcon name="filter" />`).
  31. - **FR-5: Accessibility Support**: Decorative icons must default to `aria-hidden="true"`, while standalone interactive or informational icons must support customizable `title` and `role="img"` attributes.
  32. ### Non-Functional Requirements
  33. - **Performance**: Zero runtime overhead from external bundle bloat; SVG trees must remain tree-shakeable and lightweight.
  34. - **Type Safety**: Fully typed with TypeScript 5+ and Vue 3 `<script setup>` compiler macros.
  35. - **Maintainability**: Clear separation of concern between icon vector geometries and presentation styles. Adding a new icon must require minimal boilerplate.
  36. # Technical Design
  37. ### Current Implementation
  38. The current codebase contains 14 icon files located in `src/components/icons/`:
  39. - `DownArrow.vue`, `Filter.vue`, `Identification.vue`, `Menu.vue`, `Plus.vue`, and `SpeechBubbles.vue` contain outer `<div class="h-4 w-4 cursor-pointer mx-2">` wrappers, hardcoding margins, cursor behavior, and dimensions inside the icon definition.
  40. - `Abstain.vue`, `Cancel.vue`, and `Confirm.vue` define custom `withDefaults(defineProps<{ box?: number; width?: number | string; height?: number | string }>())` props but hardcode geometry coordinates.
  41. - `Stack.vue` defines `:viewBox="0 0 ${viewBox} ${viewBox}"` (defaulting to 90) but hardcodes `style="width: 20px; height: 20px;"` and `fill="white"`.
  42. - `Arrow.vue` hardcodes `style="width: 10px; height: 20px;"`.
  43. - `SpeechBubbles.vue` and `DownArrow.vue` include scoped CSS with ID selectors and `!important` color overrides (`#down-arrow:hover`, `#speech-bubbles:hover`), preventing Tailwind styling from parent components.
  44. - Consuming components (`VotingControls.vue`, `ProposalVotingCard.vue`, `ProposalExportCard.vue`, `AmendmentCarousel.vue`, `Sidebar.vue`, `ProposalContainer.vue`, `ProposalDetail.vue`, `HomeView.vue`) use incompatible styling workarounds (e.g., manual `:box="96"`, inline style overrides, or duplicate Tailwind wrapper classes).
  45. ### Key Decisions
  46. 1. **First-Class Vue SVG Components with `IconBase` Wrapper**:
  47. - *Decision*: Standardize each icon as a dedicated Vue component composed around a lightweight `IconBase.vue` (or direct standardized `<svg>` element) using `currentColor`.
  48. - *Rationale*: Eliminates unwanted DOM nesting (`<div>` wrapping `<svg>`), allows full Tailwind utility class passthrough (`class="..."` directly targets `<svg>`), and ensures maximum rendering performance and tree-shakability without external package bloat.
  49. 2. **Unified `AppIcon.vue` Dynamic Component**:
  50. - *Decision*: Provide an `AppIcon.vue` component that maps an `IconName` string union to the corresponding icon component.
  51. - *Rationale*: Gives developers the choice between static named component imports (best for explicit, statically analyzable components) and dynamic string-based icon rendering (best for config-driven UI, tables, or iteration).
  52. 3. **Tailwind-First Sizing & Color Design**:
  53. - *Decision*: Default icon dimensions to `1em` (or preset sizes) and `currentColor` fill/stroke, allowing standard Tailwind classes (`size-4`, `text-slate-400`, `hover:text-amber-500`) to govern appearance effortlessly.
  54. - *Rationale*: Maximizes developer utility by keeping icon styling consistent with all other UI components in Tailwind CSS.
  55. ### Proposed Architecture & Flow
  56. ```mermaid
  57. graph TD
  58. subgraph Consumers
  59. A[Sidebar.vue]
  60. B[VotingControls.vue]
  61. C[ProposalVotingCard.vue]
  62. D[AmendmentCarousel.vue]
  63. E[ProposalContainer.vue]
  64. F[Other Views / Modals]
  65. end
  66. subgraph Icon System Layer
  67. G[AppIcon.vue Dynamic Component]
  68. H[Named Exports in icons/index.ts]
  69. I[IconBase.vue Core Sizing & A11y Wrapper]
  70. end
  71. subgraph Icon Definitions
  72. J[IconAbstain.vue]
  73. K[IconConfirm.vue]
  74. L[IconCancel.vue]
  75. M[IconFilter.vue]
  76. N[IconDownload.vue]
  77. O[IconIdentification.vue]
  78. P[Other 8 SVG Icons...]
  79. end
  80. A & B & C & D & E & F -->|Static Named Import| H
  81. A & B & C & D & E & F -->|Dynamic Name Prop| G
  82. G --> H
  83. H --> J & K & L & M & N & O & P
  84. J & K & L & M & N & O & P -->|Composes / Inherits| I
  85. ```
  86. ### Data Models / Contracts
  87. #### `src/components/icons/types.ts`
  88. ```typescript
  89. export type IconName =
  90. | 'abstain'
  91. | 'arrow'
  92. | 'cancel'
  93. | 'confirm'
  94. | 'down-arrow'
  95. | 'download'
  96. | 'filter'
  97. | 'identification'
  98. | 'menu'
  99. | 'plus'
  100. | 'speech-bubbles'
  101. | 'stack'
  102. | 'thumbs-down'
  103. | 'thumbs-up';
  104. export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number | string;
  105. export interface IconBaseProps {
  106. size?: IconSize;
  107. viewBox?: string;
  108. title?: string;
  109. ariaHidden?: boolean;
  110. role?: string;
  111. }
  112. ```
  113. #### `src/components/icons/IconBase.vue`
  114. ```vue
  115. <template>
  116. <svg
  117. xmlns="http://www.w3.org/2000/svg"
  118. :viewBox="viewBox"
  119. :width="computedSize"
  120. :height="computedSize"
  121. :aria-hidden="title ? undefined : ariaHidden"
  122. :aria-label="title"
  123. :role="title ? 'img' : (role || 'presentation')"
  124. class="inline-block shrink-0 align-middle select-none fill-current"
  125. >
  126. <title v-if="title">{{ title }}</title>
  127. <slot />
  128. </svg>
  129. </template>
  130. <script setup lang="ts">
  131. import { computed } from 'vue';
  132. import type { IconBaseProps } from './types';
  133. const props = withDefaults(defineProps<IconBaseProps>(), {
  134. viewBox: '0 0 24 24',
  135. ariaHidden: true,
  136. size: 'md',
  137. });
  138. const sizeMap: Record<string, string> = {
  139. xs: '12',
  140. sm: '16',
  141. md: '20',
  142. lg: '24',
  143. xl: '32',
  144. };
  145. const computedSize = computed(() => {
  146. if (typeof props.size === 'number') return props.size;
  147. return sizeMap[props.size] || props.size;
  148. });
  149. </script>
  150. ```
  151. ### File Structure & Changes
  152. ```
  153. src/
  154. ├── components/
  155. │ ├── icons/
  156. │ │ ├── types.ts # [NEW] IconName, IconSize, Prop interfaces
  157. │ │ ├── IconBase.vue # [NEW] Common SVG wrapper component
  158. │ │ ├── AppIcon.vue # [NEW] Dynamic icon component resolver
  159. │ │ ├── index.ts # [NEW] Centralized export entry point
  160. │ │ ├── Abstain.vue # [REFACTORED] Standardized SVG component
  161. │ │ ├── Arrow.vue # [REFACTORED] Standardized SVG component
  162. │ │ ├── Cancel.vue # [REFACTORED] Standardized SVG component
  163. │ │ ├── Confirm.vue # [REFACTORED] Standardized SVG component
  164. │ │ ├── DownArrow.vue # [REFACTORED] Removed wrapper div & CSS overrides
  165. │ │ ├── Download.vue # [REFACTORED] Standardized SVG component
  166. │ │ ├── Filter.vue # [REFACTORED] Removed wrapper div & CSS overrides
  167. │ │ ├── Identification.vue # [REFACTORED] Removed wrapper div & standardized viewBox
  168. │ │ ├── Menu.vue # [REFACTORED] Removed wrapper div & standardized stroke/fill
  169. │ │ ├── Plus.vue # [REFACTORED] Removed wrapper div
  170. │ │ ├── SpeechBubbles.vue # [REFACTORED] Removed wrapper div & CSS overrides
  171. │ │ ├── Stack.vue # [REFACTORED] Standardized viewBox & removed hardcoded fill
  172. │ │ ├── ThumbsDown.vue # [REFACTORED] Standardized SVG component
  173. │ │ └── ThumbsUp.vue # [REFACTORED] Standardized SVG component
  174. │ ├── AmendmentCarousel.vue # [UPDATED] Clean icon props & Tailwind classes
  175. │ ├── ProposalContainer.vue # [UPDATED] Clean icon sizing without wrapper conflicts
  176. │ ├── ProposalDetail.vue # [UPDATED] Clean icon sizing without wrapper conflicts
  177. │ ├── ProposalExportCard.vue # [UPDATED] Clean icon props & transitions
  178. │ ├── ProposalVotingCard.vue # [UPDATED] Removed obsolete :box props; standardized colors
  179. │ ├── Sidebar.vue # [UPDATED] Replaced wrapped Filter with clean SVG icon
  180. │ └── VotingControls.vue # [UPDATED] Clean Tailwind fill & hover state integration
  181. └── views/
  182. └── HomeView.vue # [UPDATED] Clean Stack icon styling
  183. ```
  184. ### Risks & Mitigations
  185. - **Risk**: Visual regression if existing viewBox ratios or path alignments change during normalization.
  186. - *Mitigation*: Preserve the exact vector path strings and original coordinate spaces (`viewBox="0 0 30 30"`, `0 0 96 96"`, etc.) for each respective icon inside its component definition, ensuring 100% pixel-perfect output.
  187. - **Risk**: Missing layout spacing when removing wrapper divs (e.g. `mx-2`, `cursor-pointer`).
  188. - *Mitigation*: Move layout concerns (`mx-2`, `cursor-pointer`, flex alignments) directly to the consuming container or parent button elements at all call sites.
  189. # Testing
  190. ### Validation Approach
  191. Verification will follow a multi-tier approach to ensure structural, aesthetic, and functional correctness across the application:
  192. 1. **Static Type & Lint Validation**: Verify strict type conformity via TypeScript compiler (`vue-tsc -b`) to ensure no broken imports or invalid prop bindings exist.
  193. 2. **Component Integration & Build Validation**: Perform full production bundling (`vite build`) to confirm zero asset resolution issues or tree-shaking failures.
  194. 3. **Visual & Interaction Verification**: Validate icon rendering across all consuming views, checking sizing, color inheritance, hover states, and responsive behavior.
  195. ### Key Scenarios
  196. - **Scenario 1: Dynamic Icon Rendering (`AppIcon`)**:
  197. - Render `<AppIcon name="confirm" class="text-green-500" />` and `<AppIcon name="cancel" class="text-red-500" />`.
  198. - Expected: Correct SVGs render with specified colors, inheriting parent classes properly without extra container nodes.
  199. - **Scenario 2: Direct Component Import & Tailwind Utility Sizing**:
  200. - Render `<IconFilter class="size-4 text-white hover:text-slate-200" />` in `Sidebar.vue`.
  201. - Expected: Icon scales to 16x16px, displays in white, and transitions to light slate on hover without unwanted wrapper margins.
  202. - **Scenario 3: Interactive Voting Controls & Active States**:
  203. - In `VotingControls.vue` and `ProposalVotingCard.vue`, trigger approval and rejection vote states.
  204. - Expected: `Confirm` and `Cancel` icons respond dynamically to positive/negative vote states (`fill-green-500`, `fill-red-500`) with smooth transitions.
  205. - **Scenario 4: Carousel Navigation Chevrons**:
  206. - In `AmendmentCarousel.vue`, navigate through amendments using the left and right arrow buttons.
  207. - Expected: `Arrow` icon renders crisply, rotates correctly with Tailwind's `rotate-180` for left navigation, and responds to click events.
  208. ### Edge Cases
  209. - **Custom Dimension Overrides**: Passing explicit numeric `size={28}` or arbitrary Tailwind strings (`class="w-7 h-7"`) must scale SVG viewports smoothly without clipping.
  210. - **Accessibility Verification**: When `title` is supplied, `<svg>` must render a `<title>` node and set `role="img"`; when omitted, it must set `aria-hidden="true"`.
  211. - **Empty or Missing Styling**: Icons with no explicit color class must default to `currentColor`, seamlessly adopting surrounding text color.
  212. # Delivery Steps
  213. ### ✓ Step 1: Establish IconBase and type system
  214. A unified `IconBase.vue` component and type definitions are created to serve as the standardized foundation for all project icons.
  215. - Define TypeScript contracts and type definitions in `src/components/icons/types.ts` (`IconName`, `IconSize`, `IconBaseProps`).
  216. - Create `src/components/icons/IconBase.vue` rendering a semantic `<svg>` root element with default classes (`inline-block shrink-0 align-middle select-none`), standardized sizing mapping (`xs`: 12px, `sm`: 16px, `md`: 20px, `lg`: 24px, `xl`: 32px or custom dimensions), `currentColor` inheritance, and accessibility attributes (`aria-hidden`, `role="img"`, `title`).
  217. - Create `src/components/icons/AppIcon.vue` providing dynamic lookup and rendering of icons by name with full type safety and prop forwarding.
  218. ### ✓ Step 2: Refactor and normalize all icon components
  219. All 14 icon components are refactored into clean, single-element SVG components adhering to the standardized base interface.
  220. - Remove extraneous `<div>` wrapper containers and hardcoded margins (`mx-2`) from `DownArrow.vue`, `Filter.vue`, `Identification.vue`, `Menu.vue`, `Plus.vue`, and `SpeechBubbles.vue`.
  221. - Eliminate hardcoded inline styles (`style="fill:#ffffff"`, `style="width: 10px; height: 20px;"`) and scoped hover overrides (`#speech-bubbles:hover`, `#down-arrow:hover`) in favor of standard `currentColor` / Tailwind class pass-through.
  222. - Standardize viewBox definitions and geometry across `Abstain.vue`, `Arrow.vue`, `Cancel.vue`, `Confirm.vue`, `Download.vue`, `Stack.vue`, `ThumbsDown.vue`, `ThumbsUp.vue`, and other icons.
  223. - Create `src/components/icons/index.ts` exporting all icon components as named exports, the dynamic `AppIcon` component, and relevant TypeScript types for streamlined imports.
  224. ### ✓ Step 3: Migrate consuming components to modernized icons
  225. All existing UI components and views are updated to consume the normalized icon system cleanly with Tailwind styling.
  226. - Update `src/components/Sidebar.vue` to use `<Filter />` (or `<IconFilter />`) cleanly inside navigation buttons without unwanted wrapper margins.
  227. - Update `src/components/ProposalContainer.vue` and `src/components/ProposalDetail.vue` to render `Identification` with proper dimensions and colors.
  228. - Update `src/components/VotingControls.vue` and `src/components/ProposalVotingCard.vue` to remove obsolete `:box` / `:width` / `:height` props from `Confirm`, `Cancel`, and `Abstain`, styling them with Tailwind classes (`size-5 text-green-500 hover:text-green-600 fill-current`).
  229. - Update `src/components/AmendmentCarousel.vue` and `src/components/ProposalExportCard.vue` to use standard arrow and download icons with seamless transition and hover effects.
  230. - Update `src/views/HomeView.vue` to replace inline styles on `Stack` with standard component props/classes.
  231. ### ✓ Step 4: Verify build integrity, visual fidelity, and accessibility
  232. The modernized icon system is validated across TypeScript compilation, build pipeline, and UI rendering.
  233. - Execute `vue-tsc -b` and `vite build` to guarantee zero type errors or broken import references across the application.
  234. - Verify visual fidelity, color transitions, hover states, and responsive layout scaling across all pages (Home, Proposal Detail, Voting modal, Carousel).
  235. - Ensure accessibility tags (`aria-hidden="true"` on decorative icons and appropriate labels on interactive icon buttons) work correctly without DOM nesting violations.