@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.
- package/README.md +839 -0
- package/dist/framework/a11y/components/LiveRegion.d.ts +64 -0
- package/dist/framework/a11y/components/LiveRegion.d.ts.map +1 -0
- package/dist/framework/a11y/components/LiveRegion.js +43 -0
- package/dist/framework/a11y/components/SkipToContent.d.ts +62 -0
- package/dist/framework/a11y/components/SkipToContent.d.ts.map +1 -0
- package/dist/framework/a11y/components/SkipToContent.js +60 -0
- package/dist/framework/a11y/hooks/useFocusManagement.d.ts +60 -0
- package/dist/framework/a11y/hooks/useFocusManagement.d.ts.map +1 -0
- package/dist/framework/a11y/hooks/useFocusManagement.js +71 -0
- package/dist/framework/a11y/hooks/useFocusTrap.d.ts +64 -0
- package/dist/framework/a11y/hooks/useFocusTrap.d.ts.map +1 -0
- package/dist/framework/a11y/hooks/useFocusTrap.js +185 -0
- package/dist/framework/a11y/hooks/useLiveRegion.d.ts +56 -0
- package/dist/framework/a11y/hooks/useLiveRegion.d.ts.map +1 -0
- package/dist/framework/a11y/hooks/useLiveRegion.js +60 -0
- package/dist/framework/a11y/index.d.ts +16 -0
- package/dist/framework/a11y/index.d.ts.map +1 -0
- package/dist/framework/a11y/index.js +11 -0
- package/dist/framework/branding/context.d.ts +52 -0
- package/dist/framework/branding/context.d.ts.map +1 -0
- package/dist/framework/branding/context.js +96 -0
- package/dist/framework/branding/css-vars.d.ts +34 -0
- package/dist/framework/branding/css-vars.d.ts.map +1 -0
- package/dist/framework/branding/css-vars.js +95 -0
- package/dist/framework/branding/tailwind-config.d.ts +38 -0
- package/dist/framework/branding/tailwind-config.d.ts.map +1 -0
- package/dist/framework/branding/tailwind-config.js +66 -0
- package/dist/framework/components/BrandedButton.d.ts +53 -0
- package/dist/framework/components/BrandedButton.d.ts.map +1 -0
- package/dist/framework/components/BrandedButton.js +40 -0
- package/dist/framework/components/BrandedCard.d.ts +52 -0
- package/dist/framework/components/BrandedCard.d.ts.map +1 -0
- package/dist/framework/components/BrandedCard.js +73 -0
- package/dist/framework/components/ErrorBoundary.d.ts +92 -0
- package/dist/framework/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/framework/components/ErrorBoundary.js +121 -0
- package/dist/framework/components/HuaUxLayout.d.ts +29 -0
- package/dist/framework/components/HuaUxLayout.d.ts.map +1 -0
- package/dist/framework/components/HuaUxLayout.js +32 -0
- package/dist/framework/components/HuaUxPage.d.ts +48 -0
- package/dist/framework/components/HuaUxPage.d.ts.map +1 -0
- package/dist/framework/components/HuaUxPage.js +105 -0
- package/dist/framework/components/Providers.d.ts +17 -0
- package/dist/framework/components/Providers.d.ts.map +1 -0
- package/dist/framework/components/Providers.js +72 -0
- package/dist/framework/components/WelcomePage.d.ts +44 -0
- package/dist/framework/components/WelcomePage.d.ts.map +1 -0
- package/dist/framework/components/WelcomePage.js +80 -0
- package/dist/framework/config/index.d.ts +182 -0
- package/dist/framework/config/index.d.ts.map +1 -0
- package/dist/framework/config/index.js +329 -0
- package/dist/framework/config/merge.d.ts +26 -0
- package/dist/framework/config/merge.d.ts.map +1 -0
- package/dist/framework/config/merge.js +160 -0
- package/dist/framework/config/schema.d.ts +25 -0
- package/dist/framework/config/schema.d.ts.map +1 -0
- package/dist/framework/config/schema.js +122 -0
- package/dist/framework/hooks/useMotion.d.ts +45 -0
- package/dist/framework/hooks/useMotion.d.ts.map +1 -0
- package/dist/framework/hooks/useMotion.js +40 -0
- package/dist/framework/index.d.ts +37 -0
- package/dist/framework/index.d.ts.map +1 -0
- package/dist/framework/index.js +42 -0
- package/dist/framework/license/errors.d.ts +15 -0
- package/dist/framework/license/errors.d.ts.map +1 -0
- package/dist/framework/license/errors.js +52 -0
- package/dist/framework/license/index.d.ts +70 -0
- package/dist/framework/license/index.d.ts.map +1 -0
- package/dist/framework/license/index.js +124 -0
- package/dist/framework/license/loader.d.ts +26 -0
- package/dist/framework/license/loader.d.ts.map +1 -0
- package/dist/framework/license/loader.js +137 -0
- package/dist/framework/license/types.d.ts +67 -0
- package/dist/framework/license/types.d.ts.map +1 -0
- package/dist/framework/license/types.js +18 -0
- package/dist/framework/loading/components/SkeletonGroup.d.ts +44 -0
- package/dist/framework/loading/components/SkeletonGroup.d.ts.map +1 -0
- package/dist/framework/loading/components/SkeletonGroup.js +34 -0
- package/dist/framework/loading/components/SuspenseWrapper.d.ts +58 -0
- package/dist/framework/loading/components/SuspenseWrapper.d.ts.map +1 -0
- package/dist/framework/loading/components/SuspenseWrapper.js +40 -0
- package/dist/framework/loading/hoc/withSuspense.d.ts +46 -0
- package/dist/framework/loading/hoc/withSuspense.d.ts.map +1 -0
- package/dist/framework/loading/hoc/withSuspense.js +54 -0
- package/dist/framework/loading/hooks/useDelayedLoading.d.ts +56 -0
- package/dist/framework/loading/hooks/useDelayedLoading.d.ts.map +1 -0
- package/dist/framework/loading/hooks/useDelayedLoading.js +97 -0
- package/dist/framework/loading/hooks/useLoadingState.d.ts +69 -0
- package/dist/framework/loading/hooks/useLoadingState.d.ts.map +1 -0
- package/dist/framework/loading/hooks/useLoadingState.js +59 -0
- package/dist/framework/loading/index.d.ts +16 -0
- package/dist/framework/loading/index.d.ts.map +1 -0
- package/dist/framework/loading/index.js +13 -0
- package/dist/framework/middleware/i18n.d.ts +90 -0
- package/dist/framework/middleware/i18n.d.ts.map +1 -0
- package/dist/framework/middleware/i18n.js +99 -0
- package/dist/framework/plugins/index.d.ts +8 -0
- package/dist/framework/plugins/index.d.ts.map +1 -0
- package/dist/framework/plugins/index.js +6 -0
- package/dist/framework/plugins/registry.d.ts +95 -0
- package/dist/framework/plugins/registry.d.ts.map +1 -0
- package/dist/framework/plugins/registry.js +160 -0
- package/dist/framework/plugins/types.d.ts +97 -0
- package/dist/framework/plugins/types.d.ts.map +1 -0
- package/dist/framework/plugins/types.js +6 -0
- package/dist/framework/seo/geo/examples.d.ts +87 -0
- package/dist/framework/seo/geo/examples.d.ts.map +1 -0
- package/dist/framework/seo/geo/examples.js +295 -0
- package/dist/framework/seo/geo/generateGEOMetadata.d.ts +107 -0
- package/dist/framework/seo/geo/generateGEOMetadata.d.ts.map +1 -0
- package/dist/framework/seo/geo/generateGEOMetadata.js +404 -0
- package/dist/framework/seo/geo/index.d.ts +19 -0
- package/dist/framework/seo/geo/index.d.ts.map +1 -0
- package/dist/framework/seo/geo/index.js +21 -0
- package/dist/framework/seo/geo/presets.d.ts +52 -0
- package/dist/framework/seo/geo/presets.d.ts.map +1 -0
- package/dist/framework/seo/geo/presets.js +47 -0
- package/dist/framework/seo/geo/structuredData.d.ts +187 -0
- package/dist/framework/seo/geo/structuredData.d.ts.map +1 -0
- package/dist/framework/seo/geo/structuredData.js +354 -0
- package/dist/framework/seo/geo/test-utils.d.ts +78 -0
- package/dist/framework/seo/geo/test-utils.d.ts.map +1 -0
- package/dist/framework/seo/geo/test-utils.js +139 -0
- package/dist/framework/seo/geo/types.d.ts +225 -0
- package/dist/framework/seo/geo/types.d.ts.map +1 -0
- package/dist/framework/seo/geo/types.js +51 -0
- package/dist/framework/types/index.d.ts +577 -0
- package/dist/framework/types/index.d.ts.map +1 -0
- package/dist/framework/types/index.js +6 -0
- package/dist/framework/utils/data-fetching.d.ts +45 -0
- package/dist/framework/utils/data-fetching.d.ts.map +1 -0
- package/dist/framework/utils/data-fetching.js +74 -0
- package/dist/framework/utils/file-structure.d.ts +29 -0
- package/dist/framework/utils/file-structure.d.ts.map +1 -0
- package/dist/framework/utils/file-structure.js +72 -0
- package/dist/framework/utils/metadata.d.ts +109 -0
- package/dist/framework/utils/metadata.d.ts.map +1 -0
- package/dist/framework/utils/metadata.js +105 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/presets/index.d.ts +8 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +7 -0
- package/dist/presets/marketing.d.ts +41 -0
- package/dist/presets/marketing.d.ts.map +1 -0
- package/dist/presets/marketing.js +81 -0
- package/dist/presets/product.d.ts +41 -0
- package/dist/presets/product.d.ts.map +1 -0
- package/dist/presets/product.js +74 -0
- package/package.json +91 -0
- package/src/framework/README.md +329 -0
- package/src/framework/__tests__/branding/css-vars.test.ts +147 -0
- package/src/framework/__tests__/components/ErrorBoundary.test.tsx +146 -0
- package/src/framework/__tests__/config/defineConfig.test.ts +138 -0
- package/src/framework/__tests__/hooks/useMotion.test.ts +105 -0
- package/src/framework/__tests__/seo/geo/generateGEOMetadata.test.ts +207 -0
- package/src/framework/__tests__/seo/geo/structuredData.test.ts +262 -0
- package/src/framework/a11y/components/LiveRegion.tsx +89 -0
- package/src/framework/a11y/components/SkipToContent.tsx +103 -0
- package/src/framework/a11y/hooks/useFocusManagement.ts +125 -0
- package/src/framework/a11y/hooks/useFocusTrap.ts +239 -0
- package/src/framework/a11y/hooks/useLiveRegion.ts +95 -0
- package/src/framework/a11y/index.ts +17 -0
- package/src/framework/branding/context.tsx +135 -0
- package/src/framework/branding/css-vars.ts +110 -0
- package/src/framework/branding/tailwind-config.ts +90 -0
- package/src/framework/components/BrandedButton.tsx +94 -0
- package/src/framework/components/BrandedCard.tsx +87 -0
- package/src/framework/components/ErrorBoundary.tsx +215 -0
- package/src/framework/components/HuaUxLayout.tsx +36 -0
- package/src/framework/components/HuaUxPage.tsx +138 -0
- package/src/framework/components/Providers.tsx +98 -0
- package/src/framework/components/WelcomePage.tsx +207 -0
- package/src/framework/config/index.ts +349 -0
- package/src/framework/config/merge.ts +190 -0
- package/src/framework/config/schema.ts +140 -0
- package/src/framework/hooks/useMotion.ts +57 -0
- package/src/framework/index.ts +122 -0
- package/src/framework/license/errors.ts +63 -0
- package/src/framework/license/index.ts +137 -0
- package/src/framework/license/loader.ts +158 -0
- package/src/framework/license/types.ts +95 -0
- package/src/framework/loading/components/SkeletonGroup.tsx +70 -0
- package/src/framework/loading/components/SuspenseWrapper.tsx +88 -0
- package/src/framework/loading/hoc/withSuspense.tsx +96 -0
- package/src/framework/loading/hooks/useDelayedLoading.ts +127 -0
- package/src/framework/loading/hooks/useLoadingState.ts +103 -0
- package/src/framework/loading/index.ts +19 -0
- package/src/framework/middleware/i18n.ts +161 -0
- package/src/framework/middleware/index.ts +7 -0
- package/src/framework/plugins/index.ts +13 -0
- package/src/framework/plugins/registry.ts +186 -0
- package/src/framework/plugins/types.ts +106 -0
- package/src/framework/seo/geo/examples.tsx +415 -0
- package/src/framework/seo/geo/generateGEOMetadata.ts +441 -0
- package/src/framework/seo/geo/index.ts +61 -0
- package/src/framework/seo/geo/presets.ts +58 -0
- package/src/framework/seo/geo/structuredData.ts +422 -0
- package/src/framework/seo/geo/test-utils.ts +179 -0
- package/src/framework/seo/geo/types.ts +315 -0
- package/src/framework/types/index.ts +623 -0
- package/src/framework/utils/data-fetching.ts +95 -0
- package/src/framework/utils/file-structure.ts +88 -0
- package/src/framework/utils/metadata.ts +152 -0
- package/src/index.ts +31 -0
- package/src/presets/index.ts +8 -0
- package/src/presets/marketing.ts +88 -0
- 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
|
+
});
|