From d3760cccb509abf6081bb09ca798ea58f94355ec Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 15 Aug 2026 11:28:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=202=20=E2=80=94=20UI=20component?= =?UTF-8?q?=20enhancement=20with=20design=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Design System (Phase 1 completion):** - tokens.ts: 12-category token system (colors, typography, spacing, shadows, etc.) - tokens.css: CSS variables with light/dark theme support - App.vue: Integrated token system app-wide **Shared Components (Phase 2):** - SkeletonLoader.vue: 5 loader types (text, card, avatar, table, list) - ErrorBoundary.vue: Error state recovery with retry - ToastNotification.vue: Toast alerts with 4 types + auto-dismiss - ToastContainer.vue: Toast provider with fixed positioning - Modal.vue: Animated modal with 4 size variants **Page Enhancements:** - ShadowRunQueue.vue: Loading → Skeleton + Error → ErrorBoundary + Normal states - ModelList.vue: Loading → Skeleton + Error → ErrorBoundary + Normal states - ApprovalQueue.vue: Loading → Skeleton + Error → ErrorBoundary + Normal states **Testing:** - All 3 pages: ✅ 100% PASS (12/12 selectors) - Playwright tests verify: loading, error, normal states - All states render correctly with token-based styling **Next: Phase 3 (2-4h)** - Layout unification (sidebar, header, footer) - Complete dark theme implementation - Mobile responsiveness & navigation Co-Authored-By: Claude Haiku 4.5 --- frontend/src/App.vue | 13 + .../features/approval/pages/ApprovalQueue.vue | 459 +++++++++----- .../src/features/models/pages/ModelList.vue | 564 ++++++++++++++---- .../shadow-run/pages/ShadowRunQueue.vue | 476 ++++++++++----- frontend/src/shared/design-system/tokens.css | 496 +++++++++++++++ frontend/src/shared/design-system/tokens.ts | 351 +++++++++++ .../shared/ui/components/ErrorBoundary.vue | 104 ++++ frontend/src/shared/ui/components/Modal.vue | 229 +++++++ .../shared/ui/components/SkeletonLoader.vue | 145 +++++ .../shared/ui/components/ToastContainer.vue | 98 +++ .../ui/components/ToastNotification.vue | 176 ++++++ 11 files changed, 2686 insertions(+), 425 deletions(-) create mode 100644 frontend/src/shared/design-system/tokens.css create mode 100644 frontend/src/shared/design-system/tokens.ts create mode 100644 frontend/src/shared/ui/components/ErrorBoundary.vue create mode 100644 frontend/src/shared/ui/components/Modal.vue create mode 100644 frontend/src/shared/ui/components/SkeletonLoader.vue create mode 100644 frontend/src/shared/ui/components/ToastContainer.vue create mode 100644 frontend/src/shared/ui/components/ToastNotification.vue diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b4082f90..cc8a8306 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,9 +1,22 @@ + + diff --git a/frontend/src/features/approval/pages/ApprovalQueue.vue b/frontend/src/features/approval/pages/ApprovalQueue.vue index 6b12a6a5..f3e3699b 100644 --- a/frontend/src/features/approval/pages/ApprovalQueue.vue +++ b/frontend/src/features/approval/pages/ApprovalQueue.vue @@ -1,5 +1,7 @@ @@ -80,207 +102,517 @@ const phaseColors: Record = {

Models (Master-Detail)

- -
- - -
- -
Loading models...
+ -
Failed to load models
+ +
+ +
+
-
- -
-

Models ({{ listQuery.data.value.items.length || 0 }})

-
-
-
- {{ model.name }} - - {{ model.phase }} - -
-
-
- PBO - {{ formatPercentage(model.pbo) }} +
diff --git a/frontend/src/shared/design-system/tokens.css b/frontend/src/shared/design-system/tokens.css new file mode 100644 index 00000000..21a63565 --- /dev/null +++ b/frontend/src/shared/design-system/tokens.css @@ -0,0 +1,496 @@ +/** + * Design System CSS Tokens + * Light mode by default, dark mode via @media prefers-color-scheme + */ + +:root { + /* ======================================================================= */ + /* COLOR PALETTE */ + /* ======================================================================= */ + + /* Primary Colors (Blue) */ + --color-primary-50: #eff6ff; + --color-primary-100: #dbeafe; + --color-primary-200: #bfdbfe; + --color-primary-300: #93c5fd; + --color-primary-400: #60a5fa; + --color-primary-500: #3b82f6; + --color-primary-600: #2563eb; + --color-primary-700: #1d4ed8; + --color-primary-800: #1e40af; + --color-primary-900: #1e3a8a; + + /* Secondary Colors (Purple) */ + --color-secondary-50: #faf5ff; + --color-secondary-100: #f3e8ff; + --color-secondary-200: #e9d5ff; + --color-secondary-300: #d8b4fe; + --color-secondary-400: #c084fc; + --color-secondary-500: #a855f7; + --color-secondary-600: #9333ea; + --color-secondary-700: #7e22ce; + --color-secondary-800: #6b21a8; + --color-secondary-900: #581c87; + + /* Success Colors (Green) */ + --color-success-50: #f0fdf4; + --color-success-100: #dcfce7; + --color-success-200: #bbf7d0; + --color-success-300: #86efac; + --color-success-400: #4ade80; + --color-success-500: #22c55e; + --color-success-600: #16a34a; + --color-success-700: #15803d; + --color-success-800: #166534; + --color-success-900: #145231; + + /* Warning Colors (Amber) */ + --color-warning-50: #fffbeb; + --color-warning-100: #fef3c7; + --color-warning-200: #fde68a; + --color-warning-300: #fcd34d; + --color-warning-400: #fbbf24; + --color-warning-500: #f59e0b; + --color-warning-600: #d97706; + --color-warning-700: #b45309; + --color-warning-800: #92400e; + --color-warning-900: #78350f; + + /* Danger Colors (Red) */ + --color-danger-50: #fef2f2; + --color-danger-100: #fee2e2; + --color-danger-200: #fecaca; + --color-danger-300: #fca5a5; + --color-danger-400: #f87171; + --color-danger-500: #ef4444; + --color-danger-600: #dc2626; + --color-danger-700: #b91c1c; + --color-danger-800: #991b1b; + --color-danger-900: #7f1d1d; + + /* Neutral Colors (Gray) */ + --color-neutral-50: #fafafa; + --color-neutral-100: #f5f5f5; + --color-neutral-200: #eeeeee; + --color-neutral-300: #e0e0e0; + --color-neutral-400: #bdbdbd; + --color-neutral-500: #9e9e9e; + --color-neutral-600: #757575; + --color-neutral-700: #616161; + --color-neutral-800: #424242; + --color-neutral-900: #212121; + + /* Semantic Colors (Light Mode) */ + --color-text-primary: var(--color-neutral-900); + --color-text-secondary: var(--color-neutral-700); + --color-text-tertiary: var(--color-neutral-500); + --color-text-inverse: #ffffff; + + --color-background-primary: #ffffff; + --color-background-secondary: var(--color-neutral-50); + --color-background-tertiary: var(--color-neutral-100); + --color-background-hover: var(--color-neutral-100); + --color-background-active: var(--color-primary-50); + + --color-border-primary: var(--color-neutral-300); + --color-border-secondary: var(--color-neutral-200); + + --color-input-background: #ffffff; + --color-input-border: var(--color-neutral-300); + --color-input-hover: var(--color-neutral-200); + --color-input-focus: var(--color-primary-500); + + /* ======================================================================= */ + /* TYPOGRAPHY */ + /* ======================================================================= */ + + /* Font Families */ + --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace; + + /* Font Sizes */ + --font-size-xs: 12px; + --font-size-sm: 14px; + --font-size-base: 16px; + --font-size-lg: 18px; + --font-size-xl: 20px; + --font-size-2xl: 24px; + --font-size-3xl: 30px; + --font-size-4xl: 36px; + + /* Line Heights */ + --line-height-tight: 1.2; + --line-height-normal: 1.5; + --line-height-relaxed: 1.75; + --line-height-loose: 2; + + /* Font Weights */ + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + /* ======================================================================= */ + /* SPACING */ + /* ======================================================================= */ + + --spacing-0: 0px; + --spacing-1: 4px; + --spacing-2: 8px; + --spacing-3: 12px; + --spacing-4: 16px; + --spacing-5: 20px; + --spacing-6: 24px; + --spacing-8: 32px; + --spacing-10: 40px; + --spacing-12: 48px; + --spacing-16: 64px; + --spacing-20: 80px; + --spacing-24: 96px; + + /* ======================================================================= */ + /* SHADOWS */ + /* ======================================================================= */ + + --shadow-none: none; + --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + + /* ======================================================================= */ + /* BORDERS */ + /* ======================================================================= */ + + --border-radius-none: 0px; + --border-radius-sm: 2px; + --border-radius-base: 4px; + --border-radius-md: 6px; + --border-radius-lg: 8px; + --border-radius-xl: 12px; + --border-radius-full: 9999px; + + --border-width-0: 0px; + --border-width-1: 1px; + --border-width-2: 2px; + + /* ======================================================================= */ + /* TRANSITIONS */ + /* ======================================================================= */ + + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slower: 500ms cubic-bezier(0.4, 0, 0.2, 1); + + /* ======================================================================= */ + /* Z-INDEX */ + /* ======================================================================= */ + + --z-index-hide: -1; + --z-index-base: 0; + --z-index-dropdown: 1000; + --z-index-sticky: 1020; + --z-index-fixed: 1030; + --z-index-modal-backdrop: 1040; + --z-index-modal: 1050; + --z-index-popover: 1060; + --z-index-tooltip: 1070; + + /* ======================================================================= */ + /* COMPONENT SIZES */ + /* ======================================================================= */ + + --input-height-sm: 32px; + --input-height-base: 36px; + --input-height-lg: 44px; + + --button-height-sm: 32px; + --button-height-base: 36px; + --button-height-lg: 44px; + + --icon-size-xs: 16px; + --icon-size-sm: 20px; + --icon-size-base: 24px; + --icon-size-lg: 32px; + --icon-size-xl: 48px; + + --grid-row-compact: 32px; + --grid-row-base: 36px; + --grid-row-comfortable: 44px; + --grid-row-touch: 52px; + + --sidebar-width-compact: 64px; + --sidebar-width-base: 256px; + --sidebar-width-wide: 320px; + + /* ======================================================================= */ + /* MODULE COLORS */ + /* ======================================================================= */ + + --module-color-oms: var(--color-primary-500); + --module-color-erp: var(--color-secondary-500); + --module-color-wms: #14b8a6; + --module-color-common: #6b7280; +} + +/* ========================================================================== */ +/* DARK MODE */ +/* ========================================================================== */ + +@media (prefers-color-scheme: dark) { + :root { + /* Semantic Colors (Dark Mode) */ + --color-text-primary: #ffffff; + --color-text-secondary: var(--color-neutral-300); + --color-text-tertiary: var(--color-neutral-500); + --color-text-inverse: var(--color-neutral-900); + + --color-background-primary: var(--color-neutral-900); + --color-background-secondary: var(--color-neutral-800); + --color-background-tertiary: var(--color-neutral-700); + --color-background-hover: var(--color-neutral-700); + --color-background-active: rgba(59, 130, 246, 0.2); + + --color-border-primary: var(--color-neutral-700); + --color-border-secondary: var(--color-neutral-600); + + --color-input-background: var(--color-neutral-800); + --color-input-border: var(--color-neutral-700); + --color-input-hover: var(--color-neutral-600); + --color-input-focus: var(--color-primary-400); + } +} + +/* ========================================================================== */ +/* BASE STYLES */ +/* ========================================================================== */ + +* { + box-sizing: border-box; +} + +html { + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + padding: 0; + color: var(--color-text-primary); + background-color: var(--color-background-primary); + font-size: var(--font-size-base); + line-height: var(--line-height-normal); + transition: background-color var(--transition-base), color var(--transition-base); +} + +/* ========================================================================== */ +/* TYPOGRAPHY UTILITIES */ +/* ========================================================================== */ + +h1 { + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); + margin: var(--spacing-4) 0; + color: var(--color-text-primary); +} + +h2 { + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); + margin: var(--spacing-3) 0; + color: var(--color-text-primary); +} + +h3 { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-tight); + margin: var(--spacing-2) 0; + color: var(--color-text-primary); +} + +p { + font-size: var(--font-size-base); + line-height: var(--line-height-normal); + margin: var(--spacing-3) 0; + color: var(--color-text-secondary); +} + +/* ========================================================================== */ +/* FORM CONTROLS */ +/* ========================================================================== */ + +input[type="text"], +input[type="search"], +input[type="email"], +input[type="password"], +input[type="number"], +select, +textarea { + width: 100%; + padding: var(--spacing-2) var(--spacing-3); + border: var(--border-width-1) solid var(--color-input-border); + border-radius: var(--border-radius-base); + background-color: var(--color-input-background); + color: var(--color-text-primary); + font-family: var(--font-sans); + font-size: var(--font-size-base); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +input:hover, +select:hover, +textarea:hover { + border-color: var(--color-input-hover); +} + +input:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--color-input-focus); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +input:disabled, +select:disabled, +textarea:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--color-background-secondary); +} + +/* ========================================================================== */ +/* BUTTONS (Basic Styles) */ +/* ========================================================================== */ + +button { + padding: var(--spacing-2) var(--spacing-4); + border: var(--border-width-1) solid var(--color-border-primary); + border-radius: var(--border-radius-base); + background-color: var(--color-background-secondary); + color: var(--color-text-primary); + font-family: var(--font-sans); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + cursor: pointer; + transition: all var(--transition-fast); +} + +button:hover:not(:disabled) { + background-color: var(--color-background-hover); + border-color: var(--color-border-secondary); +} + +button:active:not(:disabled) { + background-color: var(--color-background-active); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Primary Button */ +button.btn-primary { + background-color: var(--color-primary-500); + color: white; + border-color: var(--color-primary-500); +} + +button.btn-primary:hover:not(:disabled) { + background-color: var(--color-primary-600); + border-color: var(--color-primary-600); +} + +/* Danger Button */ +button.btn-danger { + background-color: var(--color-danger-500); + color: white; + border-color: var(--color-danger-500); +} + +button.btn-danger:hover:not(:disabled) { + background-color: var(--color-danger-600); + border-color: var(--color-danger-600); +} + +/* Success Button */ +button.btn-success { + background-color: var(--color-success-500); + color: white; + border-color: var(--color-success-500); +} + +button.btn-success:hover:not(:disabled) { + background-color: var(--color-success-600); + border-color: var(--color-success-600); +} + +/* ========================================================================== */ +/* UTILITY CLASSES */ +/* ========================================================================== */ + +.text-primary { + color: var(--color-text-primary); +} + +.text-secondary { + color: var(--color-text-secondary); +} + +.text-tertiary { + color: var(--color-text-tertiary); +} + +.bg-primary { + background-color: var(--color-background-primary); +} + +.bg-secondary { + background-color: var(--color-background-secondary); +} + +.border { + border: var(--border-width-1) solid var(--color-border-primary); +} + +.rounded { + border-radius: var(--border-radius-base); +} + +.rounded-lg { + border-radius: var(--border-radius-lg); +} + +.shadow { + box-shadow: var(--shadow-md); +} + +.shadow-lg { + box-shadow: var(--shadow-lg); +} + +.transition { + transition: all var(--transition-base); +} + +.m-4 { + margin: var(--spacing-4); +} + +.p-4 { + padding: var(--spacing-4); +} + +.gap-4 { + gap: var(--spacing-4); +} diff --git a/frontend/src/shared/design-system/tokens.ts b/frontend/src/shared/design-system/tokens.ts new file mode 100644 index 00000000..24ed2afa --- /dev/null +++ b/frontend/src/shared/design-system/tokens.ts @@ -0,0 +1,351 @@ +/** + * Design System Tokens v1.0 + * Comprehensive design token definitions for production-level UI + */ + +// ============================================================================ +// COLOR PALETTE +// ============================================================================ + +export const colors = { + // Primary (Blue - Information, Action) + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', // Primary + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + }, + + // Secondary (Purple - Emphasis) + secondary: { + 50: '#faf5ff', + 100: '#f3e8ff', + 200: '#e9d5ff', + 300: '#d8b4fe', + 400: '#c084fc', + 500: '#a855f7', // Secondary + 600: '#9333ea', + 700: '#7e22ce', + 800: '#6b21a8', + 900: '#581c87', + }, + + // Success (Green - Positive, Complete) + success: { + 50: '#f0fdf4', + 100: '#dcfce7', + 200: '#bbf7d0', + 300: '#86efac', + 400: '#4ade80', + 500: '#22c55e', // Success + 600: '#16a34a', + 700: '#15803d', + 800: '#166534', + 900: '#145231', + }, + + // Warning (Amber - Caution, Pending) + warning: { + 50: '#fffbeb', + 100: '#fef3c7', + 200: '#fde68a', + 300: '#fcd34d', + 400: '#fbbf24', + 500: '#f59e0b', // Warning + 600: '#d97706', + 700: '#b45309', + 800: '#92400e', + 900: '#78350f', + }, + + // Danger (Red - Error, Destructive) + danger: { + 50: '#fef2f2', + 100: '#fee2e2', + 200: '#fecaca', + 300: '#fca5a5', + 400: '#f87171', + 500: '#ef4444', // Danger + 600: '#dc2626', + 700: '#b91c1c', + 800: '#991b1b', + 900: '#7f1d1d', + }, + + // Neutral (Gray - Text, Backgrounds) + neutral: { + 50: '#fafafa', + 100: '#f5f5f5', + 200: '#eeeeee', + 300: '#e0e0e0', + 400: '#bdbdbd', + 500: '#9e9e9e', + 600: '#757575', + 700: '#616161', + 800: '#424242', + 900: '#212121', + }, + + // Module Colors + module: { + oms: '#3b82f6', // OMS - Blue + erp: '#a855f7', // ERP - Purple + wms: '#14b8a6', // WMS - Teal + common: '#6b7280', // COMMON - Gray + }, + + // Special + white: '#ffffff', + black: '#000000', + transparent: 'transparent', +} + +// ============================================================================ +// TYPOGRAPHY +// ============================================================================ + +export const typography = { + // Font Families + family: { + sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + mono: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace', + }, + + // Font Sizes + size: { + xs: '12px', // Captions, helpers + sm: '14px', // Small text + base: '16px', // Body text + lg: '18px', // Larger body + xl: '20px', // Headings + '2xl': '24px', // Section headings + '3xl': '30px', // Page headings + '4xl': '36px', // Hero + }, + + // Line Heights + lineHeight: { + tight: '1.2', + normal: '1.5', + relaxed: '1.75', + loose: '2', + }, + + // Font Weights + weight: { + light: 300, + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, +} + +// ============================================================================ +// SPACING +// ============================================================================ + +export const spacing = { + 0: '0px', + 1: '4px', + 2: '8px', + 3: '12px', + 4: '16px', + 5: '20px', + 6: '24px', + 8: '32px', + 10: '40px', + 12: '48px', + 16: '64px', + 20: '80px', + 24: '96px', +} + +// ============================================================================ +// SHADOWS +// ============================================================================ + +export const shadows = { + none: 'none', + xs: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + sm: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', +} + +// ============================================================================ +// BORDERS +// ============================================================================ + +export const borders = { + radius: { + none: '0px', + sm: '2px', + base: '4px', + md: '6px', + lg: '8px', + xl: '12px', + full: '9999px', + }, + + width: { + 0: '0px', + 1: '1px', + 2: '2px', + }, +} + +// ============================================================================ +// TRANSITIONS +// ============================================================================ + +export const transitions = { + fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)', + base: '200ms cubic-bezier(0.4, 0, 0.2, 1)', + slow: '300ms cubic-bezier(0.4, 0, 0.2, 1)', + slower: '500ms cubic-bezier(0.4, 0, 0.2, 1)', +} + +// ============================================================================ +// Z-INDEX +// ============================================================================ + +export const zIndex = { + hide: -1, + base: 0, + dropdown: 1000, + sticky: 1020, + fixed: 1030, + modalBackdrop: 1040, + modal: 1050, + popover: 1060, + tooltip: 1070, +} + +// ============================================================================ +// COMPONENT SIZES +// ============================================================================ + +export const componentSizes = { + // Input Heights + inputSm: '32px', + inputBase: '36px', + inputLg: '44px', + + // Button Heights + buttonSm: '32px', + buttonBase: '36px', + buttonLg: '44px', + + // Icon Sizes + iconXs: '16px', + iconSm: '20px', + iconBase: '24px', + iconLg: '32px', + iconXl: '48px', + + // Grid Row Heights + gridRowCompact: '32px', + gridRowBase: '36px', + gridRowComfortable: '44px', + gridRowTouch: '52px', + + // Sidebar Widths + sidebarCompact: '64px', + sidebarBase: '256px', + sidebarWide: '320px', +} + +// ============================================================================ +// BREAKPOINTS (CSS Media Queries) +// ============================================================================ + +export const breakpoints = { + xs: '320px', + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', +} + +// ============================================================================ +// OPACITY +// ============================================================================ + +export const opacity = { + 0: '0', + 5: '0.05', + 10: '0.1', + 20: '0.2', + 30: '0.3', + 40: '0.4', + 50: '0.5', + 60: '0.6', + 70: '0.7', + 80: '0.8', + 90: '0.9', + 100: '1', +} + +// ============================================================================ +// SEMANTIC COLORS (Context-aware, used with tokens above) +// ============================================================================ + +export const semantic = { + light: { + text: { + primary: colors.neutral[900], + secondary: colors.neutral[700], + tertiary: colors.neutral[500], + inverse: colors.white, + }, + background: { + primary: colors.white, + secondary: colors.neutral[50], + tertiary: colors.neutral[100], + hover: colors.neutral[100], + }, + border: { + primary: colors.neutral[300], + secondary: colors.neutral[200], + }, + input: { + background: colors.white, + border: colors.neutral[300], + hover: colors.neutral[200], + }, + }, + + dark: { + text: { + primary: colors.white, + secondary: colors.neutral[300], + tertiary: colors.neutral[500], + inverse: colors.neutral[900], + }, + background: { + primary: colors.neutral[900], + secondary: colors.neutral[800], + tertiary: colors.neutral[700], + hover: colors.neutral[700], + }, + border: { + primary: colors.neutral[700], + secondary: colors.neutral[600], + }, + input: { + background: colors.neutral[800], + border: colors.neutral[700], + hover: colors.neutral[600], + }, + }, +} diff --git a/frontend/src/shared/ui/components/ErrorBoundary.vue b/frontend/src/shared/ui/components/ErrorBoundary.vue new file mode 100644 index 00000000..c0eb62e9 --- /dev/null +++ b/frontend/src/shared/ui/components/ErrorBoundary.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/frontend/src/shared/ui/components/Modal.vue b/frontend/src/shared/ui/components/Modal.vue new file mode 100644 index 00000000..99cfb96b --- /dev/null +++ b/frontend/src/shared/ui/components/Modal.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/frontend/src/shared/ui/components/SkeletonLoader.vue b/frontend/src/shared/ui/components/SkeletonLoader.vue new file mode 100644 index 00000000..fd52ec1c --- /dev/null +++ b/frontend/src/shared/ui/components/SkeletonLoader.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/frontend/src/shared/ui/components/ToastContainer.vue b/frontend/src/shared/ui/components/ToastContainer.vue new file mode 100644 index 00000000..b5d9243f --- /dev/null +++ b/frontend/src/shared/ui/components/ToastContainer.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/frontend/src/shared/ui/components/ToastNotification.vue b/frontend/src/shared/ui/components/ToastNotification.vue new file mode 100644 index 00000000..7f594c9f --- /dev/null +++ b/frontend/src/shared/ui/components/ToastNotification.vue @@ -0,0 +1,176 @@ + + + + +