@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,127 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - useDelayedLoading
3
+ *
4
+ * 빠른 API 응답 시 로딩 UI 깜빡임을 방지하는 hook
5
+ * Prevents loading UI flicker for fast API responses
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { useEffect, useRef, useState } from 'react';
11
+
12
+ /**
13
+ * Delayed Loading 옵션
14
+ */
15
+ export interface DelayedLoadingOptions {
16
+ /**
17
+ * 로딩 UI를 표시하기 전 대기 시간 (밀리초)
18
+ * Delay before showing loading UI (milliseconds)
19
+ *
20
+ * @default 300
21
+ */
22
+ delay?: number;
23
+
24
+ /**
25
+ * 최소 표시 시간 (밀리초) - 로딩이 너무 빨리 끝나도 최소한 이 시간은 표시
26
+ * Minimum display time (milliseconds) - show loading for at least this duration
27
+ *
28
+ * @default 0
29
+ */
30
+ minDisplayTime?: number;
31
+ }
32
+
33
+ /**
34
+ * useDelayedLoading Hook
35
+ *
36
+ * 빠른 API 응답 시 로딩 UI가 깜빡거리는 것을 방지합니다.
37
+ * 300ms 이하로 끝나면 로딩 UI를 아예 표시하지 않습니다.
38
+ *
39
+ * Prevents loading UI flicker for fast API responses.
40
+ * If loading completes within 300ms, the loading UI is not shown at all.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * function MyComponent() {
45
+ * const [isLoading, setIsLoading] = useState(false);
46
+ * const showLoading = useDelayedLoading(isLoading);
47
+ *
48
+ * const fetchData = async () => {
49
+ * setIsLoading(true);
50
+ * await api.getData(); // 빠르게 끝나면 로딩 UI 안보임
51
+ * setIsLoading(false);
52
+ * };
53
+ *
54
+ * return showLoading ? <Spinner /> : <Content />;
55
+ * }
56
+ * ```
57
+ *
58
+ * @param isLoading - 현재 로딩 상태
59
+ * @param options - 옵션
60
+ * @returns 실제로 로딩 UI를 표시할지 여부
61
+ */
62
+ export function useDelayedLoading(
63
+ isLoading: boolean,
64
+ options: DelayedLoadingOptions = {}
65
+ ): boolean {
66
+ const { delay = 300, minDisplayTime = 0 } = options;
67
+ const [showLoading, setShowLoading] = useState(false);
68
+ const loadingStartTimeRef = useRef<number | null>(null);
69
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
70
+
71
+ useEffect(() => {
72
+ // 이전 timeout 정리 (race condition 방지)
73
+ if (timeoutRef.current) {
74
+ clearTimeout(timeoutRef.current);
75
+ timeoutRef.current = null;
76
+ }
77
+
78
+ if (isLoading) {
79
+ // 로딩 시작 시간 기록
80
+ const startTime = Date.now();
81
+ loadingStartTimeRef.current = startTime;
82
+
83
+ // delay 후에 로딩 UI 표시
84
+ timeoutRef.current = setTimeout(() => {
85
+ // timeout이 여전히 유효한지 확인 (race condition 방지)
86
+ if (timeoutRef.current) {
87
+ setShowLoading(true);
88
+ timeoutRef.current = null;
89
+ }
90
+ }, delay);
91
+ } else {
92
+ // 로딩이 끝났을 때
93
+ if (loadingStartTimeRef.current !== null) {
94
+ const elapsedTime = Date.now() - loadingStartTimeRef.current;
95
+ const remainingTime = Math.max(0, minDisplayTime - elapsedTime);
96
+
97
+ if (remainingTime > 0) {
98
+ // 최소 표시 시간이 남아있으면 그 시간만큼 더 표시
99
+ timeoutRef.current = setTimeout(() => {
100
+ // timeout이 여전히 유효한지 확인 (race condition 방지)
101
+ if (timeoutRef.current) {
102
+ setShowLoading(false);
103
+ loadingStartTimeRef.current = null;
104
+ timeoutRef.current = null;
105
+ }
106
+ }, remainingTime);
107
+ } else {
108
+ // 이미 최소 표시 시간을 넘었으면 즉시 숨김
109
+ setShowLoading(false);
110
+ loadingStartTimeRef.current = null;
111
+ }
112
+ } else {
113
+ // 로딩이 시작되지 않았으면 즉시 숨김
114
+ setShowLoading(false);
115
+ }
116
+ }
117
+
118
+ return () => {
119
+ if (timeoutRef.current) {
120
+ clearTimeout(timeoutRef.current);
121
+ timeoutRef.current = null;
122
+ }
123
+ };
124
+ }, [isLoading, delay, minDisplayTime]);
125
+
126
+ return showLoading;
127
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - useLoadingState
3
+ *
4
+ * 로딩 상태를 관리하는 편의성 hook
5
+ * Convenience hook for managing loading state
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { useCallback, useState } from 'react';
11
+ import { useDelayedLoading, type DelayedLoadingOptions } from './useDelayedLoading';
12
+
13
+ /**
14
+ * useLoadingState 반환 타입
15
+ */
16
+ export interface UseLoadingStateReturn {
17
+ /**
18
+ * 실제로 로딩 UI를 표시할지 여부 (useDelayedLoading 적용)
19
+ * Whether to actually show loading UI (with useDelayedLoading applied)
20
+ */
21
+ showLoading: boolean;
22
+
23
+ /**
24
+ * 로딩 시작
25
+ * Start loading
26
+ */
27
+ startLoading: () => void;
28
+
29
+ /**
30
+ * 로딩 종료
31
+ * Stop loading
32
+ */
33
+ stopLoading: () => void;
34
+
35
+ /**
36
+ * 로딩 상태 토글
37
+ * Toggle loading state
38
+ */
39
+ toggleLoading: () => void;
40
+
41
+ /**
42
+ * 현재 로딩 상태 (지연 없이)
43
+ * Current loading state (without delay)
44
+ */
45
+ isLoading: boolean;
46
+ }
47
+
48
+ /**
49
+ * useLoadingState Hook
50
+ *
51
+ * 로딩 상태를 쉽게 관리할 수 있는 편의성 hook입니다.
52
+ * useDelayedLoading이 자동으로 적용됩니다.
53
+ *
54
+ * Convenience hook for easily managing loading state.
55
+ * useDelayedLoading is automatically applied.
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * function MyComponent() {
60
+ * const { showLoading, startLoading, stopLoading } = useLoadingState();
61
+ *
62
+ * const fetchData = async () => {
63
+ * startLoading();
64
+ * try {
65
+ * await api.getData();
66
+ * } finally {
67
+ * stopLoading();
68
+ * }
69
+ * };
70
+ *
71
+ * return showLoading && <Spinner />;
72
+ * }
73
+ * ```
74
+ *
75
+ * @param options - useDelayedLoading 옵션
76
+ * @returns 로딩 상태 제어 함수
77
+ */
78
+ export function useLoadingState(
79
+ options: DelayedLoadingOptions = {}
80
+ ): UseLoadingStateReturn {
81
+ const [isLoading, setIsLoading] = useState(false);
82
+ const showLoading = useDelayedLoading(isLoading, options);
83
+
84
+ const startLoading = useCallback(() => {
85
+ setIsLoading(true);
86
+ }, []);
87
+
88
+ const stopLoading = useCallback(() => {
89
+ setIsLoading(false);
90
+ }, []);
91
+
92
+ const toggleLoading = useCallback(() => {
93
+ setIsLoading((prev) => !prev);
94
+ }, []);
95
+
96
+ return {
97
+ showLoading,
98
+ startLoading,
99
+ stopLoading,
100
+ toggleLoading,
101
+ isLoading,
102
+ };
103
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - Loading State
3
+ *
4
+ * 로딩 상태 최적화 도구 모음
5
+ * Loading state optimization tools
6
+ */
7
+
8
+ export { useDelayedLoading } from './hooks/useDelayedLoading';
9
+ export { useLoadingState } from './hooks/useLoadingState';
10
+ export { SkeletonGroup } from './components/SkeletonGroup';
11
+ export { SuspenseWrapper } from './components/SuspenseWrapper';
12
+ export { withSuspense } from './hoc/withSuspense';
13
+
14
+ // Skeleton은 @hua-labs/ui에서 re-export
15
+ export { Skeleton } from '@hua-labs/ui';
16
+
17
+ export type { DelayedLoadingOptions } from './hooks/useDelayedLoading';
18
+ export type { SkeletonGroupProps } from './components/SkeletonGroup';
19
+ export type { SuspenseWrapperProps } from './components/SuspenseWrapper';
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - i18n Middleware
3
+ *
4
+ * i18n middleware for automatic language detection and routing
5
+ *
6
+ * **타입 안전성**: Next.js를 peerDependency로 사용하므로,
7
+ * Next.js 프로젝트에서는 자동으로 타입이 추론됩니다.
8
+ *
9
+ * **Type Safety**: Next.js is used as a peerDependency,
10
+ * so types are automatically inferred in Next.js projects.
11
+ */
12
+
13
+ // Next.js types - 조건부 import로 타입 안전성 향상
14
+ // Next.js types - Improved type safety with conditional import
15
+
16
+ /**
17
+ * Base interface for NextRequest (when Next.js is not available)
18
+ */
19
+ interface BaseNextRequest {
20
+ headers: {
21
+ get(name: string): string | null;
22
+ };
23
+ cookies: {
24
+ get(name: string): { value: string } | undefined;
25
+ };
26
+ nextUrl: {
27
+ pathname: string;
28
+ searchParams: URLSearchParams;
29
+ };
30
+ }
31
+
32
+ /**
33
+ * NextRequest type - Next.js가 있으면 실제 타입, 없으면 기본 인터페이스
34
+ *
35
+ * Next.js가 optional peerDependency이므로, 타입 레벨에서만 처리합니다.
36
+ * 실제 Next.js 프로젝트에서는 Next.js의 NextRequest 타입이 자동으로 사용됩니다.
37
+ */
38
+ type NextRequest = BaseNextRequest;
39
+
40
+ /**
41
+ * Base interface for NextResponse (when Next.js is not available)
42
+ */
43
+ interface BaseNextResponse {
44
+ headers: Headers;
45
+ next?(): BaseNextResponse;
46
+ redirect?(url: string): BaseNextResponse;
47
+ }
48
+
49
+ /**
50
+ * NextResponse factory function
51
+ * Next.js가 설치되어 있으면 실제 NextResponse를 사용하고, 없으면 폴백 구현을 사용합니다.
52
+ */
53
+ let NextResponseFactory: {
54
+ next(): BaseNextResponse;
55
+ redirect(url: string): BaseNextResponse;
56
+ };
57
+
58
+ try {
59
+ // Next.js가 설치되어 있으면 실제 NextResponse 사용
60
+ const nextServer = require('next/server');
61
+ NextResponseFactory = nextServer.NextResponse;
62
+ } catch {
63
+ // Next.js가 없으면 폴백 구현
64
+ // Fallback implementation if Next.js is not available
65
+ NextResponseFactory = {
66
+ next: () => ({
67
+ headers: new Headers(),
68
+ } as BaseNextResponse),
69
+ redirect: (url: string) => ({
70
+ headers: new Headers(),
71
+ } as BaseNextResponse),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * i18n middleware configuration
77
+ */
78
+ export interface I18nMiddlewareConfig {
79
+ /**
80
+ * Default language
81
+ */
82
+ defaultLanguage: string;
83
+
84
+ /**
85
+ * Supported languages
86
+ */
87
+ supportedLanguages: string[];
88
+
89
+ /**
90
+ * Language detection strategy
91
+ */
92
+ detectionStrategy?: 'header' | 'cookie' | 'path' | 'query';
93
+ }
94
+
95
+ /**
96
+ * Create i18n middleware for Next.js
97
+ *
98
+ * **⚠️ Edge Runtime Note**: This middleware runs on Edge Runtime.
99
+ * Make sure your middleware.ts file exports the runtime config:
100
+ *
101
+ * ```ts
102
+ * export const runtime = 'edge';
103
+ * ```
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * // middleware.ts
108
+ * import { createI18nMiddleware } from '@hua-labs/hua-ux/framework';
109
+ *
110
+ * // Edge Runtime 명시 (Vercel 자동 감지 방지)
111
+ * export const runtime = 'edge';
112
+ *
113
+ * export default createI18nMiddleware({
114
+ * defaultLanguage: 'ko',
115
+ * supportedLanguages: ['ko', 'en'],
116
+ * });
117
+ * ```
118
+ *
119
+ * **Alternative**: If you don't want to use Edge Runtime, you can handle
120
+ * language detection in your API routes or client components instead.
121
+ */
122
+ export function createI18nMiddleware(config: I18nMiddlewareConfig) {
123
+ return function i18nMiddleware(request: NextRequest) {
124
+ const { defaultLanguage, supportedLanguages, detectionStrategy = 'header' } = config;
125
+
126
+ // Get language from various sources
127
+ let language = defaultLanguage;
128
+
129
+ if (detectionStrategy === 'header') {
130
+ const acceptLanguage = request.headers.get('accept-language');
131
+ if (acceptLanguage) {
132
+ // Simple language detection from Accept-Language header
133
+ const preferredLang = acceptLanguage.split(',')[0].split('-')[0].toLowerCase();
134
+ if (supportedLanguages.includes(preferredLang)) {
135
+ language = preferredLang;
136
+ }
137
+ }
138
+ } else if (detectionStrategy === 'cookie') {
139
+ const cookieLang = request.cookies.get('language')?.value;
140
+ if (cookieLang && supportedLanguages.includes(cookieLang)) {
141
+ language = cookieLang;
142
+ }
143
+ } else if (detectionStrategy === 'path') {
144
+ const pathLang = request.nextUrl.pathname.split('/')[1];
145
+ if (pathLang && supportedLanguages.includes(pathLang)) {
146
+ language = pathLang;
147
+ }
148
+ } else if (detectionStrategy === 'query') {
149
+ const queryLang = request.nextUrl.searchParams.get('lang');
150
+ if (queryLang && supportedLanguages.includes(queryLang)) {
151
+ language = queryLang;
152
+ }
153
+ }
154
+
155
+ // Add language to request headers for use in components
156
+ const response = NextResponseFactory.next();
157
+ response.headers.set('x-language', language);
158
+
159
+ return response;
160
+ };
161
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - Middleware System
3
+ *
4
+ * Middleware system for framework-level functionality
5
+ */
6
+
7
+ export * from './i18n';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - Plugin System
3
+ *
4
+ * 플러그인 시스템 메인 export
5
+ */
6
+
7
+ export {
8
+ pluginRegistry,
9
+ registerPlugin,
10
+ getPlugin,
11
+ getAllPlugins
12
+ } from './registry';
13
+ export type { HuaUxPlugin } from './types';
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - Plugin Registry
3
+ *
4
+ * 플러그인 등록 및 관리 시스템
5
+ */
6
+
7
+ import type { HuaUxPlugin } from './types';
8
+ import { hasLicense } from '../license';
9
+
10
+ /**
11
+ * 플러그인 레지스트리 / Plugin registry
12
+ *
13
+ * 모든 플러그인을 등록하고 관리하는 싱글톤 클래스
14
+ * Singleton class that registers and manages all plugins
15
+ */
16
+ class PluginRegistry {
17
+ private plugins: Map<string, HuaUxPlugin> = new Map();
18
+ private initialized: Set<string> = new Set();
19
+
20
+ /**
21
+ * 플러그인 등록 / Register plugin
22
+ *
23
+ * @param plugin - 등록할 플러그인
24
+ * @throws 라이선스가 없을 경우 에러
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { registerPlugin } from '@hua-labs/hua-ux/framework';
29
+ * import { motionProPlugin } from '@hua-labs/motion-core/pro';
30
+ *
31
+ * registerPlugin(motionProPlugin);
32
+ * ```
33
+ */
34
+ register(plugin: HuaUxPlugin): void {
35
+ // 중복 등록 확인
36
+ if (this.plugins.has(plugin.name)) {
37
+ console.warn(
38
+ `[hua-ux] Plugin "${plugin.name}" is already registered. Overwriting...`
39
+ );
40
+ }
41
+
42
+ // 라이선스 검증
43
+ if (plugin.license !== 'free') {
44
+ // 플러그인의 checkLicense 함수가 있으면 사용
45
+ if (plugin.checkLicense) {
46
+ if (!plugin.checkLicense()) {
47
+ throw new Error(
48
+ `[hua-ux] ❌ Plugin "${plugin.name}" requires a valid ${plugin.license} license.\n` +
49
+ `[hua-ux] ❌ Please purchase a ${plugin.license} license.\n\n` +
50
+ `💡 Purchase link: https://hua-labs.com/pricing`
51
+ );
52
+ }
53
+ } else {
54
+ // 기본 라이선스 검증 (license feature 기반)
55
+ const feature = `plugin-${plugin.name}` as any;
56
+ if (!hasLicense(feature)) {
57
+ // 라이선스가 없어도 경고만 표시 (개발 환경)
58
+ if (process.env.NODE_ENV === 'development') {
59
+ console.warn(
60
+ `[hua-ux] ⚠️ Plugin "${plugin.name}" may require a ${plugin.license} license.\n` +
61
+ `[hua-ux] ⚠️ Some features may not work without a valid license.`
62
+ );
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ // 플러그인 등록
69
+ this.plugins.set(plugin.name, plugin);
70
+
71
+ if (process.env.NODE_ENV === 'development') {
72
+ console.log(
73
+ `[hua-ux] ✅ Plugin "${plugin.name}" v${plugin.version} registered successfully`
74
+ );
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 플러그인 초기화 / Initialize plugin
80
+ *
81
+ * 등록된 플러그인의 init 함수를 호출합니다.
82
+ * Calls the init function of registered plugins.
83
+ *
84
+ * @param pluginName - 플러그인 이름
85
+ * @param config - 프레임워크 설정
86
+ */
87
+ async initialize(pluginName: string, config: any): Promise<void> {
88
+ const plugin = this.plugins.get(pluginName);
89
+ if (!plugin) {
90
+ throw new Error(`[hua-ux] Plugin "${pluginName}" not found`);
91
+ }
92
+
93
+ // 이미 초기화된 경우 스킵
94
+ if (this.initialized.has(pluginName)) {
95
+ return;
96
+ }
97
+
98
+ // init 함수 호출
99
+ if (plugin.init) {
100
+ await plugin.init(config);
101
+ this.initialized.add(pluginName);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 모든 플러그인 초기화 / Initialize all plugins
107
+ *
108
+ * @param config - 프레임워크 설정
109
+ */
110
+ async initializeAll(config: any): Promise<void> {
111
+ const promises = Array.from(this.plugins.keys()).map(name =>
112
+ this.initialize(name, config)
113
+ );
114
+ await Promise.all(promises);
115
+ }
116
+
117
+ /**
118
+ * 플러그인 가져오기 / Get plugin
119
+ *
120
+ * @param name - 플러그인 이름
121
+ * @returns 플러그인 또는 undefined
122
+ */
123
+ get(name: string): HuaUxPlugin | undefined {
124
+ return this.plugins.get(name);
125
+ }
126
+
127
+ /**
128
+ * 모든 플러그인 가져오기 / Get all plugins
129
+ *
130
+ * @returns 플러그인 배열
131
+ */
132
+ getAll(): HuaUxPlugin[] {
133
+ return Array.from(this.plugins.values());
134
+ }
135
+
136
+ /**
137
+ * 플러그인 등록 해제 / Unregister plugin
138
+ *
139
+ * @param name - 플러그인 이름
140
+ */
141
+ unregister(name: string): void {
142
+ this.plugins.delete(name);
143
+ this.initialized.delete(name);
144
+ }
145
+
146
+ /**
147
+ * 레지스트리 초기화 (테스트용) / Reset registry (for testing)
148
+ */
149
+ reset(): void {
150
+ this.plugins.clear();
151
+ this.initialized.clear();
152
+ }
153
+ }
154
+
155
+ /**
156
+ * 전역 플러그인 레지스트리 인스턴스 / Global plugin registry instance
157
+ */
158
+ export const pluginRegistry = new PluginRegistry();
159
+
160
+ /**
161
+ * 플러그인 등록 (편의 함수) / Register plugin (convenience function)
162
+ *
163
+ * @param plugin - 등록할 플러그인
164
+ */
165
+ export function registerPlugin(plugin: HuaUxPlugin): void {
166
+ pluginRegistry.register(plugin);
167
+ }
168
+
169
+ /**
170
+ * 플러그인 가져오기 (편의 함수) / Get plugin (convenience function)
171
+ *
172
+ * @param name - 플러그인 이름
173
+ * @returns 플러그인 또는 undefined
174
+ */
175
+ export function getPlugin(name: string): HuaUxPlugin | undefined {
176
+ return pluginRegistry.get(name);
177
+ }
178
+
179
+ /**
180
+ * 모든 플러그인 가져오기 (편의 함수) / Get all plugins (convenience function)
181
+ *
182
+ * @returns 플러그인 배열
183
+ */
184
+ export function getAllPlugins(): HuaUxPlugin[] {
185
+ return pluginRegistry.getAll();
186
+ }