@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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - License System
|
|
3
|
+
*
|
|
4
|
+
* 라이선스 검증 및 관리 시스템
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LicenseInfo, LicenseFeature, LicenseCheckResult } from './types';
|
|
8
|
+
import { loadLicense } from './loader';
|
|
9
|
+
import { FEATURE_LICENSE_MAP } from './types';
|
|
10
|
+
import { createLicenseError } from './errors';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 전역 라이선스 인스턴스
|
|
14
|
+
*/
|
|
15
|
+
let globalLicense: LicenseInfo | null = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 라이선스 초기화
|
|
19
|
+
*
|
|
20
|
+
* 설정 파일의 라이선스 정보를 사용하여 라이선스를 로드합니다.
|
|
21
|
+
*
|
|
22
|
+
* @param configLicense - 설정 파일의 라이선스 정보 (선택적)
|
|
23
|
+
*/
|
|
24
|
+
export function initLicense(configLicense?: { apiKey?: string; type?: 'free' | 'pro' | 'enterprise' }): void {
|
|
25
|
+
globalLicense = loadLicense(configLicense);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 현재 라이선스 가져오기
|
|
30
|
+
*
|
|
31
|
+
* @returns 라이선스 정보
|
|
32
|
+
*/
|
|
33
|
+
export function getLicense(): LicenseInfo {
|
|
34
|
+
if (!globalLicense) {
|
|
35
|
+
// 초기화되지 않은 경우 기본값 (Free) 로드
|
|
36
|
+
globalLicense = loadLicense();
|
|
37
|
+
}
|
|
38
|
+
return globalLicense;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 라이선스 검증
|
|
43
|
+
*
|
|
44
|
+
* 특정 기능이 현재 라이선스에서 사용 가능한지 확인합니다.
|
|
45
|
+
*
|
|
46
|
+
* @param feature - 확인할 기능
|
|
47
|
+
* @returns 검증 결과
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const result = checkLicense('motion-pro');
|
|
52
|
+
* if (result.valid) {
|
|
53
|
+
* // Pro 기능 사용
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function checkLicense(feature: LicenseFeature): LicenseCheckResult {
|
|
58
|
+
const license = getLicense();
|
|
59
|
+
|
|
60
|
+
// 라이선스가 유효하지 않은 경우
|
|
61
|
+
if (!license.valid) {
|
|
62
|
+
return {
|
|
63
|
+
valid: false,
|
|
64
|
+
reason: 'License is not valid',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 만료 확인
|
|
69
|
+
if (license.expiresAt && license.expiresAt < new Date()) {
|
|
70
|
+
return {
|
|
71
|
+
valid: false,
|
|
72
|
+
reason: 'License has expired',
|
|
73
|
+
expiresAt: license.expiresAt,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 기능 확인
|
|
78
|
+
const requiredLicenses = FEATURE_LICENSE_MAP[feature];
|
|
79
|
+
if (!requiredLicenses.includes(license.type)) {
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
reason: `Feature "${feature}" requires one of: ${requiredLicenses.join(', ')}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 기능이 features 목록에 있는지 확인 (Enterprise는 모든 기능 사용 가능)
|
|
87
|
+
if (license.type !== 'enterprise' && !license.features.includes(feature)) {
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
reason: `Feature "${feature}" is not included in the license`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
valid: true,
|
|
96
|
+
expiresAt: license.expiresAt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 라이선스 확인 (간단한 boolean 반환)
|
|
102
|
+
*
|
|
103
|
+
* @param feature - 확인할 기능
|
|
104
|
+
* @returns 사용 가능 여부
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* if (hasLicense('motion-pro')) {
|
|
109
|
+
* // Pro 기능 사용
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function hasLicense(feature: LicenseFeature): boolean {
|
|
114
|
+
return checkLicense(feature).valid;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 라이선스 필수 확인
|
|
119
|
+
*
|
|
120
|
+
* 라이선스가 없으면 에러를 throw합니다.
|
|
121
|
+
*
|
|
122
|
+
* @param feature - 확인할 기능
|
|
123
|
+
* @throws 라이선스가 없을 경우 에러
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* requireLicense('motion-pro');
|
|
128
|
+
* // Pro 기능 사용 (에러 없음)
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function requireLicense(feature: LicenseFeature): void {
|
|
132
|
+
const result = checkLicense(feature);
|
|
133
|
+
if (!result.valid) {
|
|
134
|
+
const license = getLicense();
|
|
135
|
+
throw new Error(createLicenseError(feature, license.type));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - License Loader
|
|
3
|
+
*
|
|
4
|
+
* 라이선스 로드 및 검증
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LicenseInfo, LicenseType } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 라이선스 캐시
|
|
11
|
+
*/
|
|
12
|
+
let cachedLicense: LicenseInfo | null = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Free 라이선스 기본값 / Free license default
|
|
16
|
+
*/
|
|
17
|
+
const FREE_LICENSE: LicenseInfo = {
|
|
18
|
+
type: 'free',
|
|
19
|
+
valid: true,
|
|
20
|
+
features: ['core', 'motion-basic', 'i18n-basic', 'preset-basic'],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* API 키 검증 (미구현 - 향후 구현)
|
|
25
|
+
*
|
|
26
|
+
* 실제 구현 시에는 서버 API를 호출하여 라이선스를 검증합니다.
|
|
27
|
+
* In actual implementation, this would call a server API to validate the license.
|
|
28
|
+
*
|
|
29
|
+
* @param apiKey - API 키
|
|
30
|
+
* @returns 라이선스 정보 또는 null
|
|
31
|
+
*/
|
|
32
|
+
function validateLicenseKey(apiKey: string): LicenseInfo | null {
|
|
33
|
+
// TODO: 실제 API 호출 구현
|
|
34
|
+
// TODO: Implement actual API call
|
|
35
|
+
|
|
36
|
+
// 현재는 기본값 반환 (개발 단계)
|
|
37
|
+
// Currently returns default (development phase)
|
|
38
|
+
if (apiKey.startsWith('pro_')) {
|
|
39
|
+
return {
|
|
40
|
+
type: 'pro',
|
|
41
|
+
valid: true,
|
|
42
|
+
features: ['core', 'motion-basic', 'motion-pro', 'i18n-basic', 'i18n-pro', 'preset-basic', 'preset-pro'],
|
|
43
|
+
apiKey,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (apiKey.startsWith('enterprise_')) {
|
|
48
|
+
return {
|
|
49
|
+
type: 'enterprise',
|
|
50
|
+
valid: true,
|
|
51
|
+
features: ['core', 'motion-basic', 'motion-pro', 'i18n-basic', 'i18n-pro', 'preset-basic', 'preset-pro', 'white-labeling'],
|
|
52
|
+
apiKey,
|
|
53
|
+
companyName: 'Enterprise Customer',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 환경 변수에서 라이선스 로드
|
|
62
|
+
*
|
|
63
|
+
* @returns 라이선스 정보 또는 null
|
|
64
|
+
*/
|
|
65
|
+
function loadLicenseFromEnv(): LicenseInfo | null {
|
|
66
|
+
// Node.js 환경에서만 동작
|
|
67
|
+
if (typeof process === 'undefined' || !process.env) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const apiKey = process.env.HUA_UX_LICENSE_KEY;
|
|
72
|
+
if (!apiKey) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return validateLicenseKey(apiKey);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 설정 파일에서 라이선스 로드
|
|
81
|
+
*
|
|
82
|
+
* @param configLicense - 설정 파일의 라이선스 정보
|
|
83
|
+
* @returns 라이선스 정보 또는 null
|
|
84
|
+
*/
|
|
85
|
+
function loadLicenseFromConfig(configLicense?: { apiKey?: string; type?: LicenseType }): LicenseInfo | null {
|
|
86
|
+
if (!configLicense) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// API 키가 있으면 검증
|
|
91
|
+
if (configLicense.apiKey) {
|
|
92
|
+
return validateLicenseKey(configLicense.apiKey);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 타입만 지정된 경우 (개발용)
|
|
96
|
+
if (configLicense.type) {
|
|
97
|
+
if (configLicense.type === 'pro') {
|
|
98
|
+
return {
|
|
99
|
+
type: 'pro',
|
|
100
|
+
valid: true,
|
|
101
|
+
features: ['core', 'motion-basic', 'motion-pro', 'i18n-basic', 'i18n-pro', 'preset-basic', 'preset-pro'],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (configLicense.type === 'enterprise') {
|
|
106
|
+
return {
|
|
107
|
+
type: 'enterprise',
|
|
108
|
+
valid: true,
|
|
109
|
+
features: ['core', 'motion-basic', 'motion-pro', 'i18n-basic', 'i18n-pro', 'preset-basic', 'preset-pro', 'white-labeling'],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 라이선스 로드
|
|
119
|
+
*
|
|
120
|
+
* 로드 순서:
|
|
121
|
+
* 1. 환경 변수 (HUA_UX_LICENSE_KEY)
|
|
122
|
+
* 2. 설정 파일 (hua-ux.config.ts)
|
|
123
|
+
* 3. 기본값 (Free)
|
|
124
|
+
*
|
|
125
|
+
* @param configLicense - 설정 파일의 라이선스 정보 (선택적)
|
|
126
|
+
* @returns 라이선스 정보
|
|
127
|
+
*/
|
|
128
|
+
export function loadLicense(configLicense?: { apiKey?: string; type?: LicenseType }): LicenseInfo {
|
|
129
|
+
// 캐시 확인
|
|
130
|
+
if (cachedLicense) {
|
|
131
|
+
return cachedLicense;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 1. 환경 변수에서 로드
|
|
135
|
+
const envLicense = loadLicenseFromEnv();
|
|
136
|
+
if (envLicense) {
|
|
137
|
+
cachedLicense = envLicense;
|
|
138
|
+
return cachedLicense;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. 설정 파일에서 로드
|
|
142
|
+
const configLicenseInfo = loadLicenseFromConfig(configLicense);
|
|
143
|
+
if (configLicenseInfo) {
|
|
144
|
+
cachedLicense = configLicenseInfo;
|
|
145
|
+
return cachedLicense;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 3. 기본값 (Free)
|
|
149
|
+
cachedLicense = FREE_LICENSE;
|
|
150
|
+
return cachedLicense;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 라이선스 캐시 초기화 (테스트용)
|
|
155
|
+
*/
|
|
156
|
+
export function resetLicenseCache(): void {
|
|
157
|
+
cachedLicense = null;
|
|
158
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - License Types
|
|
3
|
+
*
|
|
4
|
+
* 라이선스 타입 정의
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 라이선스 타입 / License type
|
|
9
|
+
*/
|
|
10
|
+
export type LicenseType = 'free' | 'pro' | 'enterprise';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 라이선스 기능 / License feature
|
|
14
|
+
*
|
|
15
|
+
* 각 기능은 특정 라이선스 타입에서만 사용 가능합니다.
|
|
16
|
+
* Each feature is available only with specific license types.
|
|
17
|
+
*/
|
|
18
|
+
export type LicenseFeature =
|
|
19
|
+
| 'core' // 기본 기능 (모든 라이선스)
|
|
20
|
+
| 'motion-basic' // 기본 모션 (Free)
|
|
21
|
+
| 'motion-pro' // 고급 모션 (Pro+)
|
|
22
|
+
| 'i18n-basic' // 기본 i18n (Free)
|
|
23
|
+
| 'i18n-pro' // 고급 i18n (Pro+)
|
|
24
|
+
| 'preset-basic' // 기본 프리셋 (Free)
|
|
25
|
+
| 'preset-pro' // 고급 프리셋 (Pro+)
|
|
26
|
+
| 'white-labeling'; // 화이트 라벨링 (Enterprise)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 라이선스 정보 / License information
|
|
30
|
+
*/
|
|
31
|
+
export interface LicenseInfo {
|
|
32
|
+
/**
|
|
33
|
+
* 라이선스 타입 / License type
|
|
34
|
+
*/
|
|
35
|
+
type: LicenseType;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 라이선스 유효성 / License validity
|
|
39
|
+
*/
|
|
40
|
+
valid: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 만료일 (선택적) / Expiration date (optional)
|
|
44
|
+
*/
|
|
45
|
+
expiresAt?: Date;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 사용 가능한 기능 목록 / List of available features
|
|
49
|
+
*/
|
|
50
|
+
features: LicenseFeature[];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 회사/서비스 이름 (Enterprise) / Company/service name (Enterprise)
|
|
54
|
+
*/
|
|
55
|
+
companyName?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* API 키 (Pro/Enterprise) / API key (Pro/Enterprise)
|
|
59
|
+
*/
|
|
60
|
+
apiKey?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 라이선스 검증 결과 / License check result
|
|
65
|
+
*/
|
|
66
|
+
export interface LicenseCheckResult {
|
|
67
|
+
/**
|
|
68
|
+
* 검증 성공 여부 / Validation success
|
|
69
|
+
*/
|
|
70
|
+
valid: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 실패 이유 (검증 실패 시) / Failure reason (if validation failed)
|
|
74
|
+
*/
|
|
75
|
+
reason?: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 만료일 (선택적) / Expiration date (optional)
|
|
79
|
+
*/
|
|
80
|
+
expiresAt?: Date;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 기능별 필요한 라이선스 타입 매핑 / Feature to license type mapping
|
|
85
|
+
*/
|
|
86
|
+
export const FEATURE_LICENSE_MAP: Record<LicenseFeature, LicenseType[]> = {
|
|
87
|
+
'core': ['free', 'pro', 'enterprise'],
|
|
88
|
+
'motion-basic': ['free', 'pro', 'enterprise'],
|
|
89
|
+
'motion-pro': ['pro', 'enterprise'],
|
|
90
|
+
'i18n-basic': ['free', 'pro', 'enterprise'],
|
|
91
|
+
'i18n-pro': ['pro', 'enterprise'],
|
|
92
|
+
'preset-basic': ['free', 'pro', 'enterprise'],
|
|
93
|
+
'preset-pro': ['pro', 'enterprise'],
|
|
94
|
+
'white-labeling': ['enterprise'],
|
|
95
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - SkeletonGroup
|
|
3
|
+
*
|
|
4
|
+
* 여러 Skeleton을 그룹으로 묶는 컴포넌트
|
|
5
|
+
* Component for grouping multiple Skeletons
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { Skeleton } from '@hua-labs/ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* SkeletonGroup 컴포넌트 props
|
|
15
|
+
*/
|
|
16
|
+
export interface SkeletonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
/**
|
|
18
|
+
* 자식 요소들
|
|
19
|
+
* Children elements
|
|
20
|
+
*/
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 간격 크기
|
|
25
|
+
* Spacing size
|
|
26
|
+
*
|
|
27
|
+
* @default "md"
|
|
28
|
+
*/
|
|
29
|
+
spacing?: 'sm' | 'md' | 'lg';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* SkeletonGroup 컴포넌트
|
|
34
|
+
*
|
|
35
|
+
* 여러 Skeleton을 일관된 간격으로 그룹화합니다.
|
|
36
|
+
* Groups multiple Skeletons with consistent spacing.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* <SkeletonGroup>
|
|
41
|
+
* <Skeleton width="60%" height={32} />
|
|
42
|
+
* <Skeleton width="80%" />
|
|
43
|
+
* <Skeleton width="70%" />
|
|
44
|
+
* </SkeletonGroup>
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param props - SkeletonGroup props
|
|
48
|
+
* @returns SkeletonGroup 컴포넌트
|
|
49
|
+
*/
|
|
50
|
+
export function SkeletonGroup({
|
|
51
|
+
children,
|
|
52
|
+
spacing = 'md',
|
|
53
|
+
className,
|
|
54
|
+
...props
|
|
55
|
+
}: SkeletonGroupProps): React.JSX.Element {
|
|
56
|
+
const spacingClasses = {
|
|
57
|
+
sm: 'space-y-2', // 8px
|
|
58
|
+
md: 'space-y-4', // 16px
|
|
59
|
+
lg: 'space-y-6', // 24px
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={`${spacingClasses[spacing]} ${className || ''}`}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - SuspenseWrapper
|
|
3
|
+
*
|
|
4
|
+
* React Suspense를 더 쉽게 사용할 수 있게 해주는 컴포넌트
|
|
5
|
+
* Component that makes React Suspense easier to use
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React, { Suspense, useMemo, type ReactNode } from 'react';
|
|
11
|
+
import { Skeleton } from '@hua-labs/ui';
|
|
12
|
+
import { SkeletonGroup } from './SkeletonGroup';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* SuspenseWrapper 컴포넌트 props
|
|
16
|
+
*/
|
|
17
|
+
export interface SuspenseWrapperProps {
|
|
18
|
+
/**
|
|
19
|
+
* Suspense로 감쌀 자식 요소
|
|
20
|
+
* Children to wrap with Suspense
|
|
21
|
+
*/
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 로딩 중 표시할 fallback
|
|
26
|
+
* Fallback to show while loading
|
|
27
|
+
*
|
|
28
|
+
* @default <SkeletonGroup>...</SkeletonGroup>
|
|
29
|
+
*/
|
|
30
|
+
fallback?: ReactNode;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 기본 Skeleton fallback 사용 여부
|
|
34
|
+
* Whether to use default Skeleton fallback
|
|
35
|
+
*
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
useDefaultFallback?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* SuspenseWrapper 컴포넌트
|
|
43
|
+
*
|
|
44
|
+
* React Suspense를 더 쉽게 사용할 수 있게 해줍니다.
|
|
45
|
+
* 기본적으로 Skeleton fallback을 제공합니다.
|
|
46
|
+
*
|
|
47
|
+
* Makes React Suspense easier to use.
|
|
48
|
+
* Provides Skeleton fallback by default.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* // 기본 사용 (자동 Skeleton fallback)
|
|
53
|
+
* <SuspenseWrapper>
|
|
54
|
+
* <AsyncComponent />
|
|
55
|
+
* </SuspenseWrapper>
|
|
56
|
+
*
|
|
57
|
+
* // 커스텀 fallback
|
|
58
|
+
* <SuspenseWrapper fallback={<Spinner />}>
|
|
59
|
+
* <AsyncComponent />
|
|
60
|
+
* </SuspenseWrapper>
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @param props - SuspenseWrapper props
|
|
64
|
+
* @returns SuspenseWrapper 컴포넌트
|
|
65
|
+
*/
|
|
66
|
+
export function SuspenseWrapper({
|
|
67
|
+
children,
|
|
68
|
+
fallback,
|
|
69
|
+
useDefaultFallback = true,
|
|
70
|
+
}: SuspenseWrapperProps): React.JSX.Element {
|
|
71
|
+
const defaultFallback = useMemo(
|
|
72
|
+
() =>
|
|
73
|
+
useDefaultFallback ? (
|
|
74
|
+
<SkeletonGroup>
|
|
75
|
+
<Skeleton width="60%" height={32} />
|
|
76
|
+
<Skeleton width="80%" />
|
|
77
|
+
<Skeleton width="70%" />
|
|
78
|
+
</SkeletonGroup>
|
|
79
|
+
) : null,
|
|
80
|
+
[useDefaultFallback]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Suspense fallback={fallback ?? defaultFallback}>
|
|
85
|
+
{children}
|
|
86
|
+
</Suspense>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hua-labs/hua-ux/framework - withSuspense
|
|
3
|
+
*
|
|
4
|
+
* 컴포넌트를 Suspense로 감싸는 HOC
|
|
5
|
+
* HOC that wraps a component with Suspense
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React, { Suspense, useMemo, type ComponentType, type ReactNode } from 'react';
|
|
11
|
+
import { Skeleton } from '@hua-labs/ui';
|
|
12
|
+
import { SkeletonGroup } from '../components/SkeletonGroup';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* withSuspense 옵션
|
|
16
|
+
*/
|
|
17
|
+
export interface WithSuspenseOptions {
|
|
18
|
+
/**
|
|
19
|
+
* 로딩 중 표시할 fallback
|
|
20
|
+
* Fallback to show while loading
|
|
21
|
+
*/
|
|
22
|
+
fallback?: ReactNode;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 기본 Skeleton fallback 사용 여부
|
|
26
|
+
* Whether to use default Skeleton fallback
|
|
27
|
+
*
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
useDefaultFallback?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* withSuspense HOC
|
|
35
|
+
*
|
|
36
|
+
* 컴포넌트를 Suspense로 감싸는 Higher-Order Component입니다.
|
|
37
|
+
*
|
|
38
|
+
* Higher-Order Component that wraps a component with Suspense.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const AsyncPosts = withSuspense(Posts, <Skeleton height={200} />);
|
|
43
|
+
*
|
|
44
|
+
* function MyPage() {
|
|
45
|
+
* return <AsyncPosts />;
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param Component - 감쌀 컴포넌트
|
|
50
|
+
* @param options - 옵션 또는 fallback
|
|
51
|
+
* @returns Suspense로 감싼 컴포넌트
|
|
52
|
+
*/
|
|
53
|
+
export function withSuspense<P extends object>(
|
|
54
|
+
Component: ComponentType<P>,
|
|
55
|
+
optionsOrFallback?: WithSuspenseOptions | ReactNode
|
|
56
|
+
): ComponentType<P> {
|
|
57
|
+
let fallback: ReactNode | undefined;
|
|
58
|
+
let useDefaultFallback = true;
|
|
59
|
+
|
|
60
|
+
if (optionsOrFallback !== undefined) {
|
|
61
|
+
if (React.isValidElement(optionsOrFallback) || typeof optionsOrFallback === 'string') {
|
|
62
|
+
// fallback이 직접 전달된 경우
|
|
63
|
+
fallback = optionsOrFallback;
|
|
64
|
+
useDefaultFallback = false;
|
|
65
|
+
} else {
|
|
66
|
+
// 옵션 객체가 전달된 경우
|
|
67
|
+
const options = optionsOrFallback as WithSuspenseOptions;
|
|
68
|
+
fallback = options.fallback;
|
|
69
|
+
useDefaultFallback = options.useDefaultFallback ?? true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const defaultFallback = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
useDefaultFallback ? (
|
|
76
|
+
<SkeletonGroup>
|
|
77
|
+
<Skeleton width="60%" height={32} />
|
|
78
|
+
<Skeleton width="80%" />
|
|
79
|
+
<Skeleton width="70%" />
|
|
80
|
+
</SkeletonGroup>
|
|
81
|
+
) : null,
|
|
82
|
+
[useDefaultFallback]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const WrappedComponent = (props: P) => {
|
|
86
|
+
return (
|
|
87
|
+
<Suspense fallback={fallback ?? defaultFallback}>
|
|
88
|
+
<Component {...props} />
|
|
89
|
+
</Suspense>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
WrappedComponent.displayName = `withSuspense(${Component.displayName || Component.name || 'Component'})`;
|
|
94
|
+
|
|
95
|
+
return WrappedComponent;
|
|
96
|
+
}
|