@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,94 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - BrandedButton
3
+ *
4
+ * Button 컴포넌트에 branding을 자동으로 적용하는 wrapper
5
+ * Wrapper that automatically applies branding to Button component
6
+ *
7
+ * CSS 변수를 사용하여 Tailwind의 최적화를 활용합니다.
8
+ * Uses CSS variables to leverage Tailwind's optimization.
9
+ */
10
+
11
+ 'use client';
12
+
13
+ import * as React from 'react';
14
+ import type { ComponentPropsWithoutRef } from 'react';
15
+ import { Button, merge } from '@hua-labs/ui';
16
+ import { useBranding } from '../branding/context';
17
+
18
+ /**
19
+ * BrandedButton Component
20
+ *
21
+ * Button 컴포넌트에 branding 설정을 자동으로 적용합니다.
22
+ * Automatically applies branding configuration to Button component.
23
+ *
24
+ * **자동 적용되는 branding**:
25
+ * - Primary 색상: `variant="default"`일 때 `bg-[var(--color-primary)]` 사용
26
+ * - Secondary 색상: `variant="secondary"`일 때 `bg-[var(--color-secondary)]` 사용
27
+ * - Accent 색상: `variant="outline"`일 때 `border-[var(--color-accent)]` 사용
28
+ *
29
+ * **Auto-applied branding**:
30
+ * - Primary color: Uses `bg-[var(--color-primary)]` when `variant="default"`
31
+ * - Secondary color: Uses `bg-[var(--color-secondary)]` when `variant="secondary"`
32
+ * - Accent color: Uses `border-[var(--color-accent)]` when `variant="outline"`
33
+ *
34
+ * **CSS 변수 방식의 장점**:
35
+ * - Tailwind의 JIT 컴파일러 최적화 활용
36
+ * - 인라인 스타일 없이 깔끔한 코드
37
+ * - 런타임에 동적으로 색상 변경 가능
38
+ *
39
+ * **Advantages of CSS variables**:
40
+ * - Leverages Tailwind's JIT compiler optimization
41
+ * - Clean code without inline styles
42
+ * - Dynamic color changes at runtime
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * // branding 설정이 있으면 자동으로 primary 색상 적용
47
+ * // Automatically applies primary color if branding is configured
48
+ * <BrandedButton variant="default">저장</BrandedButton>
49
+ *
50
+ * // branding이 없으면 기본 Button과 동일하게 동작
51
+ * // Works same as default Button if branding is not configured
52
+ * <BrandedButton variant="outline">취소</BrandedButton>
53
+ * ```
54
+ */
55
+ type BrandedButtonProps = ComponentPropsWithoutRef<typeof Button>;
56
+
57
+ export const BrandedButton = React.forwardRef<
58
+ HTMLButtonElement | HTMLAnchorElement,
59
+ BrandedButtonProps
60
+ >((props: BrandedButtonProps, ref: React.ForwardedRef<HTMLButtonElement | HTMLAnchorElement>) => {
61
+ const { variant = 'default', className, ...restProps } = props;
62
+ const branding = useBranding();
63
+
64
+ // Branding 색상이 있으면 Tailwind arbitrary values 사용
65
+ // Use Tailwind arbitrary values if branding colors exist
66
+ let brandingClasses = '';
67
+
68
+ if (branding?.colors) {
69
+ if (variant === 'default' && branding.colors.primary) {
70
+ // Primary 색상을 배경으로 사용
71
+ // Use primary color as background
72
+ brandingClasses = 'bg-[var(--color-primary)] text-white border-[var(--color-primary)] hover:opacity-90';
73
+ } else if (variant === 'secondary' && branding.colors.secondary) {
74
+ // Secondary 색상을 배경으로 사용
75
+ // Use secondary color as background
76
+ brandingClasses = 'bg-[var(--color-secondary)] text-white border-[var(--color-secondary)] hover:opacity-90';
77
+ } else if (variant === 'outline' && branding.colors.accent) {
78
+ // Accent 색상을 테두리와 텍스트로 사용
79
+ // Use accent color for border and text
80
+ brandingClasses = 'border-[var(--color-accent)] text-[var(--color-accent)] bg-transparent hover:bg-[var(--color-accent)]/10';
81
+ }
82
+ }
83
+
84
+ return (
85
+ <Button
86
+ ref={ref}
87
+ variant={variant}
88
+ className={merge(brandingClasses, className)}
89
+ {...restProps}
90
+ />
91
+ );
92
+ });
93
+
94
+ BrandedButton.displayName = 'BrandedButton';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - BrandedCard
3
+ *
4
+ * Card 컴포넌트에 branding을 자동으로 적용하는 wrapper
5
+ * Wrapper that automatically applies branding to Card component
6
+ *
7
+ * CSS 변수를 사용하여 Tailwind의 최적화를 활용합니다.
8
+ * Uses CSS variables to leverage Tailwind's optimization.
9
+ */
10
+
11
+ 'use client';
12
+
13
+ import React from 'react';
14
+ import { Card, merge, type CardProps } from '@hua-labs/ui';
15
+ import { useBranding } from '../branding/context';
16
+
17
+ /**
18
+ * BrandedCard Component
19
+ *
20
+ * Card 컴포넌트에 branding 설정을 자동으로 적용합니다.
21
+ * Automatically applies branding configuration to Card component.
22
+ *
23
+ * **자동 적용되는 branding**:
24
+ * - Accent 색상: `variant="outline"`일 때 `border-[var(--color-accent)]` 사용
25
+ * - Primary 색상: `variant="default"`일 때 `bg-[var(--color-primary)]/5` 사용
26
+ *
27
+ * **Auto-applied branding**:
28
+ * - Accent color: Uses `border-[var(--color-accent)]` when `variant="outline"`
29
+ * - Primary color: Uses `bg-[var(--color-primary)]/5` when `variant="default"`
30
+ *
31
+ * **CSS 변수 방식의 장점**:
32
+ * - Tailwind의 JIT 컴파일러 최적화 활용
33
+ * - 인라인 스타일 없이 깔끔한 코드
34
+ * - 런타임에 동적으로 색상 변경 가능
35
+ *
36
+ * **Advantages of CSS variables**:
37
+ * - Leverages Tailwind's JIT compiler optimization
38
+ * - Clean code without inline styles
39
+ * - Dynamic color changes at runtime
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * // branding 설정이 있으면 자동으로 색상 적용
44
+ * // Automatically applies colors if branding is configured
45
+ * <BrandedCard variant="outline">
46
+ * <CardContent>내용</CardContent>
47
+ * </BrandedCard>
48
+ *
49
+ * // branding이 없으면 기본 Card와 동일하게 동작
50
+ * // Works same as default Card if branding is not configured
51
+ * <BrandedCard variant="elevated">
52
+ * <CardContent>내용</CardContent>
53
+ * </BrandedCard>
54
+ * ```
55
+ */
56
+ export const BrandedCard = React.forwardRef<HTMLDivElement, CardProps>(
57
+ ({ variant = 'default', className, ...props }, ref) => {
58
+ const branding = useBranding();
59
+
60
+ // Branding 색상이 있으면 Tailwind arbitrary values 사용
61
+ // Use Tailwind arbitrary values if branding colors exist
62
+ let brandingClasses = '';
63
+
64
+ if (branding?.colors) {
65
+ if (variant === 'outline' && branding.colors.accent) {
66
+ // Accent 색상을 테두리로 사용
67
+ // Use accent color as border
68
+ brandingClasses = 'border-[var(--color-accent)]';
69
+ } else if (variant === 'default' && branding.colors.primary) {
70
+ // Primary 색상의 약간의 tint를 배경에 적용
71
+ // Apply slight tint of primary color to background
72
+ brandingClasses = 'bg-[var(--color-primary)]/5';
73
+ }
74
+ }
75
+
76
+ return (
77
+ <Card
78
+ ref={ref}
79
+ variant={variant}
80
+ className={merge(brandingClasses, className)}
81
+ {...props}
82
+ />
83
+ );
84
+ }
85
+ );
86
+
87
+ BrandedCard.displayName = 'BrandedCard';
@@ -0,0 +1,215 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - ErrorBoundary
3
+ *
4
+ * React Error Boundary component for catching and handling errors
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React, { Component, ReactNode, ErrorInfo } from 'react';
10
+
11
+ /**
12
+ * Global error reporter interface
13
+ *
14
+ * 프로덕션 환경에서 에러 리포팅 서비스(Sentry, LogRocket 등)와 통합하기 위한 인터페이스
15
+ * Interface for integrating with error reporting services (Sentry, LogRocket, etc.) in production
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // Sentry 통합 예시
20
+ * window.__ERROR_REPORTER__ = (error, errorInfo) => {
21
+ * Sentry.captureException(error, {
22
+ * contexts: { react: errorInfo },
23
+ * });
24
+ * };
25
+ * ```
26
+ */
27
+ declare global {
28
+ interface Window {
29
+ __ERROR_REPORTER__?: (error: Error, errorInfo: ErrorInfo) => void;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Error Boundary Props
35
+ */
36
+ export interface ErrorBoundaryProps {
37
+ /**
38
+ * Children to render
39
+ */
40
+ children: ReactNode;
41
+
42
+ /**
43
+ * Fallback UI to show when error occurs
44
+ * Can be a ReactNode or a function that receives error and reset callback
45
+ */
46
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
47
+
48
+ /**
49
+ * Callback when error is caught
50
+ */
51
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
52
+
53
+ /**
54
+ * Custom error filter - return true to catch, false to rethrow
55
+ */
56
+ onReset?: () => void;
57
+ }
58
+
59
+ /**
60
+ * Error Boundary State
61
+ */
62
+ interface ErrorBoundaryState {
63
+ hasError: boolean;
64
+ error: Error | null;
65
+ }
66
+
67
+ /**
68
+ * Default fallback UI
69
+ */
70
+ function DefaultFallback({ error, reset }: { error: Error; reset: () => void }) {
71
+ return (
72
+ <div
73
+ role="alert"
74
+ style={{
75
+ padding: '2rem',
76
+ margin: '2rem auto',
77
+ maxWidth: '600px',
78
+ border: '1px solid #ef4444',
79
+ borderRadius: '0.5rem',
80
+ backgroundColor: '#fef2f2',
81
+ color: '#991b1b',
82
+ }}
83
+ >
84
+ <h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', marginBottom: '1rem' }}>
85
+ ⚠️ 오류가 발생했습니다 / An error occurred
86
+ </h2>
87
+ <details style={{ marginBottom: '1rem' }}>
88
+ <summary style={{ cursor: 'pointer', fontWeight: 500 }}>
89
+ 에러 상세 정보 / Error details
90
+ </summary>
91
+ <pre
92
+ style={{
93
+ marginTop: '0.5rem',
94
+ padding: '1rem',
95
+ backgroundColor: '#fee2e2',
96
+ borderRadius: '0.25rem',
97
+ overflow: 'auto',
98
+ fontSize: '0.875rem',
99
+ }}
100
+ >
101
+ {error.message}
102
+ </pre>
103
+ </details>
104
+ <button
105
+ onClick={reset}
106
+ style={{
107
+ padding: '0.5rem 1rem',
108
+ backgroundColor: '#dc2626',
109
+ color: 'white',
110
+ border: 'none',
111
+ borderRadius: '0.25rem',
112
+ cursor: 'pointer',
113
+ fontWeight: 500,
114
+ }}
115
+ >
116
+ 🔄 다시 시도 / Try again
117
+ </button>
118
+ </div>
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Error Boundary Component
124
+ *
125
+ * Catches JavaScript errors anywhere in the child component tree,
126
+ * logs those errors, and displays a fallback UI.
127
+ *
128
+ * @example
129
+ * ```tsx
130
+ * <ErrorBoundary fallback={<div>Something went wrong</div>}>
131
+ * <MyComponent />
132
+ * </ErrorBoundary>
133
+ * ```
134
+ *
135
+ * @example
136
+ * ```tsx
137
+ * <ErrorBoundary
138
+ * fallback={(error, reset) => (
139
+ * <div>
140
+ * <h1>Error: {error.message}</h1>
141
+ * <button onClick={reset}>Try again</button>
142
+ * </div>
143
+ * )}
144
+ * >
145
+ * <MyComponent />
146
+ * </ErrorBoundary>
147
+ * ```
148
+ */
149
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
150
+ constructor(props: ErrorBoundaryProps) {
151
+ super(props);
152
+ this.state = { hasError: false, error: null };
153
+ }
154
+
155
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
156
+ // Update state so the next render will show the fallback UI
157
+ return { hasError: true, error };
158
+ }
159
+
160
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
161
+ // 개발 모드: 콘솔에 에러 로그
162
+ // Development mode: Log error to console
163
+ if (process.env.NODE_ENV === 'development') {
164
+ console.error('[ErrorBoundary] Caught error:', error);
165
+ console.error('[ErrorBoundary] Error info:', errorInfo);
166
+ }
167
+
168
+ // 프로덕션 모드: 에러 리포팅 서비스 통합 (선택적)
169
+ // Production mode: Integrate with error reporting service (optional)
170
+ if (process.env.NODE_ENV === 'production') {
171
+ // 전역 에러 리포터가 설정되어 있으면 사용
172
+ // Use global error reporter if configured
173
+ if (typeof window !== 'undefined' && window.__ERROR_REPORTER__) {
174
+ try {
175
+ window.__ERROR_REPORTER__(error, errorInfo);
176
+ } catch (reportingError) {
177
+ // 에러 리포팅 자체가 실패해도 앱은 계속 동작해야 함
178
+ // App should continue working even if error reporting fails
179
+ console.error('[ErrorBoundary] Error reporting failed:', reportingError);
180
+ }
181
+ }
182
+ }
183
+
184
+ // Call onError callback if provided
185
+ this.props.onError?.(error, errorInfo);
186
+ }
187
+
188
+ reset = (): void => {
189
+ // Call onReset callback if provided
190
+ this.props.onReset?.();
191
+
192
+ // Reset error state
193
+ this.setState({ hasError: false, error: null });
194
+ };
195
+
196
+ render(): ReactNode {
197
+ if (this.state.hasError && this.state.error) {
198
+ // Render fallback UI
199
+ const { fallback } = this.props;
200
+
201
+ if (typeof fallback === 'function') {
202
+ return fallback(this.state.error, this.reset);
203
+ }
204
+
205
+ if (fallback) {
206
+ return fallback;
207
+ }
208
+
209
+ // Default fallback
210
+ return <DefaultFallback error={this.state.error} reset={this.reset} />;
211
+ }
212
+
213
+ return this.props.children;
214
+ }
215
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - HuaUxLayout
3
+ *
4
+ * Root layout wrapper that automatically sets up all providers
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import type { HuaUxLayoutProps } from '../types';
11
+ import { UnifiedProviders } from './Providers';
12
+
13
+ /**
14
+ * HuaUxLayout Component
15
+ *
16
+ * Automatically configures i18n, motion, and state providers based on configuration.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * // app/layout.tsx
21
+ * import { HuaUxLayout } from '@hua-labs/hua-ux/framework';
22
+ *
23
+ * export default function RootLayout({ children }) {
24
+ * return (
25
+ * <html lang="ko">
26
+ * <body>
27
+ * <HuaUxLayout>{children}</HuaUxLayout>
28
+ * </body>
29
+ * </html>
30
+ * );
31
+ * }
32
+ * ```
33
+ */
34
+ export function HuaUxLayout({ children, config }: HuaUxLayoutProps) {
35
+ return <UnifiedProviders config={config}>{children}</UnifiedProviders>;
36
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - HuaUxPage
3
+ *
4
+ * Page wrapper with automatic motion and i18n support
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import type { HuaUxPageProps } from '../types';
11
+ import { getConfig } from '../config';
12
+ import { useMotion } from '../hooks/useMotion';
13
+ import { ErrorBoundary } from './ErrorBoundary';
14
+
15
+ /**
16
+ * HuaUxPage Component
17
+ *
18
+ * 페이지 콘텐츠를 자동 모션 애니메이션과 i18n 지원으로 감싸는 컴포넌트입니다.
19
+ * Wraps page content with automatic motion animations and i18n support.
20
+ *
21
+ * **바이브 코딩 친화적 / Vibe Coding Friendly**:
22
+ * - 한 파일에서 SEO, Motion, i18n을 모두 결정할 수 있습니다.
23
+ * - You can decide SEO, Motion, and i18n all in one file.
24
+ * - AI가 파일 하나만 보고도 완벽한 페이지를 생성할 수 있습니다.
25
+ * - AI can generate a perfect page by looking at just one file.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * // app/page.tsx
30
+ * import { HuaUxPage } from '@hua-labs/hua-ux/framework';
31
+ *
32
+ * export default function HomePage() {
33
+ * return (
34
+ * <HuaUxPage
35
+ * vibe="clean"
36
+ * i18nKey="home"
37
+ * title="홈"
38
+ * description="환영합니다"
39
+ * >
40
+ * <h1>Welcome</h1>
41
+ * </HuaUxPage>
42
+ * );
43
+ * }
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * // 바이브 코더용: vibe만으로 스타일 결정
49
+ * // For vibe coders: decide style with just vibe
50
+ * <HuaUxPage vibe="fancy">
51
+ * <div>화려한 인터랙션이 자동 적용됩니다</div>
52
+ * </HuaUxPage>
53
+ * ```
54
+ */
55
+ export function HuaUxPage({
56
+ children,
57
+ title,
58
+ description,
59
+ vibe,
60
+ i18nKey,
61
+ enableMotion = true,
62
+ enableErrorBoundary = true,
63
+ errorBoundaryFallback,
64
+ seo,
65
+ motion,
66
+ }: HuaUxPageProps) {
67
+ const config = getConfig();
68
+
69
+ // vibe에 따라 모션 설정 조정
70
+ // Adjust motion settings based on vibe
71
+ const motionDuration = vibe === 'fancy' ? 800 : vibe === 'minimal' ? 300 : 600;
72
+ const shouldEnableMotion = enableMotion && config.motion?.enableAnimations !== false;
73
+
74
+ // motion prop 또는 vibe에 따라 모션 타입 결정
75
+ // Determine motion type based on motion prop or vibe
76
+ const motionType = motion || (vibe === 'fancy' ? 'slideUp' : vibe === 'minimal' ? 'fadeIn' : 'fadeIn');
77
+
78
+ // 통합 Motion Hook 사용 (성능 최적화)
79
+ // Use unified Motion Hook (performance optimization)
80
+ // Note: React Rules of Hooks를 준수하기 위해 내부적으로 모든 hook을 호출하지만,
81
+ // 실제로는 선택된 hook만 활성화되어 성능 오버헤드를 최소화합니다.
82
+ // Note: All hooks are called internally to respect React Rules of Hooks,
83
+ // but only the selected hook is actually activated to minimize performance overhead.
84
+ const motionResult = useMotion<HTMLDivElement>({
85
+ type: motionType,
86
+ duration: motionDuration,
87
+ autoStart: false, // 수동으로 start 호출
88
+ });
89
+
90
+ // Start the selected motion if enabled
91
+ // 모션이 활성화된 경우 선택된 모션 시작
92
+ React.useEffect(() => {
93
+ if (shouldEnableMotion) {
94
+ motionResult.start?.();
95
+ }
96
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
+ }, [shouldEnableMotion]); // motionResult.start는 안정적인 함수이므로 dependency에서 제외
98
+ const pageRef = motionResult.ref;
99
+
100
+ // SEO 메타데이터는 Next.js App Router에서 page.tsx의 export const metadata로 처리하는 것이 권장됩니다.
101
+ // 이 컴포넌트는 Client Component이므로 메타데이터를 직접 설정할 수 없습니다.
102
+ // SEO 설정이 있으면 개발자에게 경고 메시지 표시 (개발 모드에서만)
103
+ React.useEffect(() => {
104
+ if (process.env.NODE_ENV === 'development' && seo) {
105
+ console.warn(
106
+ '[HuaUxPage] SEO metadata should be set using `export const metadata` in page.tsx.\n' +
107
+ 'Use `generatePageMetadata()` helper function from @hua-labs/hua-ux/framework'
108
+ );
109
+ }
110
+ }, [seo]);
111
+
112
+ // i18nKey가 있으면 해당 네임스페이스를 자동으로 로드하도록 안내
113
+ // (실제 로드는 I18nProvider에서 자동 처리됨)
114
+ React.useEffect(() => {
115
+ if (process.env.NODE_ENV === 'development' && i18nKey) {
116
+ // i18nKey가 설정되어 있으면 해당 네임스페이스가 자동으로 로드됩니다.
117
+ // 번역 키는 `${i18nKey}:title`, `${i18nKey}:description` 형식으로 사용할 수 있습니다.
118
+ // Translation keys can be used in the format `${i18nKey}:title`, `${i18nKey}:description`
119
+ }
120
+ }, [i18nKey]);
121
+
122
+ const pageContent = (
123
+ <div ref={pageRef} style={motionResult.style}>
124
+ {children}
125
+ </div>
126
+ );
127
+
128
+ // Wrap with ErrorBoundary if enabled
129
+ if (enableErrorBoundary) {
130
+ return (
131
+ <ErrorBoundary fallback={errorBoundaryFallback}>
132
+ {pageContent}
133
+ </ErrorBoundary>
134
+ );
135
+ }
136
+
137
+ return pageContent;
138
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - Providers
3
+ *
4
+ * Unified providers for i18n, motion, and state
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import type { ReactNode } from 'react';
11
+ import type { HuaUxConfig } from '../types';
12
+ import { getConfig } from '../config';
13
+ import { createZustandI18n } from '@hua-labs/i18n-core-zustand';
14
+ import { createI18nStore } from '@hua-labs/state';
15
+ import { BrandingProvider } from '../branding/context';
16
+
17
+ /**
18
+ * Create providers based on configuration
19
+ *
20
+ * Provider 체인을 생성합니다. Branding Provider는 가장 바깥쪽에 위치하여
21
+ * 다른 Provider들이 브랜딩 설정을 사용할 수 있도록 합니다.
22
+ *
23
+ * Creates a provider chain. Branding Provider is placed at the outermost level
24
+ * so other providers can use branding configuration.
25
+ */
26
+ function createProviders(config: HuaUxConfig) {
27
+ const providers: React.ComponentType<{ children: ReactNode }>[] = [];
28
+
29
+ // Branding Provider를 먼저 추가 (다른 Provider들이 사용 가능하도록)
30
+ // Add Branding Provider first (so other providers can use it)
31
+ if (config.branding) {
32
+ providers.push(({ children }) => (
33
+ <BrandingProvider branding={config.branding || null}>{children}</BrandingProvider>
34
+ ));
35
+ }
36
+
37
+ // i18n Provider
38
+ if (config.i18n) {
39
+ const i18nStore = createI18nStore({
40
+ defaultLanguage: config.i18n.defaultLanguage,
41
+ supportedLanguages: config.i18n.supportedLanguages,
42
+ persist: config.state?.persist ?? true,
43
+ ssr: config.state?.ssr ?? true,
44
+ });
45
+
46
+ // Type assertion: I18nStoreState extends ZustandLanguageStore, so this is safe
47
+ const I18nProvider = createZustandI18n(
48
+ i18nStore as any, // Type assertion needed due to Zustand's complex type system
49
+ {
50
+ fallbackLanguage: config.i18n.fallbackLanguage || 'en',
51
+ namespaces: config.i18n.namespaces || ['common'],
52
+ translationLoader: config.i18n.translationLoader || 'api',
53
+ translationApiPath: config.i18n.translationApiPath || '/api/translations',
54
+ defaultLanguage: config.i18n.defaultLanguage,
55
+ loadTranslations: config.i18n.loadTranslations,
56
+ debug: config.i18n.debug,
57
+ }
58
+ );
59
+
60
+ providers.push(I18nProvider);
61
+ }
62
+
63
+ // Return a combined provider
64
+ return function CombinedProvider({ children }: { children: ReactNode }) {
65
+ // Provider 체인 생성 (Branding Provider가 가장 바깥쪽)
66
+ // Create provider chain (Branding Provider is outermost)
67
+ const wrapped = providers.reduceRight(
68
+ (acc, Provider) => <Provider>{acc}</Provider>,
69
+ children
70
+ );
71
+
72
+ return wrapped;
73
+ };
74
+ }
75
+
76
+ /**
77
+ * UnifiedProviders component
78
+ *
79
+ * Automatically creates and wraps children with all necessary providers
80
+ */
81
+ export function UnifiedProviders({
82
+ children,
83
+ config: overrideConfig,
84
+ }: {
85
+ children: ReactNode;
86
+ config?: Partial<HuaUxConfig>;
87
+ }) {
88
+ const baseConfig = getConfig();
89
+ const config = overrideConfig
90
+ ? { ...baseConfig, ...overrideConfig }
91
+ : baseConfig;
92
+
93
+ const Provider = React.useMemo(() => createProviders(config), [config]);
94
+
95
+ // Provider 체인은 createProviders 내부에서 처리됨
96
+ // Provider chain is handled inside createProviders
97
+ return <Provider>{children}</Provider>;
98
+ }