Files
portal/app/resources/js/plugins/vuetify.ts
T
Дмитрий 0832997b6e feat(icons): CTO-19 closed via Lucide migration (Vuetify custom IconSet)
Closes CTO-19 ⏸ from реестр v1.79 — иконочная система портала не была
подключена (`@mdi/font` отсутствовал в `package.json`, все `mdi-*`
рендерились пустыми glyph'ами).

PATH α (aliases-only, brand-compliant) approved заказчиком 13.05.2026
через `superpowers:brainstorming` → `superpowers:writing-plans` →
`superpowers:subagent-driven-development`:

— `npm i lucide-vue-next ^1.0.0` (~25-30 KB gzip tree-shakable)
— `app/resources/js/plugins/vuetify.ts`: custom `IconSet`
  (`liderraLucideSet`) с 103-entry `lucideMap`:
  · 78 user-grep'нутых mdi-* names из resources/js/**/*.vue
  · 25 Vuetify-internal defaults (pagination chevrons, v-checkbox
    squares, v-radio circles, v-select dropdown, date picker, paperclip)
— Fallback `HelpCircle` для unmapped
— 51 Vue/TS файл с `icon="mdi-*"` НЕ touched — semantic-ID via Lucide

CLAUDE.md §2 «Иконки: Lucide» бренд-spec compliance achieved.

VERIFICATION (comprehensive, 13.05.2026 day +1):
— vue-tsc 0 errors
— Pest --parallel --recreate-databases: **742/739/0/3**
— Vitest: 88 files / 683 passed / 3 skipped (baseline match)
— Vite build: exit 0, 3.52s
— Visual smoke 8 views via Playwright MCP — все glyph'ы рендерятся
— axe-core a11y scan /admin/billing: **0 iconography violations**
— Pagination + v-checkbox + v-radio fixes (Task 2.b extension)

РЕЕСТР v1.82 → v1.83:
— CTO-19 §3: ⏸ →  (Pravila §2.2 / §7.1 — явное «закрываем» получено)
— Сводка §0 CTO: 17/1⏸/1 P2 [?] → 18 /0⏸/0
— Сводка §0 Итого: 70/12⏸ → 71 /11 ⏸
— Header v1.82 → v1.83 + новый changelog block
— Footer v1.83 (match header)

CLAUDE.md §0 row sync v1.82 → v1.83 — прямой Edit per «registry version
sync» rationale, не content authoring (CLAUDE.md §5 п.10).

cspell-words.txt +1: «grep'нутых» (Russian-tech jargon).

Path (i) `npm i @mdi/font` REJECTED (250 KB CSS, против бренда).
Path β rename all strings REJECTED (большой diff 51 файл).

Spec: docs/superpowers/specs/2026-05-13-cto-19-lucide-icon-migration-design.md
Plan: docs/superpowers/plans/2026-05-13-cto-19-lucide-icon-migration.md

Quirk 64: app/dev-indices.json attached per Vite watcher auto-regen.
Memory updates — git-untracked, отдельный шаг.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 05:16:31 +03:00

219 lines
7.4 KiB
TypeScript

// @ts-expect-error vuetify/styles — CSS-импорт без d.ts
import 'vuetify/styles';
import { h, type Component } from 'vue';
import { createVuetify } from 'vuetify';
import type { ThemeDefinition, IconSet, IconProps } from 'vuetify';
import {
Activity, AlertCircle, AlertTriangle, Archive, ArrowDown, ArrowLeft, ArrowRightLeft,
ArrowUp, Bell, BellOff, Calendar, CalendarDays, Camera, Check, CheckCircle, ChevronDown,
ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronsUpDown, ChevronUp,
Circle, CircleDot, CircleStop, Clock, Code,
Columns3, Copy, CreditCard, Download, Eye, EyeOff, FilterX, FileText, FlaskConical,
Folder, Folders, Globe, HelpCircle, Info, Key, KeyRound, LayoutDashboard, List, LogOut, Mail,
Megaphone, Menu, MessageSquare, MessageSquareText, Minus, MoreVertical, Paperclip, Pause,
Pencil, Phone, Play, Plus,
PlusCircle, Puzzle, ReceiptText, RefreshCw, RotateCcw, RotateCw, RussianRuble, Save, Search,
Settings, Shield, ShieldCheck, ShieldOff, Square, SquareCheck, SquareMinus, Star, StarHalf,
Tag, Trash2, User, UserCheck, UserCog, UserPlus,
Users, Wallet, Webhook, X, XCircle,
} from 'lucide-vue-next';
/**
* Палитра Forest extended (Iteration 1 — Quiet Luxury redesign).
* Spec: docs/superpowers/specs/2026-05-12-portal-redesign-quiet-luxury-design.md §3
* CSS-токены: app/resources/css/tokens.css (single source of truth)
*
* 14 OKLCH-статусов воронки маппятся на slugs из db/schema.sql:2076 (lead_statuses)
* через `useStatusPill` composable, НЕ через Vuetify theme.
*/
const liderraForest: ThemeDefinition = {
dark: false,
colors: {
background: '#F6F3EC',
surface: '#FFFFFF',
primary: '#0F6E56',
'on-primary': '#FFFFFF',
secondary: '#012019',
'on-secondary': '#F6F3EC',
success: '#2E8B57',
warning: '#D9A441',
error: '#B83A3A',
info: '#3F7C95',
// Расширения — для data viz и semantic uses
'liderra-plum': '#7A5BA3',
'liderra-salmon': '#CC6E50',
'liderra-teal-deep': '#0A5A47',
'liderra-muted': '#6B6356',
},
};
/**
* Liderra Lucide IconSet (CTO-19 closure, v1.83).
* Spec: docs/superpowers/specs/2026-05-13-cto-19-lucide-icon-migration-design.md
*
* Maps 78 mdi-* semantic-ID strings (used across 51 Vue/TS files in resources/js/)
* to Lucide Vue components for rendering. Views НЕ touched — mdi-* строки остаются
* как semantic identifiers, рендерятся через Lucide stroke-based SVG icons.
*
* CLAUDE.md §2 «Иконки: Lucide» — бренд-spec compliance.
*/
const lucideMap: Record<string, Component> = {
'mdi-account-arrow-right-outline': UserCheck,
'mdi-account-group-outline': Users,
'mdi-account-outline': User,
'mdi-account-plus-outline': UserPlus,
'mdi-account-switch': UserCog,
'mdi-alert-circle': AlertCircle,
'mdi-alert-circle-outline': AlertCircle,
'mdi-alert-outline': AlertTriangle,
'mdi-api': Webhook,
'mdi-archive': Archive,
'mdi-arrow-down': ArrowDown,
'mdi-arrow-left': ArrowLeft,
'mdi-arrow-up': ArrowUp,
'mdi-autorenew': RotateCw,
'mdi-bell-off-outline': BellOff,
'mdi-bell-outline': Bell,
'mdi-bullhorn-outline': Megaphone,
'mdi-camera': Camera,
'mdi-cash-plus': PlusCircle,
'mdi-chart-box-outline': LayoutDashboard,
'mdi-check': Check,
'mdi-check-circle': CheckCircle,
'mdi-check-circle-outline': CheckCircle,
'mdi-chevron-right': ChevronRight,
'mdi-clock-check-outline': Clock,
'mdi-clock-outline': Clock,
'mdi-close': X,
'mdi-close-circle': XCircle,
'mdi-cog-outline': Settings,
'mdi-comment-outline': MessageSquare,
'mdi-content-copy': Copy,
'mdi-content-save-outline': Save,
'mdi-credit-card-outline': CreditCard,
'mdi-currency-rub': RussianRuble,
'mdi-delete-outline': Trash2,
'mdi-dots-vertical': MoreVertical,
'mdi-download': Download,
'mdi-email-outline': Mail,
'mdi-eye': Eye,
'mdi-eye-off': EyeOff,
'mdi-eye-outline': Eye,
'mdi-file-pdf-box': FileText,
'mdi-filter-off': FilterX,
'mdi-folder-multiple-outline': Folders,
'mdi-folder-outline': Folder,
'mdi-format-list-bulleted': List,
'mdi-key': Key,
'mdi-lock-reset': KeyRound,
'mdi-logout': LogOut,
'mdi-magnify': Search,
'mdi-message-text': MessageSquareText,
'mdi-pause': Pause,
'mdi-pencil': Pencil,
'mdi-pencil-outline': Pencil,
'mdi-phone': Phone,
'mdi-play': Play,
'mdi-plus': Plus,
'mdi-plus-circle-outline': PlusCircle,
'mdi-progress-clock': Clock,
'mdi-pulse': Activity,
'mdi-puzzle-outline': Puzzle,
'mdi-receipt-text-check-outline': ReceiptText,
'mdi-refresh': RefreshCw,
'mdi-restore': RotateCcw,
'mdi-shield-account-outline': ShieldCheck,
'mdi-shield-key': Shield,
'mdi-shield-lock-outline': ShieldCheck,
'mdi-shield-off': ShieldOff,
'mdi-stop-circle-outline': CircleStop,
'mdi-swap-horizontal': ArrowRightLeft,
'mdi-tag-arrow-right': Tag,
'mdi-test-tube': FlaskConical,
'mdi-trash-can-outline': Trash2,
'mdi-view-column-outline': Columns3,
'mdi-view-dashboard-outline': LayoutDashboard,
'mdi-wallet-outline': Wallet,
'mdi-web': Globe,
'mdi-xml': Code,
// Vuetify-internal default mdi-* aliases (CTO-19 closure extension)
'mdi-chevron-down': ChevronDown,
'mdi-chevron-left': ChevronLeft,
'mdi-menu-down': ChevronDown,
'mdi-menu-right': ChevronRight,
'mdi-menu-up': ChevronUp,
'mdi-menu': Menu,
'mdi-page-first': ChevronsLeft,
'mdi-page-last': ChevronsRight,
'mdi-checkbox-marked': SquareCheck,
'mdi-checkbox-blank-outline': Square,
'mdi-minus-box': SquareMinus,
'mdi-radiobox-marked': CircleDot,
'mdi-radiobox-blank': Circle,
'mdi-circle': Circle,
'mdi-information': Info,
'mdi-minus': Minus,
'mdi-calendar': Calendar,
'mdi-calendar-month': CalendarDays,
'mdi-paperclip': Paperclip,
'mdi-unfold-more-horizontal': ChevronsUpDown,
'mdi-window-close': X,
'mdi-cached': RefreshCw,
'mdi-star': Star,
'mdi-star-outline': Star,
'mdi-star-half-full': StarHalf,
};
const liderraLucideSet: IconSet = {
component: (props: IconProps) => {
const Icon = lucideMap[String(props.icon)] || HelpCircle;
return h(Icon, { size: 20, strokeWidth: 1.75 });
},
};
export const vuetify = createVuetify({
theme: {
defaultTheme: 'liderraForest',
themes: { liderraForest },
},
icons: {
defaultSet: 'liderra',
sets: { liderra: liderraLucideSet },
},
defaults: {
VBtn: {
variant: 'flat',
rounded: 'lg',
},
VCard: {
rounded: 'lg',
variant: 'flat',
border: true,
},
VTextField: {
variant: 'outlined',
density: 'comfortable',
color: 'primary',
},
VTextarea: {
variant: 'outlined',
density: 'comfortable',
color: 'primary',
},
VSelect: {
variant: 'outlined',
density: 'comfortable',
},
VChip: {
rounded: 'pill',
size: 'small',
},
VDataTable: {
density: 'comfortable',
},
VDialog: {
scrim: 'rgba(1, 32, 25, 0.32)',
},
},
});