| sessionId |
|---|
| session-261002-165538-pjxq |
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.
Currently, icons in the project are implemented with substantial inconsistencies:
<div> tags with hardcoded layout properties (e.g., class="h-4 w-4 cursor-pointer mx-2").:box="96" vs :viewBox="36" vs :width="20" vs hardcoded inline styles).text-*, fill-current, stroke-current, size-*) from controlling colors and dimensions naturally.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.
IconBase.vue component and types.ts defining standard icon contracts (IconName, IconSize, sizing scales, accessibility attributes).Abstain, Arrow, Cancel, Confirm, DownArrow, Download, Filter, Identification, Menu, Plus, SpeechBubbles, Stack, ThumbsDown, ThumbsUp) into pure SVG components using currentColor and consistent viewBox geometries.AppIcon.vue for dynamic runtime icon rendering and src/components/icons/index.ts for clean tree-shakeable named exports.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.vue-tsc), clean Vite builds, and consistent rendering across light/dark backgrounds and responsive viewport sizes.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.<svg> root element without superfluous wrapper <div> elements.'xs' | 'sm' | 'md' | 'lg' | 'xl'), numerical/pixel dimensions, or inherit sizing directly from standard Tailwind classes (size-4, w-5 h-5).fill="currentColor" or stroke="currentColor", allowing instant theme adaptations (text-slate-500, hover:text-green-500, fill-amber-500).import { IconFilter } from '@/components/icons') and dynamic component resolution (e.g., <AppIcon name="filter" />).aria-hidden="true", while standalone interactive or informational icons must support customizable title and role="img" attributes.<script setup> compiler macros.The current codebase contains 14 icon files located in src/components/icons/:
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.Abstain.vue, Cancel.vue, and Confirm.vue define custom withDefaults(defineProps<{ box?: number; width?: number | string; height?: number | string }>()) props but hardcode geometry coordinates.Stack.vue defines :viewBox="0 0 ${viewBox} ${viewBox}" (defaulting to 90) but hardcodes style="width: 20px; height: 20px;" and fill="white".Arrow.vue hardcodes style="width: 10px; height: 20px;".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.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).IconBase Wrapper:
IconBase.vue (or direct standardized <svg> element) using currentColor.<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.AppIcon.vue Dynamic Component:
AppIcon.vue component that maps an IconName string union to the corresponding icon component.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.graph TD
subgraph Consumers
A[Sidebar.vue]
B[VotingControls.vue]
C[ProposalVotingCard.vue]
D[AmendmentCarousel.vue]
E[ProposalContainer.vue]
F[Other Views / Modals]
end
subgraph Icon System Layer
G[AppIcon.vue Dynamic Component]
H[Named Exports in icons/index.ts]
I[IconBase.vue Core Sizing & A11y Wrapper]
end
subgraph Icon Definitions
J[IconAbstain.vue]
K[IconConfirm.vue]
L[IconCancel.vue]
M[IconFilter.vue]
N[IconDownload.vue]
O[IconIdentification.vue]
P[Other 8 SVG Icons...]
end
A & B & C & D & E & F -->|Static Named Import| H
A & B & C & D & E & F -->|Dynamic Name Prop| G
G --> H
H --> J & K & L & M & N & O & P
J & K & L & M & N & O & P -->|Composes / Inherits| I
src/components/icons/types.tsexport type IconName =
| 'abstain'
| 'arrow'
| 'cancel'
| 'confirm'
| 'down-arrow'
| 'download'
| 'filter'
| 'identification'
| 'menu'
| 'plus'
| 'speech-bubbles'
| 'stack'
| 'thumbs-down'
| 'thumbs-up';
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number | string;
export interface IconBaseProps {
size?: IconSize;
viewBox?: string;
title?: string;
ariaHidden?: boolean;
role?: string;
}
src/components/icons/IconBase.vue<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:viewBox="viewBox"
:width="computedSize"
:height="computedSize"
:aria-hidden="title ? undefined : ariaHidden"
:aria-label="title"
:role="title ? 'img' : (role || 'presentation')"
class="inline-block shrink-0 align-middle select-none fill-current"
>
<title v-if="title">{{ title }}</title>
<slot />
</svg>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { IconBaseProps } from './types';
const props = withDefaults(defineProps<IconBaseProps>(), {
viewBox: '0 0 24 24',
ariaHidden: true,
size: 'md',
});
const sizeMap: Record<string, string> = {
xs: '12',
sm: '16',
md: '20',
lg: '24',
xl: '32',
};
const computedSize = computed(() => {
if (typeof props.size === 'number') return props.size;
return sizeMap[props.size] || props.size;
});
</script>
src/
├── components/
│ ├── icons/
│ │ ├── types.ts # [NEW] IconName, IconSize, Prop interfaces
│ │ ├── IconBase.vue # [NEW] Common SVG wrapper component
│ │ ├── AppIcon.vue # [NEW] Dynamic icon component resolver
│ │ ├── index.ts # [NEW] Centralized export entry point
│ │ ├── Abstain.vue # [REFACTORED] Standardized SVG component
│ │ ├── Arrow.vue # [REFACTORED] Standardized SVG component
│ │ ├── Cancel.vue # [REFACTORED] Standardized SVG component
│ │ ├── Confirm.vue # [REFACTORED] Standardized SVG component
│ │ ├── DownArrow.vue # [REFACTORED] Removed wrapper div & CSS overrides
│ │ ├── Download.vue # [REFACTORED] Standardized SVG component
│ │ ├── Filter.vue # [REFACTORED] Removed wrapper div & CSS overrides
│ │ ├── Identification.vue # [REFACTORED] Removed wrapper div & standardized viewBox
│ │ ├── Menu.vue # [REFACTORED] Removed wrapper div & standardized stroke/fill
│ │ ├── Plus.vue # [REFACTORED] Removed wrapper div
│ │ ├── SpeechBubbles.vue # [REFACTORED] Removed wrapper div & CSS overrides
│ │ ├── Stack.vue # [REFACTORED] Standardized viewBox & removed hardcoded fill
│ │ ├── ThumbsDown.vue # [REFACTORED] Standardized SVG component
│ │ └── ThumbsUp.vue # [REFACTORED] Standardized SVG component
│ ├── AmendmentCarousel.vue # [UPDATED] Clean icon props & Tailwind classes
│ ├── ProposalContainer.vue # [UPDATED] Clean icon sizing without wrapper conflicts
│ ├── ProposalDetail.vue # [UPDATED] Clean icon sizing without wrapper conflicts
│ ├── ProposalExportCard.vue # [UPDATED] Clean icon props & transitions
│ ├── ProposalVotingCard.vue # [UPDATED] Removed obsolete :box props; standardized colors
│ ├── Sidebar.vue # [UPDATED] Replaced wrapped Filter with clean SVG icon
│ └── VotingControls.vue # [UPDATED] Clean Tailwind fill & hover state integration
└── views/
└── HomeView.vue # [UPDATED] Clean Stack icon styling
viewBox="0 0 30 30", 0 0 96 96", etc.) for each respective icon inside its component definition, ensuring 100% pixel-perfect output.mx-2, cursor-pointer).
mx-2, cursor-pointer, flex alignments) directly to the consuming container or parent button elements at all call sites.Verification will follow a multi-tier approach to ensure structural, aesthetic, and functional correctness across the application:
vue-tsc -b) to ensure no broken imports or invalid prop bindings exist.vite build) to confirm zero asset resolution issues or tree-shaking failures.AppIcon):
<AppIcon name="confirm" class="text-green-500" /> and <AppIcon name="cancel" class="text-red-500" />.<IconFilter class="size-4 text-white hover:text-slate-200" /> in Sidebar.vue.VotingControls.vue and ProposalVotingCard.vue, trigger approval and rejection vote states.Confirm and Cancel icons respond dynamically to positive/negative vote states (fill-green-500, fill-red-500) with smooth transitions.AmendmentCarousel.vue, navigate through amendments using the left and right arrow buttons.Arrow icon renders crisply, rotates correctly with Tailwind’s rotate-180 for left navigation, and responds to click events.size={28} or arbitrary Tailwind strings (class="w-7 h-7") must scale SVG viewports smoothly without clipping.title is supplied, <svg> must render a <title> node and set role="img"; when omitted, it must set aria-hidden="true".currentColor, seamlessly adopting surrounding text color.A unified IconBase.vue component and type definitions are created to serve as the standardized foundation for all project icons.
src/components/icons/types.ts (IconName, IconSize, IconBaseProps).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).src/components/icons/AppIcon.vue providing dynamic lookup and rendering of icons by name with full type safety and prop forwarding.All 14 icon components are refactored into clean, single-element SVG components adhering to the standardized base interface.
<div> wrapper containers and hardcoded margins (mx-2) from DownArrow.vue, Filter.vue, Identification.vue, Menu.vue, Plus.vue, and SpeechBubbles.vue.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.Abstain.vue, Arrow.vue, Cancel.vue, Confirm.vue, Download.vue, Stack.vue, ThumbsDown.vue, ThumbsUp.vue, and other icons.src/components/icons/index.ts exporting all icon components as named exports, the dynamic AppIcon component, and relevant TypeScript types for streamlined imports.All existing UI components and views are updated to consume the normalized icon system cleanly with Tailwind styling.
src/components/Sidebar.vue to use <Filter /> (or <IconFilter />) cleanly inside navigation buttons without unwanted wrapper margins.src/components/ProposalContainer.vue and src/components/ProposalDetail.vue to render Identification with proper dimensions and colors.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).src/components/AmendmentCarousel.vue and src/components/ProposalExportCard.vue to use standard arrow and download icons with seamless transition and hover effects.src/views/HomeView.vue to replace inline styles on Stack with standard component props/classes.The modernized icon system is validated across TypeScript compilation, build pipeline, and UI rendering.
vue-tsc -b and vite build to guarantee zero type errors or broken import references across the application.aria-hidden="true" on decorative icons and appropriate labels on interactive icon buttons) work correctly without DOM nesting violations.