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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +839 -0
  2. package/dist/framework/a11y/components/LiveRegion.d.ts +64 -0
  3. package/dist/framework/a11y/components/LiveRegion.d.ts.map +1 -0
  4. package/dist/framework/a11y/components/LiveRegion.js +43 -0
  5. package/dist/framework/a11y/components/SkipToContent.d.ts +62 -0
  6. package/dist/framework/a11y/components/SkipToContent.d.ts.map +1 -0
  7. package/dist/framework/a11y/components/SkipToContent.js +60 -0
  8. package/dist/framework/a11y/hooks/useFocusManagement.d.ts +60 -0
  9. package/dist/framework/a11y/hooks/useFocusManagement.d.ts.map +1 -0
  10. package/dist/framework/a11y/hooks/useFocusManagement.js +71 -0
  11. package/dist/framework/a11y/hooks/useFocusTrap.d.ts +64 -0
  12. package/dist/framework/a11y/hooks/useFocusTrap.d.ts.map +1 -0
  13. package/dist/framework/a11y/hooks/useFocusTrap.js +185 -0
  14. package/dist/framework/a11y/hooks/useLiveRegion.d.ts +56 -0
  15. package/dist/framework/a11y/hooks/useLiveRegion.d.ts.map +1 -0
  16. package/dist/framework/a11y/hooks/useLiveRegion.js +60 -0
  17. package/dist/framework/a11y/index.d.ts +16 -0
  18. package/dist/framework/a11y/index.d.ts.map +1 -0
  19. package/dist/framework/a11y/index.js +11 -0
  20. package/dist/framework/branding/context.d.ts +52 -0
  21. package/dist/framework/branding/context.d.ts.map +1 -0
  22. package/dist/framework/branding/context.js +96 -0
  23. package/dist/framework/branding/css-vars.d.ts +34 -0
  24. package/dist/framework/branding/css-vars.d.ts.map +1 -0
  25. package/dist/framework/branding/css-vars.js +95 -0
  26. package/dist/framework/branding/tailwind-config.d.ts +38 -0
  27. package/dist/framework/branding/tailwind-config.d.ts.map +1 -0
  28. package/dist/framework/branding/tailwind-config.js +66 -0
  29. package/dist/framework/components/BrandedButton.d.ts +53 -0
  30. package/dist/framework/components/BrandedButton.d.ts.map +1 -0
  31. package/dist/framework/components/BrandedButton.js +40 -0
  32. package/dist/framework/components/BrandedCard.d.ts +52 -0
  33. package/dist/framework/components/BrandedCard.d.ts.map +1 -0
  34. package/dist/framework/components/BrandedCard.js +73 -0
  35. package/dist/framework/components/ErrorBoundary.d.ts +92 -0
  36. package/dist/framework/components/ErrorBoundary.d.ts.map +1 -0
  37. package/dist/framework/components/ErrorBoundary.js +121 -0
  38. package/dist/framework/components/HuaUxLayout.d.ts +29 -0
  39. package/dist/framework/components/HuaUxLayout.d.ts.map +1 -0
  40. package/dist/framework/components/HuaUxLayout.js +32 -0
  41. package/dist/framework/components/HuaUxPage.d.ts +48 -0
  42. package/dist/framework/components/HuaUxPage.d.ts.map +1 -0
  43. package/dist/framework/components/HuaUxPage.js +105 -0
  44. package/dist/framework/components/Providers.d.ts +17 -0
  45. package/dist/framework/components/Providers.d.ts.map +1 -0
  46. package/dist/framework/components/Providers.js +72 -0
  47. package/dist/framework/components/WelcomePage.d.ts +44 -0
  48. package/dist/framework/components/WelcomePage.d.ts.map +1 -0
  49. package/dist/framework/components/WelcomePage.js +80 -0
  50. package/dist/framework/config/index.d.ts +182 -0
  51. package/dist/framework/config/index.d.ts.map +1 -0
  52. package/dist/framework/config/index.js +329 -0
  53. package/dist/framework/config/merge.d.ts +26 -0
  54. package/dist/framework/config/merge.d.ts.map +1 -0
  55. package/dist/framework/config/merge.js +160 -0
  56. package/dist/framework/config/schema.d.ts +25 -0
  57. package/dist/framework/config/schema.d.ts.map +1 -0
  58. package/dist/framework/config/schema.js +122 -0
  59. package/dist/framework/hooks/useMotion.d.ts +45 -0
  60. package/dist/framework/hooks/useMotion.d.ts.map +1 -0
  61. package/dist/framework/hooks/useMotion.js +40 -0
  62. package/dist/framework/index.d.ts +37 -0
  63. package/dist/framework/index.d.ts.map +1 -0
  64. package/dist/framework/index.js +42 -0
  65. package/dist/framework/license/errors.d.ts +15 -0
  66. package/dist/framework/license/errors.d.ts.map +1 -0
  67. package/dist/framework/license/errors.js +52 -0
  68. package/dist/framework/license/index.d.ts +70 -0
  69. package/dist/framework/license/index.d.ts.map +1 -0
  70. package/dist/framework/license/index.js +124 -0
  71. package/dist/framework/license/loader.d.ts +26 -0
  72. package/dist/framework/license/loader.d.ts.map +1 -0
  73. package/dist/framework/license/loader.js +137 -0
  74. package/dist/framework/license/types.d.ts +67 -0
  75. package/dist/framework/license/types.d.ts.map +1 -0
  76. package/dist/framework/license/types.js +18 -0
  77. package/dist/framework/loading/components/SkeletonGroup.d.ts +44 -0
  78. package/dist/framework/loading/components/SkeletonGroup.d.ts.map +1 -0
  79. package/dist/framework/loading/components/SkeletonGroup.js +34 -0
  80. package/dist/framework/loading/components/SuspenseWrapper.d.ts +58 -0
  81. package/dist/framework/loading/components/SuspenseWrapper.d.ts.map +1 -0
  82. package/dist/framework/loading/components/SuspenseWrapper.js +40 -0
  83. package/dist/framework/loading/hoc/withSuspense.d.ts +46 -0
  84. package/dist/framework/loading/hoc/withSuspense.d.ts.map +1 -0
  85. package/dist/framework/loading/hoc/withSuspense.js +54 -0
  86. package/dist/framework/loading/hooks/useDelayedLoading.d.ts +56 -0
  87. package/dist/framework/loading/hooks/useDelayedLoading.d.ts.map +1 -0
  88. package/dist/framework/loading/hooks/useDelayedLoading.js +97 -0
  89. package/dist/framework/loading/hooks/useLoadingState.d.ts +69 -0
  90. package/dist/framework/loading/hooks/useLoadingState.d.ts.map +1 -0
  91. package/dist/framework/loading/hooks/useLoadingState.js +59 -0
  92. package/dist/framework/loading/index.d.ts +16 -0
  93. package/dist/framework/loading/index.d.ts.map +1 -0
  94. package/dist/framework/loading/index.js +13 -0
  95. package/dist/framework/middleware/i18n.d.ts +90 -0
  96. package/dist/framework/middleware/i18n.d.ts.map +1 -0
  97. package/dist/framework/middleware/i18n.js +99 -0
  98. package/dist/framework/plugins/index.d.ts +8 -0
  99. package/dist/framework/plugins/index.d.ts.map +1 -0
  100. package/dist/framework/plugins/index.js +6 -0
  101. package/dist/framework/plugins/registry.d.ts +95 -0
  102. package/dist/framework/plugins/registry.d.ts.map +1 -0
  103. package/dist/framework/plugins/registry.js +160 -0
  104. package/dist/framework/plugins/types.d.ts +97 -0
  105. package/dist/framework/plugins/types.d.ts.map +1 -0
  106. package/dist/framework/plugins/types.js +6 -0
  107. package/dist/framework/seo/geo/examples.d.ts +87 -0
  108. package/dist/framework/seo/geo/examples.d.ts.map +1 -0
  109. package/dist/framework/seo/geo/examples.js +295 -0
  110. package/dist/framework/seo/geo/generateGEOMetadata.d.ts +107 -0
  111. package/dist/framework/seo/geo/generateGEOMetadata.d.ts.map +1 -0
  112. package/dist/framework/seo/geo/generateGEOMetadata.js +404 -0
  113. package/dist/framework/seo/geo/index.d.ts +19 -0
  114. package/dist/framework/seo/geo/index.d.ts.map +1 -0
  115. package/dist/framework/seo/geo/index.js +21 -0
  116. package/dist/framework/seo/geo/presets.d.ts +52 -0
  117. package/dist/framework/seo/geo/presets.d.ts.map +1 -0
  118. package/dist/framework/seo/geo/presets.js +47 -0
  119. package/dist/framework/seo/geo/structuredData.d.ts +187 -0
  120. package/dist/framework/seo/geo/structuredData.d.ts.map +1 -0
  121. package/dist/framework/seo/geo/structuredData.js +354 -0
  122. package/dist/framework/seo/geo/test-utils.d.ts +78 -0
  123. package/dist/framework/seo/geo/test-utils.d.ts.map +1 -0
  124. package/dist/framework/seo/geo/test-utils.js +139 -0
  125. package/dist/framework/seo/geo/types.d.ts +225 -0
  126. package/dist/framework/seo/geo/types.d.ts.map +1 -0
  127. package/dist/framework/seo/geo/types.js +51 -0
  128. package/dist/framework/types/index.d.ts +577 -0
  129. package/dist/framework/types/index.d.ts.map +1 -0
  130. package/dist/framework/types/index.js +6 -0
  131. package/dist/framework/utils/data-fetching.d.ts +45 -0
  132. package/dist/framework/utils/data-fetching.d.ts.map +1 -0
  133. package/dist/framework/utils/data-fetching.js +74 -0
  134. package/dist/framework/utils/file-structure.d.ts +29 -0
  135. package/dist/framework/utils/file-structure.d.ts.map +1 -0
  136. package/dist/framework/utils/file-structure.js +72 -0
  137. package/dist/framework/utils/metadata.d.ts +109 -0
  138. package/dist/framework/utils/metadata.d.ts.map +1 -0
  139. package/dist/framework/utils/metadata.js +105 -0
  140. package/dist/index.d.ts +15 -0
  141. package/dist/index.d.ts.map +1 -0
  142. package/dist/index.js +21 -0
  143. package/dist/presets/index.d.ts +8 -0
  144. package/dist/presets/index.d.ts.map +1 -0
  145. package/dist/presets/index.js +7 -0
  146. package/dist/presets/marketing.d.ts +41 -0
  147. package/dist/presets/marketing.d.ts.map +1 -0
  148. package/dist/presets/marketing.js +81 -0
  149. package/dist/presets/product.d.ts +41 -0
  150. package/dist/presets/product.d.ts.map +1 -0
  151. package/dist/presets/product.js +74 -0
  152. package/package.json +91 -0
  153. package/src/framework/README.md +329 -0
  154. package/src/framework/__tests__/branding/css-vars.test.ts +147 -0
  155. package/src/framework/__tests__/components/ErrorBoundary.test.tsx +146 -0
  156. package/src/framework/__tests__/config/defineConfig.test.ts +138 -0
  157. package/src/framework/__tests__/hooks/useMotion.test.ts +105 -0
  158. package/src/framework/__tests__/seo/geo/generateGEOMetadata.test.ts +207 -0
  159. package/src/framework/__tests__/seo/geo/structuredData.test.ts +262 -0
  160. package/src/framework/a11y/components/LiveRegion.tsx +89 -0
  161. package/src/framework/a11y/components/SkipToContent.tsx +103 -0
  162. package/src/framework/a11y/hooks/useFocusManagement.ts +125 -0
  163. package/src/framework/a11y/hooks/useFocusTrap.ts +239 -0
  164. package/src/framework/a11y/hooks/useLiveRegion.ts +95 -0
  165. package/src/framework/a11y/index.ts +17 -0
  166. package/src/framework/branding/context.tsx +135 -0
  167. package/src/framework/branding/css-vars.ts +110 -0
  168. package/src/framework/branding/tailwind-config.ts +90 -0
  169. package/src/framework/components/BrandedButton.tsx +94 -0
  170. package/src/framework/components/BrandedCard.tsx +87 -0
  171. package/src/framework/components/ErrorBoundary.tsx +215 -0
  172. package/src/framework/components/HuaUxLayout.tsx +36 -0
  173. package/src/framework/components/HuaUxPage.tsx +138 -0
  174. package/src/framework/components/Providers.tsx +98 -0
  175. package/src/framework/components/WelcomePage.tsx +207 -0
  176. package/src/framework/config/index.ts +349 -0
  177. package/src/framework/config/merge.ts +190 -0
  178. package/src/framework/config/schema.ts +140 -0
  179. package/src/framework/hooks/useMotion.ts +57 -0
  180. package/src/framework/index.ts +122 -0
  181. package/src/framework/license/errors.ts +63 -0
  182. package/src/framework/license/index.ts +137 -0
  183. package/src/framework/license/loader.ts +158 -0
  184. package/src/framework/license/types.ts +95 -0
  185. package/src/framework/loading/components/SkeletonGroup.tsx +70 -0
  186. package/src/framework/loading/components/SuspenseWrapper.tsx +88 -0
  187. package/src/framework/loading/hoc/withSuspense.tsx +96 -0
  188. package/src/framework/loading/hooks/useDelayedLoading.ts +127 -0
  189. package/src/framework/loading/hooks/useLoadingState.ts +103 -0
  190. package/src/framework/loading/index.ts +19 -0
  191. package/src/framework/middleware/i18n.ts +161 -0
  192. package/src/framework/middleware/index.ts +7 -0
  193. package/src/framework/plugins/index.ts +13 -0
  194. package/src/framework/plugins/registry.ts +186 -0
  195. package/src/framework/plugins/types.ts +106 -0
  196. package/src/framework/seo/geo/examples.tsx +415 -0
  197. package/src/framework/seo/geo/generateGEOMetadata.ts +441 -0
  198. package/src/framework/seo/geo/index.ts +61 -0
  199. package/src/framework/seo/geo/presets.ts +58 -0
  200. package/src/framework/seo/geo/structuredData.ts +422 -0
  201. package/src/framework/seo/geo/test-utils.ts +179 -0
  202. package/src/framework/seo/geo/types.ts +315 -0
  203. package/src/framework/types/index.ts +623 -0
  204. package/src/framework/utils/data-fetching.ts +95 -0
  205. package/src/framework/utils/file-structure.ts +88 -0
  206. package/src/framework/utils/metadata.ts +152 -0
  207. package/src/index.ts +31 -0
  208. package/src/presets/index.ts +8 -0
  209. package/src/presets/marketing.ts +88 -0
  210. package/src/presets/product.ts +81 -0
@@ -0,0 +1,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
+ }