# Component Taxonomy: 4-Layer Architecture for OMS·WMS·ERP SPA **Status**: DRAFT (Phase 0, requires Figma finalization) **Date**: 2026-07-26 **Related**: [ADR-001 (Spec 65)](spec/65_adr_001_monolithic_spa_architecture.md), [OpenAPI (Spec 63)](spec/63_oms_wms_erp_api_openapi.yaml) --- ## Overview **Component Hierarchy**: 4 layers, 65 total components across OMS/WMS/ERP domains ``` ┌─────────────────────────────────────────────────────────────────┐ │ Layer 4: Business Composite (11 CRUD Workflows) │ │ └─ OrderForm, InventoryTransferWizard, VoucherEditor, etc. │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 3: Domain Fields (12 Domain-Specific Inputs) │ │ └─ OrderLineField, InventoryField, VoucherLineField, etc. │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 2: Typed Fields (12 Type-Safe Inputs) │ │ └─ TextField, DateField, CurrencyField, StatusField, etc. │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 1: Primitives (30 UI Building Blocks) │ │ └─ Button, Input, Select, Table, Card, Badge, etc. │ └─────────────────────────────────────────────────────────────────┘ ``` **Design System**: Tabler UI (Bootstrap 5) + Storybook 7.0+ --- ## Layer 1: Primitive Components (30) ### Purpose Reusable UI elements with **zero business logic**, full accessibility (WCAG 2.1 AA), typed props, consistent behavior. ### Folder Structure ``` src/components/primitives/ ├─ Button/ │ ├─ ButtonBase.vue │ ├─ ButtonBase.stories.ts │ └─ ButtonBase.spec.ts ├─ Input/ │ ├─ InputBase.vue │ ├─ InputBase.stories.ts │ └─ InputBase.spec.ts ├─ Select/ │ ├─ SelectBase.vue │ ├─ SelectBase.stories.ts │ └─ SelectBase.spec.ts ├─ Table/ │ ├─ TableBase.vue │ ├─ TableBase.stories.ts │ └─ TableBase.spec.ts ├─ Card/ │ ├─ CardBase.vue │ └─ CardBase.stories.ts ├─ Badge/ ├─ Modal/ ├─ Checkbox/ ├─ Radio/ ├─ Textarea/ ├─ Pagination/ ├─ Alert/ ├─ Spinner/ ├─ Tooltip/ ├─ Dropdown/ ├─ Tabs/ ├─ Breadcrumb/ ├─ NavBar/ ├─ Sidebar/ ├─ Icon/ └─ Link/ ``` ### Component Specifications | Component | Props | Events | A11y | Story | |-----------|-------|--------|------|-------| | **ButtonBase** | variant (primary/secondary/danger), size (sm/md/lg), disabled, loading | click | aria-label, focus-visible | 12 stories | | **InputBase** | type (text/email/number), placeholder, value, disabled, error, required | input, change, blur | label + aria-describedby (error) | 8 stories | | **SelectBase** | options: Array<{value, label}>, value, disabled, multiple | change | aria-label, aria-expanded | 10 stories | | **TableBase** | columns: Array<{key, header, sortable}>, data: any[], onSort | row-click, sort | semantic , scope | 6 stories | | **CardBase** | title, subtitle, footer, clickable | click | semantic
| 5 stories | | **BadgeBase** | status (success/danger/warning/info), size | — | aria-label | 8 stories | | **ModalBase** | isOpen, title, onClose | close | role="dialog", focus-trap | 6 stories | | **CheckboxBase** | value, label, disabled, required | change | aria-label, aria-describedby | 6 stories | | **RadioBase** | name, options, value, disabled | change | role="radiogroup" | 5 stories | | **TextareaBase** | value, placeholder, rows, disabled, error | input, change | aria-describedby | 5 stories | | **PaginationBase** | currentPage, totalPages, onPageChange | page-change | aria-label (next/prev) | 4 stories | | **AlertBase** | type (success/error/warning), dismissible, onDismiss | dismiss | role="alert" | 8 stories | | **SpinnerBase** | size, color | — | aria-busy | 4 stories | | **TooltipBase** | text, position (top/bottom/left/right) | show, hide | aria-describedby | 5 stories | | **DropdownBase** | trigger, items: Array<{label, action}>, onSelect | select | role="menu", role="menuitem" | 6 stories | | **TabsBase** | tabs: Array<{id, label, disabled}>, activeId, onTabChange | tab-change | role="tablist", role="tab" | 6 stories | | **BreadcrumbBase** | items: Array<{label, href}> | navigate | aria-label | 3 stories | | **NavBarBase** | title, items: Array<{label, href}>, sticky | navigate | semantic