@hua-labs/hua-ux 0.1.0-alpha.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +839 -0
- package/dist/framework/a11y/components/LiveRegion.d.ts +64 -0
- package/dist/framework/a11y/components/LiveRegion.d.ts.map +1 -0
- package/dist/framework/a11y/components/LiveRegion.js +43 -0
- package/dist/framework/a11y/components/SkipToContent.d.ts +62 -0
- package/dist/framework/a11y/components/SkipToContent.d.ts.map +1 -0
- package/dist/framework/a11y/components/SkipToContent.js +60 -0
- package/dist/framework/a11y/hooks/useFocusManagement.d.ts +60 -0
- package/dist/framework/a11y/hooks/useFocusManagement.d.ts.map +1 -0
- package/dist/framework/a11y/hooks/useFocusManagement.js +71 -0
- package/dist/framework/a11y/hooks/useFocusTrap.d.ts +64 -0
- package/dist/framework/a11y/hooks/useFocusTrap.d.ts.map +1 -0
- package/dist/framework/a11y/hooks/useFocusTrap.js +185 -0
- package/dist/framework/a11y/hooks/useLiveRegion.d.ts +56 -0
- package/dist/framework/a11y/hooks/useLiveRegion.d.ts.map +1 -0
- package/dist/framework/a11y/hooks/useLiveRegion.js +60 -0
- package/dist/framework/a11y/index.d.ts +16 -0
- package/dist/framework/a11y/index.d.ts.map +1 -0
- package/dist/framework/a11y/index.js +11 -0
- package/dist/framework/branding/context.d.ts +52 -0
- package/dist/framework/branding/context.d.ts.map +1 -0
- package/dist/framework/branding/context.js +96 -0
- package/dist/framework/branding/css-vars.d.ts +34 -0
- package/dist/framework/branding/css-vars.d.ts.map +1 -0
- package/dist/framework/branding/css-vars.js +95 -0
- package/dist/framework/branding/tailwind-config.d.ts +38 -0
- package/dist/framework/branding/tailwind-config.d.ts.map +1 -0
- package/dist/framework/branding/tailwind-config.js +66 -0
- package/dist/framework/components/BrandedButton.d.ts +53 -0
- package/dist/framework/components/BrandedButton.d.ts.map +1 -0
- package/dist/framework/components/BrandedButton.js +40 -0
- package/dist/framework/components/BrandedCard.d.ts +52 -0
- package/dist/framework/components/BrandedCard.d.ts.map +1 -0
- package/dist/framework/components/BrandedCard.js +73 -0
- package/dist/framework/components/ErrorBoundary.d.ts +92 -0
- package/dist/framework/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/framework/components/ErrorBoundary.js +121 -0
- package/dist/framework/components/HuaUxLayout.d.ts +29 -0
- package/dist/framework/components/HuaUxLayout.d.ts.map +1 -0
- package/dist/framework/components/HuaUxLayout.js +32 -0
- package/dist/framework/components/HuaUxPage.d.ts +48 -0
- package/dist/framework/components/HuaUxPage.d.ts.map +1 -0
- package/dist/framework/components/HuaUxPage.js +105 -0
- package/dist/framework/components/Providers.d.ts +17 -0
- package/dist/framework/components/Providers.d.ts.map +1 -0
- package/dist/framework/components/Providers.js +72 -0
- package/dist/framework/components/WelcomePage.d.ts +44 -0
- package/dist/framework/components/WelcomePage.d.ts.map +1 -0
- package/dist/framework/components/WelcomePage.js +80 -0
- package/dist/framework/config/index.d.ts +182 -0
- package/dist/framework/config/index.d.ts.map +1 -0
- package/dist/framework/config/index.js +329 -0
- package/dist/framework/config/merge.d.ts +26 -0
- package/dist/framework/config/merge.d.ts.map +1 -0
- package/dist/framework/config/merge.js +160 -0
- package/dist/framework/config/schema.d.ts +25 -0
- package/dist/framework/config/schema.d.ts.map +1 -0
- package/dist/framework/config/schema.js +122 -0
- package/dist/framework/hooks/useMotion.d.ts +45 -0
- package/dist/framework/hooks/useMotion.d.ts.map +1 -0
- package/dist/framework/hooks/useMotion.js +40 -0
- package/dist/framework/index.d.ts +37 -0
- package/dist/framework/index.d.ts.map +1 -0
- package/dist/framework/index.js +42 -0
- package/dist/framework/license/errors.d.ts +15 -0
- package/dist/framework/license/errors.d.ts.map +1 -0
- package/dist/framework/license/errors.js +52 -0
- package/dist/framework/license/index.d.ts +70 -0
- package/dist/framework/license/index.d.ts.map +1 -0
- package/dist/framework/license/index.js +124 -0
- package/dist/framework/license/loader.d.ts +26 -0
- package/dist/framework/license/loader.d.ts.map +1 -0
- package/dist/framework/license/loader.js +137 -0
- package/dist/framework/license/types.d.ts +67 -0
- package/dist/framework/license/types.d.ts.map +1 -0
- package/dist/framework/license/types.js +18 -0
- package/dist/framework/loading/components/SkeletonGroup.d.ts +44 -0
- package/dist/framework/loading/components/SkeletonGroup.d.ts.map +1 -0
- package/dist/framework/loading/components/SkeletonGroup.js +34 -0
- package/dist/framework/loading/components/SuspenseWrapper.d.ts +58 -0
- package/dist/framework/loading/components/SuspenseWrapper.d.ts.map +1 -0
- package/dist/framework/loading/components/SuspenseWrapper.js +40 -0
- package/dist/framework/loading/hoc/withSuspense.d.ts +46 -0
- package/dist/framework/loading/hoc/withSuspense.d.ts.map +1 -0
- package/dist/framework/loading/hoc/withSuspense.js +54 -0
- package/dist/framework/loading/hooks/useDelayedLoading.d.ts +56 -0
- package/dist/framework/loading/hooks/useDelayedLoading.d.ts.map +1 -0
- package/dist/framework/loading/hooks/useDelayedLoading.js +97 -0
- package/dist/framework/loading/hooks/useLoadingState.d.ts +69 -0
- package/dist/framework/loading/hooks/useLoadingState.d.ts.map +1 -0
- package/dist/framework/loading/hooks/useLoadingState.js +59 -0
- package/dist/framework/loading/index.d.ts +16 -0
- package/dist/framework/loading/index.d.ts.map +1 -0
- package/dist/framework/loading/index.js +13 -0
- package/dist/framework/middleware/i18n.d.ts +90 -0
- package/dist/framework/middleware/i18n.d.ts.map +1 -0
- package/dist/framework/middleware/i18n.js +99 -0
- package/dist/framework/plugins/index.d.ts +8 -0
- package/dist/framework/plugins/index.d.ts.map +1 -0
- package/dist/framework/plugins/index.js +6 -0
- package/dist/framework/plugins/registry.d.ts +95 -0
- package/dist/framework/plugins/registry.d.ts.map +1 -0
- package/dist/framework/plugins/registry.js +160 -0
- package/dist/framework/plugins/types.d.ts +97 -0
- package/dist/framework/plugins/types.d.ts.map +1 -0
- package/dist/framework/plugins/types.js +6 -0
- package/dist/framework/seo/geo/examples.d.ts +87 -0
- package/dist/framework/seo/geo/examples.d.ts.map +1 -0
- package/dist/framework/seo/geo/examples.js +295 -0
- package/dist/framework/seo/geo/generateGEOMetadata.d.ts +107 -0
- package/dist/framework/seo/geo/generateGEOMetadata.d.ts.map +1 -0
- package/dist/framework/seo/geo/generateGEOMetadata.js +404 -0
- package/dist/framework/seo/geo/index.d.ts +19 -0
- package/dist/framework/seo/geo/index.d.ts.map +1 -0
- package/dist/framework/seo/geo/index.js +21 -0
- package/dist/framework/seo/geo/presets.d.ts +52 -0
- package/dist/framework/seo/geo/presets.d.ts.map +1 -0
- package/dist/framework/seo/geo/presets.js +47 -0
- package/dist/framework/seo/geo/structuredData.d.ts +187 -0
- package/dist/framework/seo/geo/structuredData.d.ts.map +1 -0
- package/dist/framework/seo/geo/structuredData.js +354 -0
- package/dist/framework/seo/geo/test-utils.d.ts +78 -0
- package/dist/framework/seo/geo/test-utils.d.ts.map +1 -0
- package/dist/framework/seo/geo/test-utils.js +139 -0
- package/dist/framework/seo/geo/types.d.ts +225 -0
- package/dist/framework/seo/geo/types.d.ts.map +1 -0
- package/dist/framework/seo/geo/types.js +51 -0
- package/dist/framework/types/index.d.ts +577 -0
- package/dist/framework/types/index.d.ts.map +1 -0
- package/dist/framework/types/index.js +6 -0
- package/dist/framework/utils/data-fetching.d.ts +45 -0
- package/dist/framework/utils/data-fetching.d.ts.map +1 -0
- package/dist/framework/utils/data-fetching.js +74 -0
- package/dist/framework/utils/file-structure.d.ts +29 -0
- package/dist/framework/utils/file-structure.d.ts.map +1 -0
- package/dist/framework/utils/file-structure.js +72 -0
- package/dist/framework/utils/metadata.d.ts +109 -0
- package/dist/framework/utils/metadata.d.ts.map +1 -0
- package/dist/framework/utils/metadata.js +105 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/presets/index.d.ts +8 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +7 -0
- package/dist/presets/marketing.d.ts +41 -0
- package/dist/presets/marketing.d.ts.map +1 -0
- package/dist/presets/marketing.js +81 -0
- package/dist/presets/product.d.ts +41 -0
- package/dist/presets/product.d.ts.map +1 -0
- package/dist/presets/product.js +74 -0
- package/package.json +91 -0
- package/src/framework/README.md +329 -0
- package/src/framework/__tests__/branding/css-vars.test.ts +147 -0
- package/src/framework/__tests__/components/ErrorBoundary.test.tsx +146 -0
- package/src/framework/__tests__/config/defineConfig.test.ts +138 -0
- package/src/framework/__tests__/hooks/useMotion.test.ts +105 -0
- package/src/framework/__tests__/seo/geo/generateGEOMetadata.test.ts +207 -0
- package/src/framework/__tests__/seo/geo/structuredData.test.ts +262 -0
- package/src/framework/a11y/components/LiveRegion.tsx +89 -0
- package/src/framework/a11y/components/SkipToContent.tsx +103 -0
- package/src/framework/a11y/hooks/useFocusManagement.ts +125 -0
- package/src/framework/a11y/hooks/useFocusTrap.ts +239 -0
- package/src/framework/a11y/hooks/useLiveRegion.ts +95 -0
- package/src/framework/a11y/index.ts +17 -0
- package/src/framework/branding/context.tsx +135 -0
- package/src/framework/branding/css-vars.ts +110 -0
- package/src/framework/branding/tailwind-config.ts +90 -0
- package/src/framework/components/BrandedButton.tsx +94 -0
- package/src/framework/components/BrandedCard.tsx +87 -0
- package/src/framework/components/ErrorBoundary.tsx +215 -0
- package/src/framework/components/HuaUxLayout.tsx +36 -0
- package/src/framework/components/HuaUxPage.tsx +138 -0
- package/src/framework/components/Providers.tsx +98 -0
- package/src/framework/components/WelcomePage.tsx +207 -0
- package/src/framework/config/index.ts +349 -0
- package/src/framework/config/merge.ts +190 -0
- package/src/framework/config/schema.ts +140 -0
- package/src/framework/hooks/useMotion.ts +57 -0
- package/src/framework/index.ts +122 -0
- package/src/framework/license/errors.ts +63 -0
- package/src/framework/license/index.ts +137 -0
- package/src/framework/license/loader.ts +158 -0
- package/src/framework/license/types.ts +95 -0
- package/src/framework/loading/components/SkeletonGroup.tsx +70 -0
- package/src/framework/loading/components/SuspenseWrapper.tsx +88 -0
- package/src/framework/loading/hoc/withSuspense.tsx +96 -0
- package/src/framework/loading/hooks/useDelayedLoading.ts +127 -0
- package/src/framework/loading/hooks/useLoadingState.ts +103 -0
- package/src/framework/loading/index.ts +19 -0
- package/src/framework/middleware/i18n.ts +161 -0
- package/src/framework/middleware/index.ts +7 -0
- package/src/framework/plugins/index.ts +13 -0
- package/src/framework/plugins/registry.ts +186 -0
- package/src/framework/plugins/types.ts +106 -0
- package/src/framework/seo/geo/examples.tsx +415 -0
- package/src/framework/seo/geo/generateGEOMetadata.ts +441 -0
- package/src/framework/seo/geo/index.ts +61 -0
- package/src/framework/seo/geo/presets.ts +58 -0
- package/src/framework/seo/geo/structuredData.ts +422 -0
- package/src/framework/seo/geo/test-utils.ts +179 -0
- package/src/framework/seo/geo/types.ts +315 -0
- package/src/framework/types/index.ts +623 -0
- package/src/framework/utils/data-fetching.ts +95 -0
- package/src/framework/utils/file-structure.ts +88 -0
- package/src/framework/utils/metadata.ts +152 -0
- package/src/index.ts +31 -0
- package/src/presets/index.ts +8 -0
- package/src/presets/marketing.ts +88 -0
- package/src/presets/product.ts +81 -0
|
@@ -0,0 +1,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
|
+
}
|