@hua-labs/hua-ux 0.1.0-alpha.0.1

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.
Files changed (210) hide show
  1. package/README.md +839 -0
  2. package/dist/framework/a11y/components/LiveRegion.d.ts +64 -0
  3. package/dist/framework/a11y/components/LiveRegion.d.ts.map +1 -0
  4. package/dist/framework/a11y/components/LiveRegion.js +43 -0
  5. package/dist/framework/a11y/components/SkipToContent.d.ts +62 -0
  6. package/dist/framework/a11y/components/SkipToContent.d.ts.map +1 -0
  7. package/dist/framework/a11y/components/SkipToContent.js +60 -0
  8. package/dist/framework/a11y/hooks/useFocusManagement.d.ts +60 -0
  9. package/dist/framework/a11y/hooks/useFocusManagement.d.ts.map +1 -0
  10. package/dist/framework/a11y/hooks/useFocusManagement.js +71 -0
  11. package/dist/framework/a11y/hooks/useFocusTrap.d.ts +64 -0
  12. package/dist/framework/a11y/hooks/useFocusTrap.d.ts.map +1 -0
  13. package/dist/framework/a11y/hooks/useFocusTrap.js +185 -0
  14. package/dist/framework/a11y/hooks/useLiveRegion.d.ts +56 -0
  15. package/dist/framework/a11y/hooks/useLiveRegion.d.ts.map +1 -0
  16. package/dist/framework/a11y/hooks/useLiveRegion.js +60 -0
  17. package/dist/framework/a11y/index.d.ts +16 -0
  18. package/dist/framework/a11y/index.d.ts.map +1 -0
  19. package/dist/framework/a11y/index.js +11 -0
  20. package/dist/framework/branding/context.d.ts +52 -0
  21. package/dist/framework/branding/context.d.ts.map +1 -0
  22. package/dist/framework/branding/context.js +96 -0
  23. package/dist/framework/branding/css-vars.d.ts +34 -0
  24. package/dist/framework/branding/css-vars.d.ts.map +1 -0
  25. package/dist/framework/branding/css-vars.js +95 -0
  26. package/dist/framework/branding/tailwind-config.d.ts +38 -0
  27. package/dist/framework/branding/tailwind-config.d.ts.map +1 -0
  28. package/dist/framework/branding/tailwind-config.js +66 -0
  29. package/dist/framework/components/BrandedButton.d.ts +53 -0
  30. package/dist/framework/components/BrandedButton.d.ts.map +1 -0
  31. package/dist/framework/components/BrandedButton.js +40 -0
  32. package/dist/framework/components/BrandedCard.d.ts +52 -0
  33. package/dist/framework/components/BrandedCard.d.ts.map +1 -0
  34. package/dist/framework/components/BrandedCard.js +73 -0
  35. package/dist/framework/components/ErrorBoundary.d.ts +92 -0
  36. package/dist/framework/components/ErrorBoundary.d.ts.map +1 -0
  37. package/dist/framework/components/ErrorBoundary.js +121 -0
  38. package/dist/framework/components/HuaUxLayout.d.ts +29 -0
  39. package/dist/framework/components/HuaUxLayout.d.ts.map +1 -0
  40. package/dist/framework/components/HuaUxLayout.js +32 -0
  41. package/dist/framework/components/HuaUxPage.d.ts +48 -0
  42. package/dist/framework/components/HuaUxPage.d.ts.map +1 -0
  43. package/dist/framework/components/HuaUxPage.js +105 -0
  44. package/dist/framework/components/Providers.d.ts +17 -0
  45. package/dist/framework/components/Providers.d.ts.map +1 -0
  46. package/dist/framework/components/Providers.js +72 -0
  47. package/dist/framework/components/WelcomePage.d.ts +44 -0
  48. package/dist/framework/components/WelcomePage.d.ts.map +1 -0
  49. package/dist/framework/components/WelcomePage.js +80 -0
  50. package/dist/framework/config/index.d.ts +182 -0
  51. package/dist/framework/config/index.d.ts.map +1 -0
  52. package/dist/framework/config/index.js +329 -0
  53. package/dist/framework/config/merge.d.ts +26 -0
  54. package/dist/framework/config/merge.d.ts.map +1 -0
  55. package/dist/framework/config/merge.js +160 -0
  56. package/dist/framework/config/schema.d.ts +25 -0
  57. package/dist/framework/config/schema.d.ts.map +1 -0
  58. package/dist/framework/config/schema.js +122 -0
  59. package/dist/framework/hooks/useMotion.d.ts +45 -0
  60. package/dist/framework/hooks/useMotion.d.ts.map +1 -0
  61. package/dist/framework/hooks/useMotion.js +40 -0
  62. package/dist/framework/index.d.ts +37 -0
  63. package/dist/framework/index.d.ts.map +1 -0
  64. package/dist/framework/index.js +42 -0
  65. package/dist/framework/license/errors.d.ts +15 -0
  66. package/dist/framework/license/errors.d.ts.map +1 -0
  67. package/dist/framework/license/errors.js +52 -0
  68. package/dist/framework/license/index.d.ts +70 -0
  69. package/dist/framework/license/index.d.ts.map +1 -0
  70. package/dist/framework/license/index.js +124 -0
  71. package/dist/framework/license/loader.d.ts +26 -0
  72. package/dist/framework/license/loader.d.ts.map +1 -0
  73. package/dist/framework/license/loader.js +137 -0
  74. package/dist/framework/license/types.d.ts +67 -0
  75. package/dist/framework/license/types.d.ts.map +1 -0
  76. package/dist/framework/license/types.js +18 -0
  77. package/dist/framework/loading/components/SkeletonGroup.d.ts +44 -0
  78. package/dist/framework/loading/components/SkeletonGroup.d.ts.map +1 -0
  79. package/dist/framework/loading/components/SkeletonGroup.js +34 -0
  80. package/dist/framework/loading/components/SuspenseWrapper.d.ts +58 -0
  81. package/dist/framework/loading/components/SuspenseWrapper.d.ts.map +1 -0
  82. package/dist/framework/loading/components/SuspenseWrapper.js +40 -0
  83. package/dist/framework/loading/hoc/withSuspense.d.ts +46 -0
  84. package/dist/framework/loading/hoc/withSuspense.d.ts.map +1 -0
  85. package/dist/framework/loading/hoc/withSuspense.js +54 -0
  86. package/dist/framework/loading/hooks/useDelayedLoading.d.ts +56 -0
  87. package/dist/framework/loading/hooks/useDelayedLoading.d.ts.map +1 -0
  88. package/dist/framework/loading/hooks/useDelayedLoading.js +97 -0
  89. package/dist/framework/loading/hooks/useLoadingState.d.ts +69 -0
  90. package/dist/framework/loading/hooks/useLoadingState.d.ts.map +1 -0
  91. package/dist/framework/loading/hooks/useLoadingState.js +59 -0
  92. package/dist/framework/loading/index.d.ts +16 -0
  93. package/dist/framework/loading/index.d.ts.map +1 -0
  94. package/dist/framework/loading/index.js +13 -0
  95. package/dist/framework/middleware/i18n.d.ts +90 -0
  96. package/dist/framework/middleware/i18n.d.ts.map +1 -0
  97. package/dist/framework/middleware/i18n.js +99 -0
  98. package/dist/framework/plugins/index.d.ts +8 -0
  99. package/dist/framework/plugins/index.d.ts.map +1 -0
  100. package/dist/framework/plugins/index.js +6 -0
  101. package/dist/framework/plugins/registry.d.ts +95 -0
  102. package/dist/framework/plugins/registry.d.ts.map +1 -0
  103. package/dist/framework/plugins/registry.js +160 -0
  104. package/dist/framework/plugins/types.d.ts +97 -0
  105. package/dist/framework/plugins/types.d.ts.map +1 -0
  106. package/dist/framework/plugins/types.js +6 -0
  107. package/dist/framework/seo/geo/examples.d.ts +87 -0
  108. package/dist/framework/seo/geo/examples.d.ts.map +1 -0
  109. package/dist/framework/seo/geo/examples.js +295 -0
  110. package/dist/framework/seo/geo/generateGEOMetadata.d.ts +107 -0
  111. package/dist/framework/seo/geo/generateGEOMetadata.d.ts.map +1 -0
  112. package/dist/framework/seo/geo/generateGEOMetadata.js +404 -0
  113. package/dist/framework/seo/geo/index.d.ts +19 -0
  114. package/dist/framework/seo/geo/index.d.ts.map +1 -0
  115. package/dist/framework/seo/geo/index.js +21 -0
  116. package/dist/framework/seo/geo/presets.d.ts +52 -0
  117. package/dist/framework/seo/geo/presets.d.ts.map +1 -0
  118. package/dist/framework/seo/geo/presets.js +47 -0
  119. package/dist/framework/seo/geo/structuredData.d.ts +187 -0
  120. package/dist/framework/seo/geo/structuredData.d.ts.map +1 -0
  121. package/dist/framework/seo/geo/structuredData.js +354 -0
  122. package/dist/framework/seo/geo/test-utils.d.ts +78 -0
  123. package/dist/framework/seo/geo/test-utils.d.ts.map +1 -0
  124. package/dist/framework/seo/geo/test-utils.js +139 -0
  125. package/dist/framework/seo/geo/types.d.ts +225 -0
  126. package/dist/framework/seo/geo/types.d.ts.map +1 -0
  127. package/dist/framework/seo/geo/types.js +51 -0
  128. package/dist/framework/types/index.d.ts +577 -0
  129. package/dist/framework/types/index.d.ts.map +1 -0
  130. package/dist/framework/types/index.js +6 -0
  131. package/dist/framework/utils/data-fetching.d.ts +45 -0
  132. package/dist/framework/utils/data-fetching.d.ts.map +1 -0
  133. package/dist/framework/utils/data-fetching.js +74 -0
  134. package/dist/framework/utils/file-structure.d.ts +29 -0
  135. package/dist/framework/utils/file-structure.d.ts.map +1 -0
  136. package/dist/framework/utils/file-structure.js +72 -0
  137. package/dist/framework/utils/metadata.d.ts +109 -0
  138. package/dist/framework/utils/metadata.d.ts.map +1 -0
  139. package/dist/framework/utils/metadata.js +105 -0
  140. package/dist/index.d.ts +15 -0
  141. package/dist/index.d.ts.map +1 -0
  142. package/dist/index.js +21 -0
  143. package/dist/presets/index.d.ts +8 -0
  144. package/dist/presets/index.d.ts.map +1 -0
  145. package/dist/presets/index.js +7 -0
  146. package/dist/presets/marketing.d.ts +41 -0
  147. package/dist/presets/marketing.d.ts.map +1 -0
  148. package/dist/presets/marketing.js +81 -0
  149. package/dist/presets/product.d.ts +41 -0
  150. package/dist/presets/product.d.ts.map +1 -0
  151. package/dist/presets/product.js +74 -0
  152. package/package.json +91 -0
  153. package/src/framework/README.md +329 -0
  154. package/src/framework/__tests__/branding/css-vars.test.ts +147 -0
  155. package/src/framework/__tests__/components/ErrorBoundary.test.tsx +146 -0
  156. package/src/framework/__tests__/config/defineConfig.test.ts +138 -0
  157. package/src/framework/__tests__/hooks/useMotion.test.ts +105 -0
  158. package/src/framework/__tests__/seo/geo/generateGEOMetadata.test.ts +207 -0
  159. package/src/framework/__tests__/seo/geo/structuredData.test.ts +262 -0
  160. package/src/framework/a11y/components/LiveRegion.tsx +89 -0
  161. package/src/framework/a11y/components/SkipToContent.tsx +103 -0
  162. package/src/framework/a11y/hooks/useFocusManagement.ts +125 -0
  163. package/src/framework/a11y/hooks/useFocusTrap.ts +239 -0
  164. package/src/framework/a11y/hooks/useLiveRegion.ts +95 -0
  165. package/src/framework/a11y/index.ts +17 -0
  166. package/src/framework/branding/context.tsx +135 -0
  167. package/src/framework/branding/css-vars.ts +110 -0
  168. package/src/framework/branding/tailwind-config.ts +90 -0
  169. package/src/framework/components/BrandedButton.tsx +94 -0
  170. package/src/framework/components/BrandedCard.tsx +87 -0
  171. package/src/framework/components/ErrorBoundary.tsx +215 -0
  172. package/src/framework/components/HuaUxLayout.tsx +36 -0
  173. package/src/framework/components/HuaUxPage.tsx +138 -0
  174. package/src/framework/components/Providers.tsx +98 -0
  175. package/src/framework/components/WelcomePage.tsx +207 -0
  176. package/src/framework/config/index.ts +349 -0
  177. package/src/framework/config/merge.ts +190 -0
  178. package/src/framework/config/schema.ts +140 -0
  179. package/src/framework/hooks/useMotion.ts +57 -0
  180. package/src/framework/index.ts +122 -0
  181. package/src/framework/license/errors.ts +63 -0
  182. package/src/framework/license/index.ts +137 -0
  183. package/src/framework/license/loader.ts +158 -0
  184. package/src/framework/license/types.ts +95 -0
  185. package/src/framework/loading/components/SkeletonGroup.tsx +70 -0
  186. package/src/framework/loading/components/SuspenseWrapper.tsx +88 -0
  187. package/src/framework/loading/hoc/withSuspense.tsx +96 -0
  188. package/src/framework/loading/hooks/useDelayedLoading.ts +127 -0
  189. package/src/framework/loading/hooks/useLoadingState.ts +103 -0
  190. package/src/framework/loading/index.ts +19 -0
  191. package/src/framework/middleware/i18n.ts +161 -0
  192. package/src/framework/middleware/index.ts +7 -0
  193. package/src/framework/plugins/index.ts +13 -0
  194. package/src/framework/plugins/registry.ts +186 -0
  195. package/src/framework/plugins/types.ts +106 -0
  196. package/src/framework/seo/geo/examples.tsx +415 -0
  197. package/src/framework/seo/geo/generateGEOMetadata.ts +441 -0
  198. package/src/framework/seo/geo/index.ts +61 -0
  199. package/src/framework/seo/geo/presets.ts +58 -0
  200. package/src/framework/seo/geo/structuredData.ts +422 -0
  201. package/src/framework/seo/geo/test-utils.ts +179 -0
  202. package/src/framework/seo/geo/types.ts +315 -0
  203. package/src/framework/types/index.ts +623 -0
  204. package/src/framework/utils/data-fetching.ts +95 -0
  205. package/src/framework/utils/file-structure.ts +88 -0
  206. package/src/framework/utils/metadata.ts +152 -0
  207. package/src/index.ts +31 -0
  208. package/src/presets/index.ts +8 -0
  209. package/src/presets/marketing.ts +88 -0
  210. package/src/presets/product.ts +81 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Product Preset
3
+ *
4
+ * 기본 여백 + 기본 모션 설정
5
+ * 제품 페이지, 대시보드 등 일반적인 제품 UI에 적합
6
+ */
7
+ /**
8
+ * Product Preset Configuration
9
+ *
10
+ * - 보수적인 모션 (빠른 전환, 최소한의 딜레이)
11
+ * - 일관된 스페이싱 (md 기본값)
12
+ * - 호버/클릭 인터랙션 최소화
13
+ */
14
+ export const productPreset = {
15
+ /**
16
+ * Motion Presets for Product UI
17
+ */
18
+ motion: {
19
+ hero: {
20
+ entrance: 'fadeIn',
21
+ delay: 100,
22
+ duration: 400,
23
+ hover: false,
24
+ click: false
25
+ },
26
+ title: {
27
+ entrance: 'slideUp',
28
+ delay: 150,
29
+ duration: 350,
30
+ hover: false,
31
+ click: false
32
+ },
33
+ button: {
34
+ entrance: 'scaleIn',
35
+ delay: 200,
36
+ duration: 200,
37
+ hover: true,
38
+ click: true
39
+ },
40
+ card: {
41
+ entrance: 'slideUp',
42
+ delay: 100,
43
+ duration: 300,
44
+ hover: true,
45
+ click: false
46
+ },
47
+ text: {
48
+ entrance: 'fadeIn',
49
+ delay: 50,
50
+ duration: 300,
51
+ hover: false,
52
+ click: false
53
+ }
54
+ },
55
+ /**
56
+ * Spacing Guidelines
57
+ *
58
+ * - 기본 스페이싱: md (8px)
59
+ * - 섹션 간격: lg (16px)
60
+ * - 컴포넌트 내부: sm (4px)
61
+ */
62
+ spacing: {
63
+ default: 'md',
64
+ section: 'lg',
65
+ component: 'sm'
66
+ },
67
+ /**
68
+ * i18n Configuration
69
+ */
70
+ i18n: {
71
+ defaultLanguage: 'ko',
72
+ supportedLanguages: ['ko', 'en']
73
+ }
74
+ };
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@hua-labs/hua-ux",
3
+ "version": "0.1.0-alpha.0.1",
4
+ "description": "HUA UX - Ship UX faster: UI + motion + i18n, pre-wired. A framework for React product teams.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./src/index.ts",
12
+ "require": "./src/index.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./framework": {
16
+ "types": "./dist/framework/index.d.ts",
17
+ "import": "./src/framework/index.ts",
18
+ "require": "./src/framework/index.ts",
19
+ "default": "./dist/framework/index.js"
20
+ },
21
+ "./presets": {
22
+ "types": "./dist/presets/index.d.ts",
23
+ "import": "./src/presets/index.ts",
24
+ "require": "./src/presets/index.ts",
25
+ "default": "./dist/presets/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "src"
31
+ ],
32
+ "dependencies": {
33
+ "@hua-labs/i18n-core": "2.0.0",
34
+ "@hua-labs/i18n-core-zustand": "2.0.0",
35
+ "@hua-labs/motion-core": "2.0.1",
36
+ "@hua-labs/state": "0.1.0-alpha.0.1",
37
+ "@hua-labs/ui": "1.1.0-alpha.0.1"
38
+ },
39
+ "peerDependencies": {
40
+ "next": ">=13.0.0",
41
+ "react": ">=16.8.0",
42
+ "react-dom": ">=16.8.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "next": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "keywords": [
50
+ "hua-ux",
51
+ "framework",
52
+ "react",
53
+ "ui",
54
+ "motion",
55
+ "i18n",
56
+ "typescript",
57
+ "nextjs"
58
+ ],
59
+ "author": "HUA Labs",
60
+ "license": "MIT",
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "https://github.com/HUA-Labs/HUA-Labs-public.git"
64
+ },
65
+ "bugs": {
66
+ "url": "https://github.com/HUA-Labs/HUA-Labs-public/issues"
67
+ },
68
+ "homepage": "https://github.com/HUA-Labs/HUA-Labs-public#readme",
69
+ "devDependencies": {
70
+ "@testing-library/jest-dom": "^6.8.0",
71
+ "@testing-library/react": "^16.3.0",
72
+ "@testing-library/user-event": "^14.6.1",
73
+ "@types/node": "^25.0.3",
74
+ "@types/react": "^19.2.7",
75
+ "@types/react-dom": "^19.2.3",
76
+ "@vitejs/plugin-react": "^5.1.1",
77
+ "jsdom": "^27.2.0",
78
+ "typescript": "^5.9.3",
79
+ "vitest": "^4.0.14"
80
+ },
81
+ "scripts": {
82
+ "build": "node ../../node_modules/typescript/lib/tsc.js",
83
+ "dev": "node ../../node_modules/typescript/lib/tsc.js --watch",
84
+ "clean": "rm -rf dist",
85
+ "lint": "echo 'Skipping lint for hua-ux (ESLint 8, needs separate config)'",
86
+ "type-check": "tsc --noEmit",
87
+ "test": "vitest",
88
+ "test:ui": "vitest --ui",
89
+ "test:coverage": "vitest --coverage"
90
+ }
91
+ }
@@ -0,0 +1,329 @@
1
+ # @hua-labs/hua-ux/framework
2
+
3
+ **Framework layer for hua-ux** - Structure and rules enforcement with developer affordances.
4
+
5
+ A framework layer that wraps Next.js to enforce structure and conventions while providing maximum convenience through affordances.
6
+
7
+ ## Installation
8
+
9
+ This is part of `@hua-labs/hua-ux`. Install the main package:
10
+
11
+ ```bash
12
+ pnpm add @hua-labs/hua-ux zustand
13
+ ```
14
+
15
+ ## Features
16
+
17
+ - ✅ **Automatic Provider Setup**: `HuaUxLayout` automatically configures i18n, motion, and state
18
+ - ✅ **Configuration System**: Type-safe configuration via `hua-ux.config.ts`
19
+ - ✅ **Data Fetching**: Type-safe utilities for server and client components
20
+ - ✅ **Middleware System**: Built-in i18n middleware for language detection
21
+ - ✅ **File Structure Validation**: Ensures project follows framework conventions
22
+ - ✅ **Type Safe**: Full TypeScript support with autocomplete
23
+
24
+ ## Quick Start
25
+
26
+ ### 사용 방법 선택
27
+
28
+ **프레임워크 레이어를 사용하는 방법 (권장)**:
29
+ - `HuaUxLayout`을 사용하면 모든 Provider가 자동으로 설정됩니다
30
+ - 설정 파일만으로 간단하게 시작할 수 있습니다
31
+
32
+ **직접 사용하는 방법**:
33
+ - 더 세밀한 제어가 필요한 경우 `createI18nStore` + `createZustandI18n` 조합 사용
34
+ - 자세한 내용은 [메인 README](../../README.md)의 "방법 2: 직접 사용" 섹션 참고
35
+
36
+ ### 1. Configuration
37
+
38
+ Create `hua-ux.config.ts` in your project root:
39
+
40
+ ```tsx
41
+ import { defineConfig } from '@hua-labs/hua-ux/framework';
42
+
43
+ export default defineConfig({
44
+ i18n: {
45
+ defaultLanguage: 'ko',
46
+ supportedLanguages: ['ko', 'en'],
47
+ namespaces: ['common'],
48
+ translationLoader: 'api',
49
+ translationApiPath: '/api/translations',
50
+ },
51
+ motion: {
52
+ defaultPreset: 'product',
53
+ enableAnimations: true,
54
+ },
55
+ state: {
56
+ persist: true,
57
+ ssr: true,
58
+ },
59
+ });
60
+ ```
61
+
62
+ ### 2. Root Layout
63
+
64
+ ```tsx
65
+ // app/layout.tsx
66
+ import { HuaUxLayout } from '@hua-labs/hua-ux/framework';
67
+
68
+ export default function RootLayout({ children }) {
69
+ return (
70
+ <html lang="ko">
71
+ <body>
72
+ <HuaUxLayout>{children}</HuaUxLayout>
73
+ </body>
74
+ </html>
75
+ );
76
+ }
77
+ ```
78
+
79
+ ### 3. Pages
80
+
81
+ #### Basic Page (Client Component)
82
+
83
+ ```tsx
84
+ // app/page.tsx
85
+ 'use client';
86
+
87
+ import { HuaUxPage } from '@hua-labs/hua-ux/framework';
88
+
89
+ export default function HomePage() {
90
+ return (
91
+ <HuaUxPage title="Home" description="Welcome page">
92
+ <h1>Welcome</h1>
93
+ </HuaUxPage>
94
+ );
95
+ }
96
+ ```
97
+
98
+ #### Page with SEO Metadata (Server Component + Client Component)
99
+
100
+ For better SEO, use Next.js `metadata` export in Server Components:
101
+
102
+ ```tsx
103
+ // app/page.tsx
104
+ import { generatePageMetadata } from '@hua-labs/hua-ux/framework';
105
+ import { HomePageContent } from './HomePageContent';
106
+
107
+ export const metadata = generatePageMetadata({
108
+ title: '홈',
109
+ description: '환영합니다',
110
+ seo: {
111
+ keywords: ['키워드1', '키워드2'],
112
+ ogImage: '/og-image.png',
113
+ ogTitle: '홈 | 우리 회사',
114
+ ogDescription: '우리 회사에 오신 것을 환영합니다',
115
+ },
116
+ });
117
+
118
+ export default function HomePage() {
119
+ return <HomePageContent />;
120
+ }
121
+ ```
122
+
123
+ ```tsx
124
+ // app/HomePageContent.tsx (Client Component)
125
+ 'use client';
126
+
127
+ import { HuaUxPage } from '@hua-labs/hua-ux/framework';
128
+
129
+ export function HomePageContent() {
130
+ return (
131
+ <HuaUxPage title="Home" description="Welcome page">
132
+ <h1>Welcome</h1>
133
+ </HuaUxPage>
134
+ );
135
+ }
136
+ ```
137
+
138
+ ## API
139
+
140
+ ### Components
141
+
142
+ #### `HuaUxLayout`
143
+
144
+ Root layout wrapper that automatically sets up all providers.
145
+
146
+ ```tsx
147
+ <HuaUxLayout config={overrideConfig}>
148
+ {children}
149
+ </HuaUxLayout>
150
+ ```
151
+
152
+ #### `HuaUxPage`
153
+
154
+ Page wrapper with automatic motion and i18n support.
155
+
156
+ ```tsx
157
+ <HuaUxPage title="Page Title" enableMotion={true}>
158
+ {children}
159
+ </HuaUxPage>
160
+ ```
161
+
162
+ ### Configuration
163
+
164
+ #### `defineConfig(config)`
165
+
166
+ Define framework configuration.
167
+
168
+ ```tsx
169
+ export default defineConfig({
170
+ i18n: { /* ... */ },
171
+ motion: { /* ... */ },
172
+ state: { /* ... */ },
173
+ });
174
+ ```
175
+
176
+ ### Data Fetching
177
+
178
+ #### `useData<T>(url, options?)`
179
+
180
+ Client-side data fetching hook.
181
+
182
+ **Parameters**:
183
+ - `url`: API endpoint to fetch from
184
+ - `options`: Optional `RequestInit` configuration (standard fetch options)
185
+ - **Note**: Currently supports standard `RequestInit` only. For advanced features like `revalidateOnFocus`, `revalidateOnReconnect`, or `refreshInterval`, consider using SWR or React Query.
186
+
187
+ **Returns**:
188
+ - `data`: Fetched data (or `null` if loading/error)
189
+ - `isLoading`: Loading state
190
+ - `error`: Error object (or `null`)
191
+ - `refetch`: Function to manually refetch data
192
+
193
+ **Usage in Client Components**:
194
+
195
+ ```tsx
196
+ 'use client';
197
+
198
+ import { useData } from '@hua-labs/hua-ux/framework';
199
+
200
+ interface Post {
201
+ id: string;
202
+ title: string;
203
+ }
204
+
205
+ export default function PostsPage() {
206
+ const { data, isLoading, error, refetch } = useData<Post[]>('/api/posts');
207
+
208
+ if (isLoading) return <div>Loading...</div>;
209
+ if (error) return <div>Error: {error.message}</div>;
210
+
211
+ return (
212
+ <div>
213
+ {data?.map(post => (
214
+ <div key={post.id}>{post.title}</div>
215
+ ))}
216
+ <button onClick={() => refetch()}>Refresh</button>
217
+ </div>
218
+ );
219
+ }
220
+ ```
221
+
222
+ #### `fetchData<T>(url, options?)`
223
+
224
+ Server-side data fetching utility.
225
+
226
+ **Parameters**:
227
+ - `url`: API endpoint to fetch from
228
+ - `options`: Optional `RequestInit` configuration (standard fetch options)
229
+
230
+ **Returns**: Promise that resolves to the fetched data
231
+
232
+ **Throws**: `Error` if the request fails or response is not ok
233
+
234
+ **Usage in Server Components**:
235
+
236
+ ```tsx
237
+ // app/posts/page.tsx (Server Component)
238
+ import { fetchData } from '@hua-labs/hua-ux/framework';
239
+
240
+ interface Post {
241
+ id: string;
242
+ title: string;
243
+ }
244
+
245
+ export default async function PostsPage() {
246
+ try {
247
+ const posts = await fetchData<Post[]>('/api/posts');
248
+
249
+ return (
250
+ <div>
251
+ {posts.map(post => (
252
+ <div key={post.id}>{post.title}</div>
253
+ ))}
254
+ </div>
255
+ );
256
+ } catch (error) {
257
+ // Handle error appropriately
258
+ console.error('Failed to fetch posts:', error);
259
+ return <div>Error loading posts. Please try again later.</div>;
260
+ }
261
+ }
262
+ ```
263
+
264
+ **Error Handling**:
265
+ Always wrap `fetchData` calls in try-catch blocks in Server Components, as it throws errors on failure.
266
+
267
+ ### Middleware
268
+
269
+ #### `createI18nMiddleware(config)`
270
+
271
+ Create i18n middleware for Next.js.
272
+
273
+ **⚠️ Edge Runtime Required**: Next.js middleware runs on Edge Runtime. Make sure to export the runtime config:
274
+
275
+ ```tsx
276
+ // middleware.ts
277
+ import { createI18nMiddleware } from '@hua-labs/hua-ux/framework';
278
+
279
+ // Edge Runtime 명시 (권장)
280
+ // Next.js middleware는 기본적으로 Edge Runtime에서 실행됩니다.
281
+ // Vercel과 같은 플랫폼은 자동으로 Edge Runtime을 감지하지만,
282
+ // 명시적으로 설정하는 것이 좋습니다.
283
+ // Next.js middleware runs on Edge Runtime by default.
284
+ // Platforms like Vercel auto-detect Edge Runtime, but explicit configuration is recommended.
285
+ export const runtime = 'edge';
286
+
287
+ export default createI18nMiddleware({
288
+ defaultLanguage: 'ko',
289
+ supportedLanguages: ['ko', 'en'],
290
+ detectionStrategy: 'header',
291
+ });
292
+ ```
293
+
294
+ **Edge Runtime 제약사항 / Edge Runtime Limitations**:
295
+ - Node.js API 사용 불가 (fs, path 등) / Cannot use Node.js APIs (fs, path, etc.)
296
+ - 일부 npm 패키지가 Edge Runtime과 호환되지 않을 수 있음 / Some npm packages may not be compatible with Edge Runtime
297
+ - Next.js middleware는 기본적으로 Edge Runtime에서 실행됩니다 / Next.js middleware runs on Edge Runtime by default
298
+
299
+ **대안 / Alternatives**:
300
+ - Edge Runtime을 사용하지 않으려면 API Route나 클라이언트 컴포넌트에서 언어 감지를 처리할 수 있습니다.
301
+ - If you don't want to use Edge Runtime, you can handle language detection in API routes or client components.
302
+
303
+ **Edge Runtime에서 사용 불가능한 기능 / Features Not Available in Edge Runtime**:
304
+ - `loadConfig()`: 설정 파일 동적 로드 (서버 전용) / Dynamic config file loading (server-only)
305
+ - `validateFileStructure()`: 파일 구조 검증 (서버 전용) / File structure validation (server-only)
306
+ - Node.js 모듈 사용 (`fs`, `path` 등) / Using Node.js modules (`fs`, `path`, etc.)
307
+
308
+ **권장 사항 / Recommendations**:
309
+ - 미들웨어는 선택적 기능입니다. 사용하지 않아도 프레임워크는 정상 작동합니다.
310
+ - Middleware is optional. The framework works fine without it.
311
+ - 언어 감지는 클라이언트 컴포넌트나 API Route에서 처리할 수 있습니다.
312
+ - Language detection can be handled in client components or API routes.
313
+
314
+ ### File Structure
315
+
316
+ #### `validateFileStructure(projectRoot)`
317
+
318
+ Validate project file structure.
319
+
320
+ ```tsx
321
+ const result = validateFileStructure(process.cwd());
322
+ if (!result.valid) {
323
+ console.error('Missing directories:', result.missing);
324
+ }
325
+ ```
326
+
327
+ ## License
328
+
329
+ MIT
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - CSS Variables Generator Tests
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { generateCSSVariables, generateCSSVariablesObject } from '../../branding/css-vars';
7
+ import type { HuaUxConfig } from '../../types';
8
+
9
+ describe('generateCSSVariables', () => {
10
+ it('should generate empty string for empty branding', () => {
11
+ const branding = {} as NonNullable<HuaUxConfig['branding']>;
12
+ const result = generateCSSVariables(branding);
13
+
14
+ expect(result).toBe('');
15
+ });
16
+
17
+ it('should generate color variables', () => {
18
+ const branding: NonNullable<HuaUxConfig['branding']> = {
19
+ colors: {
20
+ primary: '#3B82F6',
21
+ secondary: '#8B5CF6',
22
+ },
23
+ };
24
+
25
+ const result = generateCSSVariables(branding);
26
+
27
+ expect(result).toContain('--color-primary: #3B82F6');
28
+ expect(result).toContain('--color-secondary: #8B5CF6');
29
+ expect(result).toContain(':root');
30
+ });
31
+
32
+ it('should generate typography variables', () => {
33
+ const branding: NonNullable<HuaUxConfig['branding']> = {
34
+ typography: {
35
+ fontFamily: ['Inter', 'sans-serif'],
36
+ fontSize: {
37
+ sm: '0.875rem',
38
+ md: '1rem',
39
+ },
40
+ },
41
+ };
42
+
43
+ const result = generateCSSVariables(branding);
44
+
45
+ expect(result).toContain('--font-family: Inter, sans-serif');
46
+ expect(result).toContain('--font-size-sm: 0.875rem');
47
+ expect(result).toContain('--font-size-md: 1rem');
48
+ });
49
+
50
+ it('should generate custom variables', () => {
51
+ const branding: NonNullable<HuaUxConfig['branding']> = {
52
+ customVariables: {
53
+ 'spacing-unit': '8px',
54
+ 'border-radius': '4px',
55
+ },
56
+ };
57
+
58
+ const result = generateCSSVariables(branding);
59
+
60
+ expect(result).toContain('--spacing-unit: 8px');
61
+ expect(result).toContain('--border-radius: 4px');
62
+ });
63
+
64
+ it('should generate all variable types together', () => {
65
+ const branding: NonNullable<HuaUxConfig['branding']> = {
66
+ colors: {
67
+ primary: '#3B82F6',
68
+ },
69
+ typography: {
70
+ fontFamily: ['Inter', 'sans-serif'],
71
+ },
72
+ customVariables: {
73
+ 'spacing-unit': '8px',
74
+ },
75
+ };
76
+
77
+ const result = generateCSSVariables(branding);
78
+
79
+ expect(result).toContain('--color-primary: #3B82F6');
80
+ expect(result).toContain('--font-family: Inter, sans-serif');
81
+ expect(result).toContain('--spacing-unit: 8px');
82
+ });
83
+
84
+ it('should format output correctly', () => {
85
+ const branding: NonNullable<HuaUxConfig['branding']> = {
86
+ colors: {
87
+ primary: '#3B82F6',
88
+ },
89
+ };
90
+
91
+ const result = generateCSSVariables(branding);
92
+
93
+ expect(result).toMatch(/^:root \{[^}]*\}$/);
94
+ expect(result).toContain('\n');
95
+ });
96
+ });
97
+
98
+ describe('generateCSSVariablesObject', () => {
99
+ it('should generate empty object for empty branding', () => {
100
+ const branding = {} as NonNullable<HuaUxConfig['branding']>;
101
+ const result = generateCSSVariablesObject(branding);
102
+
103
+ expect(result).toEqual({});
104
+ });
105
+
106
+ it('should generate color variables as object', () => {
107
+ const branding: NonNullable<HuaUxConfig['branding']> = {
108
+ colors: {
109
+ primary: '#3B82F6',
110
+ secondary: '#8B5CF6',
111
+ },
112
+ };
113
+
114
+ const result = generateCSSVariablesObject(branding);
115
+
116
+ expect(result['--color-primary']).toBe('#3B82F6');
117
+ expect(result['--color-secondary']).toBe('#8B5CF6');
118
+ });
119
+
120
+ it('should generate typography variables as object', () => {
121
+ const branding: NonNullable<HuaUxConfig['branding']> = {
122
+ typography: {
123
+ fontFamily: ['Inter', 'sans-serif'],
124
+ fontSize: {
125
+ sm: '0.875rem',
126
+ },
127
+ },
128
+ };
129
+
130
+ const result = generateCSSVariablesObject(branding);
131
+
132
+ expect(result['--font-family']).toBe('Inter, sans-serif');
133
+ expect(result['--font-size-sm']).toBe('0.875rem');
134
+ });
135
+
136
+ it('should generate custom variables as object', () => {
137
+ const branding: NonNullable<HuaUxConfig['branding']> = {
138
+ customVariables: {
139
+ 'spacing-unit': '8px',
140
+ },
141
+ };
142
+
143
+ const result = generateCSSVariablesObject(branding);
144
+
145
+ expect(result['--spacing-unit']).toBe('8px');
146
+ });
147
+ });