The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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.