softleader-nuxt-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # Nuxt Core Layer
2
+
3
+ 這是 Nuxt 3 Core Layer,提供可重用的基礎架構、元件和工具。
4
+
5
+ ## 包含內容
6
+
7
+ - 設計系統元件(Button、Card)
8
+ - 基礎 Layout
9
+ - 全域樣式與設計 Tokens
10
+ - 共用 Composables
11
+ - 統一的開發工具配置(ESLint、Prettier、TypeScript)
12
+
13
+ ## 使用方式
14
+
15
+ 在你的 Nuxt 應用中擴展此 Layer:
16
+
17
+ ```typescript
18
+ // nuxt.config.ts
19
+ export default defineNuxtConfig({
20
+ extends: ['@org/nuxt-core']
21
+ })
22
+ ```
23
+
24
+ ## 開發
25
+
26
+ ```bash
27
+ # 安裝依賴
28
+ npm install
29
+
30
+ # Lint
31
+ npm run lint
32
+
33
+ # 型別檢查
34
+ npm run typecheck
35
+ ```
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <button :class="buttonClasses" :disabled="disabled" v-bind="$attrs">
3
+ <slot />
4
+ </button>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ interface Props {
9
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
10
+ size?: 'sm' | 'md' | 'lg'
11
+ disabled?: boolean
12
+ }
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ variant: 'primary',
16
+ size: 'md',
17
+ disabled: false
18
+ })
19
+
20
+ const buttonClasses = computed(() => {
21
+ return [
22
+ 'btn',
23
+ `btn--${props.variant}`,
24
+ `btn--${props.size}`,
25
+ { 'btn--disabled': props.disabled }
26
+ ]
27
+ })
28
+ </script>
29
+
30
+ <style scoped>
31
+ .btn {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ font-weight: 500;
36
+ border: none;
37
+ border-radius: var(--radius-md);
38
+ cursor: pointer;
39
+ transition: all var(--transition-base);
40
+ font-family: inherit;
41
+ white-space: nowrap;
42
+ }
43
+
44
+ .btn:focus {
45
+ outline: 2px solid var(--color-primary-500);
46
+ outline-offset: 2px;
47
+ }
48
+
49
+ /* Sizes */
50
+ .btn--sm {
51
+ padding: var(--spacing-xs) var(--spacing-md);
52
+ font-size: var(--font-size-sm);
53
+ }
54
+
55
+ .btn--md {
56
+ padding: var(--spacing-sm) var(--spacing-lg);
57
+ font-size: var(--font-size-base);
58
+ }
59
+
60
+ .btn--lg {
61
+ padding: var(--spacing-md) var(--spacing-xl);
62
+ font-size: var(--font-size-lg);
63
+ }
64
+
65
+ /* Variants */
66
+ .btn--primary {
67
+ background-color: var(--color-primary-600);
68
+ color: white;
69
+ }
70
+
71
+ .btn--primary:hover:not(.btn--disabled) {
72
+ background-color: var(--color-primary-700);
73
+ }
74
+
75
+ .btn--secondary {
76
+ background-color: var(--color-gray-600);
77
+ color: white;
78
+ }
79
+
80
+ .btn--secondary:hover:not(.btn--disabled) {
81
+ background-color: var(--color-gray-700);
82
+ }
83
+
84
+ .btn--outline {
85
+ background-color: transparent;
86
+ color: var(--color-primary-600);
87
+ border: 2px solid var(--color-primary-600);
88
+ }
89
+
90
+ .btn--outline:hover:not(.btn--disabled) {
91
+ background-color: var(--color-primary-50);
92
+ }
93
+
94
+ .btn--ghost {
95
+ background-color: transparent;
96
+ color: var(--color-gray-700);
97
+ }
98
+
99
+ .btn--ghost:hover:not(.btn--disabled) {
100
+ background-color: var(--color-gray-100);
101
+ }
102
+
103
+ /* Disabled */
104
+ .btn--disabled {
105
+ opacity: 0.5;
106
+ cursor: not-allowed;
107
+ }
108
+ </style>
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <div :class="cardClasses">
3
+ <div v-if="$slots.header" class="card__header">
4
+ <slot name="header" />
5
+ </div>
6
+
7
+ <div class="card__body">
8
+ <slot />
9
+ </div>
10
+
11
+ <div v-if="$slots.footer" class="card__footer">
12
+ <slot name="footer" />
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ interface Props {
19
+ variant?: 'default' | 'bordered' | 'elevated'
20
+ padding?: 'sm' | 'md' | 'lg'
21
+ }
22
+
23
+ const props = withDefaults(defineProps<Props>(), {
24
+ variant: 'default',
25
+ padding: 'md'
26
+ })
27
+
28
+ const cardClasses = computed(() => {
29
+ return ['card', `card--${props.variant}`, `card--padding-${props.padding}`]
30
+ })
31
+ </script>
32
+
33
+ <style scoped>
34
+ .card {
35
+ background-color: white;
36
+ border-radius: var(--radius-lg);
37
+ overflow: hidden;
38
+ }
39
+
40
+ /* Variants */
41
+ .card--default {
42
+ border: 1px solid var(--color-gray-200);
43
+ }
44
+
45
+ .card--bordered {
46
+ border: 2px solid var(--color-gray-300);
47
+ }
48
+
49
+ .card--elevated {
50
+ box-shadow: var(--shadow-lg);
51
+ border: none;
52
+ }
53
+
54
+ /* Padding */
55
+ .card--padding-sm .card__body {
56
+ padding: var(--spacing-md);
57
+ }
58
+
59
+ .card--padding-md .card__body {
60
+ padding: var(--spacing-lg);
61
+ }
62
+
63
+ .card--padding-lg .card__body {
64
+ padding: var(--spacing-xl);
65
+ }
66
+
67
+ /* Header */
68
+ .card__header {
69
+ padding: var(--spacing-md) var(--spacing-lg);
70
+ border-bottom: 1px solid var(--color-gray-200);
71
+ background-color: var(--color-gray-50);
72
+ font-weight: 600;
73
+ }
74
+
75
+ /* Footer */
76
+ .card__footer {
77
+ padding: var(--spacing-md) var(--spacing-lg);
78
+ border-top: 1px solid var(--color-gray-200);
79
+ background-color: var(--color-gray-50);
80
+ }
81
+ </style>
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 格式化日期
3
+ */
4
+ export const useFormatDate = () => {
5
+ const formatDate = (date: Date | string, format: string = 'YYYY-MM-DD'): string => {
6
+ const d = typeof date === 'string' ? new Date(date) : date
7
+
8
+ const year = d.getFullYear()
9
+ const month = String(d.getMonth() + 1).padStart(2, '0')
10
+ const day = String(d.getDate()).padStart(2, '0')
11
+ const hours = String(d.getHours()).padStart(2, '0')
12
+ const minutes = String(d.getMinutes()).padStart(2, '0')
13
+ const seconds = String(d.getSeconds()).padStart(2, '0')
14
+
15
+ return format
16
+ .replace('YYYY', String(year))
17
+ .replace('MM', month)
18
+ .replace('DD', day)
19
+ .replace('HH', hours)
20
+ .replace('mm', minutes)
21
+ .replace('ss', seconds)
22
+ }
23
+
24
+ return {
25
+ formatDate
26
+ }
27
+ }
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <div class="layout-default">
3
+ <header class="header">
4
+ <div class="container">
5
+ <h1 class="logo">{{ appName }}</h1>
6
+ <nav class="nav">
7
+ <slot name="nav" />
8
+ </nav>
9
+ </div>
10
+ </header>
11
+
12
+ <main class="main">
13
+ <div class="container">
14
+ <slot />
15
+ </div>
16
+ </main>
17
+
18
+ <footer class="footer">
19
+ <div class="container">
20
+ <p>&copy; {{ currentYear }} {{ appName }}. All rights reserved.</p>
21
+ </div>
22
+ </footer>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ const config = useRuntimeConfig()
28
+ const appName = config.public.appName
29
+
30
+ const currentYear = new Date().getFullYear()
31
+ </script>
32
+
33
+ <style scoped>
34
+ .layout-default {
35
+ min-height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ }
39
+
40
+ .header {
41
+ background-color: white;
42
+ border-bottom: 1px solid var(--color-gray-200);
43
+ padding: var(--spacing-md) 0;
44
+ box-shadow: var(--shadow-sm);
45
+ }
46
+
47
+ .header .container {
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ }
52
+
53
+ .logo {
54
+ font-size: var(--font-size-xl);
55
+ font-weight: 700;
56
+ color: var(--color-primary-600);
57
+ margin: 0;
58
+ }
59
+
60
+ .nav {
61
+ display: flex;
62
+ gap: var(--spacing-lg);
63
+ }
64
+
65
+ .main {
66
+ flex: 1;
67
+ padding: var(--spacing-2xl) 0;
68
+ }
69
+
70
+ .footer {
71
+ background-color: var(--color-gray-800);
72
+ color: var(--color-gray-200);
73
+ padding: var(--spacing-lg) 0;
74
+ text-align: center;
75
+ }
76
+
77
+ .footer p {
78
+ margin: 0;
79
+ font-size: var(--font-size-sm);
80
+ }
81
+ </style>
package/nuxt.config.ts ADDED
@@ -0,0 +1,30 @@
1
+ // https://nuxt.com/docs/api/configuration/nuxt-config
2
+ export default defineNuxtConfig({
3
+ compatibilityDate: "2024-04-03",
4
+
5
+ // 啟用自動導入
6
+ components: true,
7
+ imports: {
8
+ autoImport: true,
9
+ },
10
+
11
+ // CSS 配置
12
+ css: ["./styles/main.css"],
13
+
14
+ // Runtime 配置
15
+ runtimeConfig: {
16
+ public: {
17
+ appName: "Nuxt Core",
18
+ apiTimeout: 30000,
19
+ },
20
+ },
21
+
22
+ // TypeScript 配置
23
+ typescript: {
24
+ strict: true,
25
+ typeCheck: true,
26
+ },
27
+
28
+ // 開發工具
29
+ devtools: { enabled: true },
30
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "softleader-nuxt-core",
3
+ "version": "1.0.0",
4
+ "description": "Nuxt 3 Core Layer - 可重用的基礎架構",
5
+ "type": "module",
6
+ "files": [
7
+ "components",
8
+ "composables",
9
+ "layouts",
10
+ "plugins",
11
+ "styles",
12
+ "nuxt.config.ts",
13
+ "tsconfig.base.json",
14
+ "README.md"
15
+ ],
16
+ "exports": {
17
+ ".": "./nuxt.config.ts"
18
+ },
19
+ "scripts": {
20
+ "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix",
22
+ "typecheck": "nuxi typecheck"
23
+ },
24
+ "dependencies": {
25
+ "@nuxt/kit": "^3.15.4"
26
+ },
27
+ "devDependencies": {
28
+ "@nuxt/eslint-config": "^0.5.7",
29
+ "@typescript-eslint/eslint-plugin": "^8.18.2",
30
+ "@typescript-eslint/parser": "^8.18.2",
31
+ "eslint": "^9.17.0",
32
+ "nuxt": "^3.15.4",
33
+ "prettier": "^3.4.2",
34
+ "typescript": "^5.7.3",
35
+ "vue-tsc": "^2.2.0"
36
+ },
37
+ "keywords": [
38
+ "nuxt3",
39
+ "layer",
40
+ "design-system"
41
+ ],
42
+ "author": "",
43
+ "license": "MIT"
44
+ }
@@ -0,0 +1,136 @@
1
+ /* 全域樣式與設計 Tokens */
2
+
3
+ /* CSS Reset & Base */
4
+ *,
5
+ *::before,
6
+ *::after {
7
+ box-sizing: border-box;
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+
12
+ /* Design Tokens - Colors */
13
+ :root {
14
+ /* Primary Colors */
15
+ --color-primary-50: #f0f9ff;
16
+ --color-primary-100: #e0f2fe;
17
+ --color-primary-200: #bae6fd;
18
+ --color-primary-300: #7dd3fc;
19
+ --color-primary-400: #38bdf8;
20
+ --color-primary-500: #0ea5e9;
21
+ --color-primary-600: #0284c7;
22
+ --color-primary-700: #0369a1;
23
+ --color-primary-800: #075985;
24
+ --color-primary-900: #0c4a6e;
25
+
26
+ /* Neutral Colors */
27
+ --color-gray-50: #f9fafb;
28
+ --color-gray-100: #f3f4f6;
29
+ --color-gray-200: #e5e7eb;
30
+ --color-gray-300: #d1d5db;
31
+ --color-gray-400: #9ca3af;
32
+ --color-gray-500: #6b7280;
33
+ --color-gray-600: #4b5563;
34
+ --color-gray-700: #374151;
35
+ --color-gray-800: #1f2937;
36
+ --color-gray-900: #111827;
37
+
38
+ /* Semantic Colors */
39
+ --color-success: #10b981;
40
+ --color-warning: #f59e0b;
41
+ --color-error: #ef4444;
42
+ --color-info: #3b82f6;
43
+
44
+ /* Spacing */
45
+ --spacing-xs: 0.25rem;
46
+ --spacing-sm: 0.5rem;
47
+ --spacing-md: 1rem;
48
+ --spacing-lg: 1.5rem;
49
+ --spacing-xl: 2rem;
50
+ --spacing-2xl: 3rem;
51
+ --spacing-3xl: 4rem;
52
+
53
+ /* Border Radius */
54
+ --radius-sm: 0.25rem;
55
+ --radius-md: 0.5rem;
56
+ --radius-lg: 0.75rem;
57
+ --radius-xl: 1rem;
58
+ --radius-full: 9999px;
59
+
60
+ /* Shadows */
61
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
62
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
63
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
64
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
65
+
66
+ /* Typography */
67
+ --font-sans:
68
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
69
+ --font-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
70
+
71
+ --font-size-xs: 0.75rem;
72
+ --font-size-sm: 0.875rem;
73
+ --font-size-base: 1rem;
74
+ --font-size-lg: 1.125rem;
75
+ --font-size-xl: 1.25rem;
76
+ --font-size-2xl: 1.5rem;
77
+ --font-size-3xl: 1.875rem;
78
+ --font-size-4xl: 2.25rem;
79
+
80
+ /* Transitions */
81
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
82
+ --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
83
+ --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
84
+ }
85
+
86
+ /* Base Styles */
87
+ html {
88
+ font-family: var(--font-sans);
89
+ font-size: 16px;
90
+ line-height: 1.5;
91
+ -webkit-font-smoothing: antialiased;
92
+ -moz-osx-font-smoothing: grayscale;
93
+ }
94
+
95
+ body {
96
+ color: var(--color-gray-900);
97
+ background-color: var(--color-gray-50);
98
+ }
99
+
100
+ /* Utility Classes */
101
+ .container {
102
+ width: 100%;
103
+ max-width: 1280px;
104
+ margin: 0 auto;
105
+ padding: 0 var(--spacing-md);
106
+ }
107
+
108
+ .text-center {
109
+ text-align: center;
110
+ }
111
+
112
+ .mt-sm {
113
+ margin-top: var(--spacing-sm);
114
+ }
115
+ .mt-md {
116
+ margin-top: var(--spacing-md);
117
+ }
118
+ .mt-lg {
119
+ margin-top: var(--spacing-lg);
120
+ }
121
+ .mt-xl {
122
+ margin-top: var(--spacing-xl);
123
+ }
124
+
125
+ .mb-sm {
126
+ margin-bottom: var(--spacing-sm);
127
+ }
128
+ .mb-md {
129
+ margin-bottom: var(--spacing-md);
130
+ }
131
+ .mb-lg {
132
+ margin-bottom: var(--spacing-lg);
133
+ }
134
+ .mb-xl {
135
+ margin-bottom: var(--spacing-xl);
136
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "jsx": "preserve",
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "verbatimModuleSyntax": true,
14
+ "noEmit": true,
15
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
16
+ "types": ["node"]
17
+ },
18
+ "exclude": ["node_modules", ".nuxt", ".output", "dist"]
19
+ }