@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,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - Config Merge
|
|
3
|
+
*
|
|
4
|
+
* Preset 병합 및 깊은 병합 로직
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HuaUxConfig, Preset, PresetName, PresetConfig } from '../types';
|
|
8
|
+
import { productPreset } from '../../presets/product';
|
|
9
|
+
import { marketingPreset } from '../../presets/marketing';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Preset 맵
|
|
13
|
+
*/
|
|
14
|
+
const PRESET_MAP: Record<PresetName, any> = {
|
|
15
|
+
product: productPreset,
|
|
16
|
+
marketing: marketingPreset,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 깊은 병합 (Deep Merge)
|
|
21
|
+
*
|
|
22
|
+
* 중첩된 객체를 재귀적으로 병합합니다.
|
|
23
|
+
* 사용자 설정이 Preset 설정보다 우선합니다.
|
|
24
|
+
*/
|
|
25
|
+
function deepMerge<T extends Record<string, any>>(
|
|
26
|
+
target: T,
|
|
27
|
+
source: Partial<T>
|
|
28
|
+
): T {
|
|
29
|
+
const result = { ...target };
|
|
30
|
+
|
|
31
|
+
for (const key in source) {
|
|
32
|
+
if (source[key] === undefined) {
|
|
33
|
+
// undefined는 병합에서 제외 (사용자가 명시적으로 끄려는 경우)
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
source[key] &&
|
|
39
|
+
typeof source[key] === 'object' &&
|
|
40
|
+
!Array.isArray(source[key]) &&
|
|
41
|
+
target[key] &&
|
|
42
|
+
typeof target[key] === 'object' &&
|
|
43
|
+
!Array.isArray(target[key])
|
|
44
|
+
) {
|
|
45
|
+
// 중첩 객체는 재귀적으로 병합
|
|
46
|
+
result[key] = deepMerge(target[key], source[key] as Partial<T[Extract<keyof T, string>]>);
|
|
47
|
+
} else {
|
|
48
|
+
// 배열이나 원시값은 그대로 덮어쓰기
|
|
49
|
+
result[key] = source[key] as T[Extract<keyof T, string>];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Preset에서 Config로 변환
|
|
58
|
+
*
|
|
59
|
+
* Preset의 구조를 HuaUxConfig 형식으로 변환합니다.
|
|
60
|
+
*/
|
|
61
|
+
function presetToConfig(preset: typeof productPreset | typeof marketingPreset): Partial<HuaUxConfig> {
|
|
62
|
+
return {
|
|
63
|
+
motion: {
|
|
64
|
+
defaultPreset: preset === productPreset ? 'product' : 'marketing',
|
|
65
|
+
enableAnimations: true,
|
|
66
|
+
},
|
|
67
|
+
i18n: {
|
|
68
|
+
defaultLanguage: preset.i18n.defaultLanguage,
|
|
69
|
+
supportedLanguages: [...preset.i18n.supportedLanguages],
|
|
70
|
+
},
|
|
71
|
+
// Preset의 spacing은 나중에 컴포넌트에서 사용
|
|
72
|
+
// Config에는 직접 포함하지 않음 (컴포넌트가 PresetContext에서 가져옴)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Preset과 사용자 설정 병합
|
|
78
|
+
*
|
|
79
|
+
* 1. Preset 기본값 로드
|
|
80
|
+
* 2. Preset 설정 오버라이드 (개발자 모드인 경우)
|
|
81
|
+
* 3. 사용자 설정으로 병합 (사용자 설정 우선)
|
|
82
|
+
* 4. 최종 검증
|
|
83
|
+
*
|
|
84
|
+
* @param preset - 사용할 Preset (문자열 또는 객체)
|
|
85
|
+
* @param userConfig - 사용자 설정 (선택적)
|
|
86
|
+
* @returns 병합된 설정
|
|
87
|
+
*/
|
|
88
|
+
export function mergePresetWithConfig(
|
|
89
|
+
preset: Preset,
|
|
90
|
+
userConfig?: Partial<HuaUxConfig>
|
|
91
|
+
): HuaUxConfig {
|
|
92
|
+
// 1. Preset 타입 추출
|
|
93
|
+
let presetName: PresetName;
|
|
94
|
+
let presetOverrides: Partial<HuaUxConfig> = {};
|
|
95
|
+
|
|
96
|
+
if (typeof preset === 'string') {
|
|
97
|
+
// 바이브 모드: 문자열 Preset
|
|
98
|
+
presetName = preset;
|
|
99
|
+
} else {
|
|
100
|
+
// 개발자 모드: 객체 Preset
|
|
101
|
+
presetName = preset.type;
|
|
102
|
+
|
|
103
|
+
// Preset 설정 오버라이드
|
|
104
|
+
if (preset.motion) {
|
|
105
|
+
presetOverrides.motion = {
|
|
106
|
+
...presetOverrides.motion,
|
|
107
|
+
duration: preset.motion.duration,
|
|
108
|
+
easing: preset.motion.easing,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// spacing은 나중에 컴포넌트 레벨에서 처리
|
|
113
|
+
// (Config에는 직접 포함하지 않음)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 2. Preset 로드
|
|
117
|
+
const presetData = PRESET_MAP[presetName];
|
|
118
|
+
if (!presetData) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Unknown preset: "${presetName}". Available presets: ${Object.keys(PRESET_MAP).join(', ')}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Preset을 Config 형식으로 변환
|
|
125
|
+
const presetConfig = presetToConfig(presetData);
|
|
126
|
+
|
|
127
|
+
// preset 속성 추가 (원본 preset 정보 보존)
|
|
128
|
+
presetConfig.preset = presetName;
|
|
129
|
+
|
|
130
|
+
// 4. Preset 오버라이드와 병합
|
|
131
|
+
const presetWithOverrides = deepMerge(presetConfig, presetOverrides);
|
|
132
|
+
|
|
133
|
+
// 5. 사용자 설정과 병합 (사용자 설정 우선)
|
|
134
|
+
const merged = userConfig
|
|
135
|
+
? deepMerge(presetWithOverrides, userConfig)
|
|
136
|
+
: presetWithOverrides;
|
|
137
|
+
|
|
138
|
+
// 4. 최종 Config 형식으로 변환 (필수 필드 보장)
|
|
139
|
+
return {
|
|
140
|
+
...presetConfig,
|
|
141
|
+
...merged,
|
|
142
|
+
// i18n은 항상 있어야 함 (Preset에서 제공)
|
|
143
|
+
i18n: merged.i18n || presetConfig.i18n,
|
|
144
|
+
// motion은 항상 있어야 함
|
|
145
|
+
motion: merged.motion || presetConfig.motion,
|
|
146
|
+
// state는 기본값 사용
|
|
147
|
+
state: merged.state || {
|
|
148
|
+
persist: true,
|
|
149
|
+
ssr: true,
|
|
150
|
+
},
|
|
151
|
+
// fileStructure는 기본값 사용
|
|
152
|
+
fileStructure: merged.fileStructure || {
|
|
153
|
+
enforce: false,
|
|
154
|
+
},
|
|
155
|
+
} as HuaUxConfig;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Preset 없이 사용자 설정만으로 Config 생성
|
|
160
|
+
*
|
|
161
|
+
* Preset을 사용하지 않고 모든 설정을 직접 지정하는 경우
|
|
162
|
+
*/
|
|
163
|
+
export function createConfigFromUserConfig(
|
|
164
|
+
userConfig: Partial<HuaUxConfig>
|
|
165
|
+
): HuaUxConfig {
|
|
166
|
+
// 기본값과 사용자 설정 병합
|
|
167
|
+
const defaultConfig: Partial<HuaUxConfig> = {
|
|
168
|
+
i18n: {
|
|
169
|
+
defaultLanguage: 'ko',
|
|
170
|
+
supportedLanguages: ['ko', 'en'],
|
|
171
|
+
fallbackLanguage: 'en',
|
|
172
|
+
namespaces: ['common'],
|
|
173
|
+
translationLoader: 'api',
|
|
174
|
+
translationApiPath: '/api/translations',
|
|
175
|
+
},
|
|
176
|
+
motion: {
|
|
177
|
+
defaultPreset: 'product',
|
|
178
|
+
enableAnimations: true,
|
|
179
|
+
},
|
|
180
|
+
state: {
|
|
181
|
+
persist: true,
|
|
182
|
+
ssr: true,
|
|
183
|
+
},
|
|
184
|
+
fileStructure: {
|
|
185
|
+
enforce: false,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return deepMerge(defaultConfig, userConfig) as HuaUxConfig;
|
|
190
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - Config Schema
|
|
3
|
+
*
|
|
4
|
+
* Configuration schema and validation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HuaUxConfig, Preset } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default configuration
|
|
11
|
+
*
|
|
12
|
+
* Preset을 사용하지 않을 때의 기본값입니다.
|
|
13
|
+
* Preset을 사용하면 이 값은 무시되고 Preset 값이 사용됩니다.
|
|
14
|
+
*/
|
|
15
|
+
export const defaultConfig: Required<Omit<HuaUxConfig, 'branding' | 'plugins' | 'license'>> & {
|
|
16
|
+
branding?: HuaUxConfig['branding'];
|
|
17
|
+
plugins?: HuaUxConfig['plugins'];
|
|
18
|
+
license?: HuaUxConfig['license'];
|
|
19
|
+
} = {
|
|
20
|
+
preset: 'product', // 기본 Preset
|
|
21
|
+
i18n: {
|
|
22
|
+
defaultLanguage: 'ko',
|
|
23
|
+
supportedLanguages: ['ko', 'en'],
|
|
24
|
+
fallbackLanguage: 'en',
|
|
25
|
+
namespaces: ['common'],
|
|
26
|
+
translationLoader: 'api',
|
|
27
|
+
translationApiPath: '/api/translations',
|
|
28
|
+
},
|
|
29
|
+
motion: {
|
|
30
|
+
defaultPreset: 'product',
|
|
31
|
+
enableAnimations: true,
|
|
32
|
+
},
|
|
33
|
+
state: {
|
|
34
|
+
persist: true,
|
|
35
|
+
ssr: true,
|
|
36
|
+
},
|
|
37
|
+
fileStructure: {
|
|
38
|
+
enforce: false,
|
|
39
|
+
},
|
|
40
|
+
plugins: [],
|
|
41
|
+
license: undefined,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate configuration
|
|
46
|
+
*
|
|
47
|
+
* 설정의 유효성을 검증하고 친절한 에러 메시지를 제공합니다.
|
|
48
|
+
* Validates configuration and provides friendly error messages.
|
|
49
|
+
*/
|
|
50
|
+
export function validateConfig(config: Partial<HuaUxConfig>): HuaUxConfig {
|
|
51
|
+
// Preset 검증
|
|
52
|
+
if (config.preset) {
|
|
53
|
+
if (typeof config.preset === 'string') {
|
|
54
|
+
// 바이브 모드: 문자열 Preset
|
|
55
|
+
if (!['product', 'marketing'].includes(config.preset)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`[hua-ux] ❌ 잘못된 Preset입니다: "${config.preset}"\n` +
|
|
58
|
+
`[hua-ux] ❌ Invalid preset: "${config.preset}"\n\n` +
|
|
59
|
+
`사용 가능한 Preset: 'product', 'marketing'\n` +
|
|
60
|
+
`Available presets: 'product', 'marketing'\n\n` +
|
|
61
|
+
`💡 해결 방법 / Solution:\n` +
|
|
62
|
+
` - 'product' 또는 'marketing' 중 하나를 선택하세요.\n` +
|
|
63
|
+
` - Select either 'product' or 'marketing'.\n\n` +
|
|
64
|
+
`📖 가이드 / Guide: https://github.com/HUA-Labs/hua-platform/tree/main/packages/hua-ux/docs`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
// 개발자 모드: 객체 Preset
|
|
69
|
+
if (!['product', 'marketing'].includes(config.preset.type)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`[hua-ux] ❌ 잘못된 Preset 타입입니다: "${config.preset.type}"\n` +
|
|
72
|
+
`[hua-ux] ❌ Invalid preset type: "${config.preset.type}"\n\n` +
|
|
73
|
+
`사용 가능한 Preset 타입: 'product', 'marketing'\n` +
|
|
74
|
+
`Available preset types: 'product', 'marketing'\n\n` +
|
|
75
|
+
`💡 해결 방법 / Solution:\n` +
|
|
76
|
+
` - preset.type을 'product' 또는 'marketing'으로 설정하세요.\n` +
|
|
77
|
+
` - Set preset.type to either 'product' or 'marketing'.\n\n` +
|
|
78
|
+
`📖 가이드 / Guide: https://github.com/HUA-Labs/hua-platform/tree/main/packages/hua-ux/docs`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// motion.style을 defaultPreset으로 매핑 (바이브 코더용)
|
|
85
|
+
if (config.motion?.style && !config.motion.defaultPreset) {
|
|
86
|
+
const styleToPreset: Record<string, 'product' | 'marketing'> = {
|
|
87
|
+
smooth: 'product',
|
|
88
|
+
minimal: 'product',
|
|
89
|
+
dramatic: 'marketing',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
config.motion.defaultPreset = styleToPreset[config.motion.style];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 기본값과 병합
|
|
96
|
+
const validated: HuaUxConfig = {
|
|
97
|
+
preset: config.preset || defaultConfig.preset,
|
|
98
|
+
i18n: {
|
|
99
|
+
...defaultConfig.i18n,
|
|
100
|
+
...config.i18n,
|
|
101
|
+
},
|
|
102
|
+
motion: {
|
|
103
|
+
...defaultConfig.motion,
|
|
104
|
+
...config.motion,
|
|
105
|
+
},
|
|
106
|
+
state: {
|
|
107
|
+
...defaultConfig.state,
|
|
108
|
+
...config.state,
|
|
109
|
+
},
|
|
110
|
+
fileStructure: {
|
|
111
|
+
...defaultConfig.fileStructure,
|
|
112
|
+
...config.fileStructure,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Validate i18n
|
|
117
|
+
if (validated.i18n) {
|
|
118
|
+
if (!validated.i18n.supportedLanguages.includes(validated.i18n.defaultLanguage)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`[hua-ux] ❌ i18n 설정 오류 / i18n configuration error\n\n` +
|
|
121
|
+
`기본 언어 "${validated.i18n.defaultLanguage}"가 지원 언어 목록에 없습니다.\n` +
|
|
122
|
+
`Default language "${validated.i18n.defaultLanguage}" is not in supportedLanguages.\n\n` +
|
|
123
|
+
`현재 지원 언어 / Current supported languages: ${validated.i18n.supportedLanguages.join(', ')}\n\n` +
|
|
124
|
+
`💡 해결 방법 / Solution:\n` +
|
|
125
|
+
` 1. supportedLanguages에 "${validated.i18n.defaultLanguage}"를 추가하세요.\n` +
|
|
126
|
+
` Add "${validated.i18n.defaultLanguage}" to supportedLanguages.\n` +
|
|
127
|
+
` 2. 또는 defaultLanguage를 지원 언어 중 하나로 변경하세요.\n` +
|
|
128
|
+
` Or change defaultLanguage to one of the supported languages.\n\n` +
|
|
129
|
+
`📝 예시 / Example:\n` +
|
|
130
|
+
` i18n: {\n` +
|
|
131
|
+
` defaultLanguage: 'ko',\n` +
|
|
132
|
+
` supportedLanguages: ['ko', 'en', 'ja'], // 'ko' 포함 필수\n` +
|
|
133
|
+
` }\n\n` +
|
|
134
|
+
`📖 가이드 / Guide: https://github.com/HUA-Labs/hua-platform/tree/main/packages/hua-ux/docs`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return validated;
|
|
140
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - useMotion
|
|
3
|
+
*
|
|
4
|
+
* 통합 Motion Hook - motion-core의 useUnifiedMotion을 래핑
|
|
5
|
+
* Unified Motion Hook - Wraps motion-core's useUnifiedMotion
|
|
6
|
+
*
|
|
7
|
+
* 이 hook은 motion-core의 useUnifiedMotion을 재export하여
|
|
8
|
+
* hua-ux 프레임워크에서 일관된 API를 제공합니다.
|
|
9
|
+
*
|
|
10
|
+
* This hook re-exports motion-core's useUnifiedMotion to provide
|
|
11
|
+
* a consistent API within the hua-ux framework.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use client';
|
|
15
|
+
|
|
16
|
+
import type { BaseMotionReturn, MotionElement, EntranceType } from '@hua-labs/motion-core';
|
|
17
|
+
import { useUnifiedMotion } from '@hua-labs/motion-core';
|
|
18
|
+
import type { UseUnifiedMotionOptions } from '@hua-labs/motion-core';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Motion type (motion-core의 EntranceType과 동일)
|
|
22
|
+
*/
|
|
23
|
+
export type MotionType = EntranceType;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* useMotion options (motion-core의 UseUnifiedMotionOptions와 동일)
|
|
27
|
+
*/
|
|
28
|
+
export type UseMotionOptions = UseUnifiedMotionOptions;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 통합 Motion Hook
|
|
32
|
+
*
|
|
33
|
+
* motion-core의 useUnifiedMotion을 래핑하여 hua-ux 프레임워크에서 사용합니다.
|
|
34
|
+
*
|
|
35
|
+
* Wraps motion-core's useUnifiedMotion for use in the hua-ux framework.
|
|
36
|
+
*
|
|
37
|
+
* @param options - Motion options
|
|
38
|
+
* @returns Motion result with ref and control functions
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const motion = useMotion({
|
|
43
|
+
* type: 'fadeIn',
|
|
44
|
+
* duration: 600,
|
|
45
|
+
* autoStart: false,
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* return <div ref={motion.ref} style={motion.style}>Content</div>;
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function useMotion<T extends MotionElement = HTMLDivElement>(
|
|
52
|
+
options: UseMotionOptions
|
|
53
|
+
): BaseMotionReturn<T> {
|
|
54
|
+
// motion-core의 useUnifiedMotion을 직접 사용
|
|
55
|
+
// Directly use motion-core's useUnifiedMotion
|
|
56
|
+
return useUnifiedMotion<T>(options);
|
|
57
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework
|
|
3
|
+
*
|
|
4
|
+
* Framework layer for hua-ux
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Components
|
|
8
|
+
export { HuaUxLayout } from './components/HuaUxLayout';
|
|
9
|
+
export { HuaUxPage } from './components/HuaUxPage';
|
|
10
|
+
export { UnifiedProviders } from './components/Providers';
|
|
11
|
+
export { BrandedButton } from './components/BrandedButton';
|
|
12
|
+
export { BrandedCard } from './components/BrandedCard';
|
|
13
|
+
export { ErrorBoundary } from './components/ErrorBoundary';
|
|
14
|
+
export type { ErrorBoundaryProps } from './components/ErrorBoundary';
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
export { defineConfig, loadConfig, getConfig, setConfig, resetConfig } from './config';
|
|
18
|
+
export type { HuaUxConfig, PresetName } from './types';
|
|
19
|
+
|
|
20
|
+
// Data Fetching
|
|
21
|
+
export { useData, fetchData } from './utils/data-fetching';
|
|
22
|
+
export type { DataFetchResult } from './utils/data-fetching';
|
|
23
|
+
|
|
24
|
+
// Middleware
|
|
25
|
+
export { createI18nMiddleware } from './middleware/i18n';
|
|
26
|
+
export type { I18nMiddlewareConfig } from './middleware/i18n';
|
|
27
|
+
|
|
28
|
+
// File Structure
|
|
29
|
+
// 서버 전용 유틸리티 (클라이언트 번들에서 제외)
|
|
30
|
+
// Server-only utilities (excluded from client bundle)
|
|
31
|
+
// export { validateFileStructure } from './utils/file-structure';
|
|
32
|
+
// export type { FileStructureResult } from './utils/file-structure';
|
|
33
|
+
// Note: validateFileStructure는 서버 전용이므로 필요시 직접 import
|
|
34
|
+
|
|
35
|
+
// Metadata Utilities
|
|
36
|
+
export { generatePageMetadata } from './utils/metadata';
|
|
37
|
+
export type { SEOConfig } from './utils/metadata';
|
|
38
|
+
|
|
39
|
+
// GEO (Generative Engine Optimization)
|
|
40
|
+
export {
|
|
41
|
+
generateGEOMetadata,
|
|
42
|
+
renderJSONLD,
|
|
43
|
+
createAIContext,
|
|
44
|
+
generateSoftwareApplicationLD,
|
|
45
|
+
generateFAQPageLD,
|
|
46
|
+
generateTechArticleLD,
|
|
47
|
+
generateHowToLD,
|
|
48
|
+
} from './seo/geo';
|
|
49
|
+
export type {
|
|
50
|
+
GEOConfig,
|
|
51
|
+
GEOMetadata,
|
|
52
|
+
StructuredData,
|
|
53
|
+
SoftwareApplicationType,
|
|
54
|
+
SoftwareCategory,
|
|
55
|
+
ProgrammingLanguage,
|
|
56
|
+
} from './seo/geo';
|
|
57
|
+
|
|
58
|
+
// License System
|
|
59
|
+
export {
|
|
60
|
+
initLicense,
|
|
61
|
+
getLicense,
|
|
62
|
+
checkLicense,
|
|
63
|
+
hasLicense,
|
|
64
|
+
requireLicense
|
|
65
|
+
} from './license';
|
|
66
|
+
export type {
|
|
67
|
+
LicenseInfo,
|
|
68
|
+
LicenseType,
|
|
69
|
+
LicenseFeature,
|
|
70
|
+
LicenseCheckResult
|
|
71
|
+
} from './license/types';
|
|
72
|
+
|
|
73
|
+
// Plugin System
|
|
74
|
+
export {
|
|
75
|
+
pluginRegistry,
|
|
76
|
+
registerPlugin,
|
|
77
|
+
getPlugin,
|
|
78
|
+
getAllPlugins
|
|
79
|
+
} from './plugins';
|
|
80
|
+
export type { HuaUxPlugin } from './plugins/types';
|
|
81
|
+
|
|
82
|
+
// Branding
|
|
83
|
+
export { BrandingProvider, useBranding, useBrandingColor } from './branding/context';
|
|
84
|
+
export { generateCSSVariables, generateCSSVariablesObject } from './branding/css-vars';
|
|
85
|
+
export { generateTailwindConfig } from './branding/tailwind-config';
|
|
86
|
+
|
|
87
|
+
// Accessibility (a11y)
|
|
88
|
+
export {
|
|
89
|
+
useFocusManagement,
|
|
90
|
+
useFocusTrap,
|
|
91
|
+
SkipToContent,
|
|
92
|
+
LiveRegion,
|
|
93
|
+
useLiveRegion,
|
|
94
|
+
} from './a11y';
|
|
95
|
+
export type {
|
|
96
|
+
FocusManagementOptions,
|
|
97
|
+
FocusTrapOptions,
|
|
98
|
+
SkipToContentProps,
|
|
99
|
+
LiveRegionProps,
|
|
100
|
+
} from './a11y';
|
|
101
|
+
|
|
102
|
+
// Loading
|
|
103
|
+
export {
|
|
104
|
+
useDelayedLoading,
|
|
105
|
+
useLoadingState,
|
|
106
|
+
Skeleton,
|
|
107
|
+
SkeletonGroup,
|
|
108
|
+
SuspenseWrapper,
|
|
109
|
+
withSuspense,
|
|
110
|
+
} from './loading';
|
|
111
|
+
export type {
|
|
112
|
+
DelayedLoadingOptions,
|
|
113
|
+
SkeletonGroupProps,
|
|
114
|
+
SuspenseWrapperProps,
|
|
115
|
+
} from './loading';
|
|
116
|
+
|
|
117
|
+
// Motion Hooks
|
|
118
|
+
export { useMotion } from './hooks/useMotion';
|
|
119
|
+
export type { MotionType, UseMotionOptions } from './hooks/useMotion';
|
|
120
|
+
|
|
121
|
+
// Types
|
|
122
|
+
export type { HuaUxLayoutProps, HuaUxPageProps } from './types';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - License Errors
|
|
3
|
+
*
|
|
4
|
+
* 라이선스 관련 에러 메시지
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LicenseFeature } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 구매 링크 (향후 실제 링크로 변경)
|
|
11
|
+
*/
|
|
12
|
+
const PURCHASE_URL = 'https://hua-labs.com/pricing';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 라이선스 에러 메시지 생성
|
|
16
|
+
*
|
|
17
|
+
* @param feature - 필요한 기능
|
|
18
|
+
* @param currentLicense - 현재 라이선스 타입
|
|
19
|
+
* @returns 에러 메시지
|
|
20
|
+
*/
|
|
21
|
+
export function createLicenseError(
|
|
22
|
+
feature: LicenseFeature,
|
|
23
|
+
currentLicense: 'free' | 'pro' | 'enterprise' = 'free'
|
|
24
|
+
): string {
|
|
25
|
+
const featureNames: Record<LicenseFeature, string> = {
|
|
26
|
+
'core': 'Core features',
|
|
27
|
+
'motion-basic': 'Basic motion',
|
|
28
|
+
'motion-pro': 'Motion Pro',
|
|
29
|
+
'i18n-basic': 'Basic i18n',
|
|
30
|
+
'i18n-pro': 'i18n Pro',
|
|
31
|
+
'preset-basic': 'Basic presets',
|
|
32
|
+
'preset-pro': 'Preset Pro',
|
|
33
|
+
'white-labeling': 'White Labeling',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const featureName = featureNames[feature] || feature;
|
|
37
|
+
|
|
38
|
+
// 필요한 라이선스 타입 결정
|
|
39
|
+
let requiredLicense: 'pro' | 'enterprise' = 'pro';
|
|
40
|
+
if (feature === 'white-labeling') {
|
|
41
|
+
requiredLicense = 'enterprise';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const messages = {
|
|
45
|
+
ko: [
|
|
46
|
+
`[hua-ux] ❌ "${featureName}" 기능을 사용하려면 ${requiredLicense === 'enterprise' ? 'Enterprise' : 'Pro'} 라이선스가 필요합니다.`,
|
|
47
|
+
`[hua-ux] ❌ Feature "${featureName}" requires a ${requiredLicense === 'enterprise' ? 'Enterprise' : 'Pro'} license.`,
|
|
48
|
+
'',
|
|
49
|
+
`현재 라이선스: ${currentLicense === 'free' ? 'Free' : currentLicense === 'pro' ? 'Pro' : 'Enterprise'}`,
|
|
50
|
+
`Current license: ${currentLicense === 'free' ? 'Free' : currentLicense === 'pro' ? 'Pro' : 'Enterprise'}`,
|
|
51
|
+
'',
|
|
52
|
+
`💡 해결 방법 / Solution:`,
|
|
53
|
+
` - ${requiredLicense === 'enterprise' ? 'Enterprise' : 'Pro'} 라이선스를 구매하세요.`,
|
|
54
|
+
` - Purchase a ${requiredLicense === 'enterprise' ? 'Enterprise' : 'Pro'} license.`,
|
|
55
|
+
` - 구매 링크: ${PURCHASE_URL}`,
|
|
56
|
+
` - Purchase link: ${PURCHASE_URL}`,
|
|
57
|
+
'',
|
|
58
|
+
`📖 가이드 / Guide: https://github.com/HUA-Labs/hua-platform/tree/main/packages/hua-ux/docs`,
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return messages.ko.join('\n');
|
|
63
|
+
}
|