@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
package/README.md
ADDED
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
# @hua-labs/hua-ux
|
|
2
|
+
|
|
3
|
+
**Ship UX faster**: UI + motion + i18n, pre-wired.
|
|
4
|
+
|
|
5
|
+
A framework for React product teams that provides pre-wired UX defaults for spacing, components, motion, and internationalization.
|
|
6
|
+
|
|
7
|
+
## 왜 hua-ux인가?
|
|
8
|
+
|
|
9
|
+
프로덕트 팀이 매번 UI 컴포넌트, 모션 라이브러리, i18n 설정을 처음부터 구성하는 것은 시간 낭비입니다. **hua-ux**는 이 세 가지를 하나의 패키지로 통합하여, 5분 안에 프로덕트에 바로 적용할 수 있도록 설계되었습니다.
|
|
10
|
+
|
|
11
|
+
**핵심 가치:**
|
|
12
|
+
- ✅ **가볍고 바로 붙는다**: Framer Motion 대비 가볍고, Next.js에 바로 통합 가능
|
|
13
|
+
- ✅ **타입 안전**: TypeScript로 모든 것이 타입 안전하게 제공
|
|
14
|
+
- ✅ **SSR 지원**: Next.js App Router와 완벽하게 작동
|
|
15
|
+
- ✅ **통합 경험**: UI, Motion, i18n이 하나의 생태계에서 작동
|
|
16
|
+
- ✅ **에러 처리 자동화**: ErrorBoundary가 HuaUxPage에 기본 내장
|
|
17
|
+
- ✅ **접근성 우선**: WCAG 2.1 준수, 스크린 리더 지원, 키보드 탐색 최적화 (useFocusManagement, useFocusTrap, SkipToContent, LiveRegion)
|
|
18
|
+
- ✅ **로딩 UX 최적화**: 깜빡임 방지, Skeleton UI, Suspense 자동화 (useDelayedLoading, useLoadingState, SuspenseWrapper)
|
|
19
|
+
|
|
20
|
+
## 5분 시작
|
|
21
|
+
|
|
22
|
+
### 1. 설치
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @hua-labs/hua-ux zustand
|
|
26
|
+
# or
|
|
27
|
+
npm install @hua-labs/hua-ux zustand
|
|
28
|
+
# or
|
|
29
|
+
yarn add @hua-labs/hua-ux zustand
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. 기본 설정
|
|
33
|
+
|
|
34
|
+
**두 가지 사용 방법이 있습니다:**
|
|
35
|
+
|
|
36
|
+
#### 방법 1: 프레임워크 레이어 사용 (권장) ⭐
|
|
37
|
+
|
|
38
|
+
프레임워크 레이어를 사용하면 자동으로 모든 Provider가 설정됩니다:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// hua-ux.config.ts
|
|
42
|
+
import { defineConfig } from '@hua-labs/hua-ux/framework';
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
preset: 'product',
|
|
46
|
+
i18n: {
|
|
47
|
+
defaultLanguage: 'ko',
|
|
48
|
+
supportedLanguages: ['ko', 'en'],
|
|
49
|
+
namespaces: ['common'],
|
|
50
|
+
translationLoader: 'api',
|
|
51
|
+
translationApiPath: '/api/translations',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// app/layout.tsx
|
|
58
|
+
import { HuaUxLayout } from '@hua-labs/hua-ux/framework';
|
|
59
|
+
|
|
60
|
+
export default function RootLayout({ children }) {
|
|
61
|
+
return (
|
|
62
|
+
<html lang="ko">
|
|
63
|
+
<body>
|
|
64
|
+
<HuaUxLayout>{children}</HuaUxLayout>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**장점**: 설정 파일만으로 모든 Provider 자동 설정, 간단함
|
|
72
|
+
|
|
73
|
+
#### 방법 2: 직접 사용 (세밀한 제어)
|
|
74
|
+
|
|
75
|
+
더 세밀한 제어가 필요한 경우 직접 설정할 수 있습니다:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// store/useAppStore.ts
|
|
79
|
+
import { create } from 'zustand';
|
|
80
|
+
import { persist } from 'zustand/middleware';
|
|
81
|
+
|
|
82
|
+
interface AppState {
|
|
83
|
+
language: 'ko' | 'en';
|
|
84
|
+
setLanguage: (lang: 'ko' | 'en') => void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const useAppStore = create<AppState>()(
|
|
88
|
+
persist(
|
|
89
|
+
(set) => ({
|
|
90
|
+
language: 'ko',
|
|
91
|
+
setLanguage: (lang) => set({ language: lang }),
|
|
92
|
+
}),
|
|
93
|
+
{
|
|
94
|
+
name: 'app-storage',
|
|
95
|
+
partialize: (state) => ({ language: state.language }),
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// lib/i18n-setup.ts
|
|
103
|
+
import { createZustandI18n } from '@hua-labs/i18n-core-zustand';
|
|
104
|
+
import { createI18nStore } from '@hua-labs/state';
|
|
105
|
+
import { useAppStore } from '../store/useAppStore';
|
|
106
|
+
|
|
107
|
+
// createI18nStore로 언어 상태 관리 스토어 생성
|
|
108
|
+
const i18nStore = createI18nStore({
|
|
109
|
+
defaultLanguage: 'ko',
|
|
110
|
+
supportedLanguages: ['ko', 'en'],
|
|
111
|
+
persist: true,
|
|
112
|
+
ssr: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// createZustandI18n으로 i18n Provider 생성
|
|
116
|
+
export const I18nProvider = createZustandI18n(i18nStore, {
|
|
117
|
+
fallbackLanguage: 'en',
|
|
118
|
+
namespaces: ['common'],
|
|
119
|
+
translationLoader: 'api',
|
|
120
|
+
translationApiPath: '/api/translations',
|
|
121
|
+
defaultLanguage: 'ko',
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
// app/layout.tsx
|
|
127
|
+
import { I18nProvider } from './lib/i18n-setup';
|
|
128
|
+
|
|
129
|
+
export default function RootLayout({ children }) {
|
|
130
|
+
return (
|
|
131
|
+
<html lang="ko">
|
|
132
|
+
<body>
|
|
133
|
+
<I18nProvider>{children}</I18nProvider>
|
|
134
|
+
</body>
|
|
135
|
+
</html>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**장점**: 세밀한 제어 가능, 커스텀 설정 용이
|
|
141
|
+
|
|
142
|
+
**언제 사용하나요?**
|
|
143
|
+
- **프레임워크 레이어**: 빠른 시작, 표준 설정으로 충분한 경우
|
|
144
|
+
- **직접 사용**: 커스텀 Provider 조합, 특수한 요구사항이 있는 경우
|
|
145
|
+
|
|
146
|
+
### 3. 사용하기
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
// app/page.tsx
|
|
150
|
+
'use client';
|
|
151
|
+
|
|
152
|
+
import { Button, Card } from '@hua-labs/hua-ux';
|
|
153
|
+
import { useFadeIn, useSlideUp } from '@hua-labs/hua-ux';
|
|
154
|
+
import { useTranslation } from '@hua-labs/hua-ux';
|
|
155
|
+
|
|
156
|
+
export default function HomePage() {
|
|
157
|
+
const { t } = useTranslation();
|
|
158
|
+
const fadeInRef = useFadeIn();
|
|
159
|
+
const slideUpRef = useSlideUp();
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div>
|
|
163
|
+
<Card ref={fadeInRef}>
|
|
164
|
+
<h1>{t('common:welcome')}</h1>
|
|
165
|
+
<Button ref={slideUpRef}>Get Started</Button>
|
|
166
|
+
</Card>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Showcase
|
|
173
|
+
|
|
174
|
+
라이브 데모를 확인하세요:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
cd apps/hua-ux-showcase
|
|
178
|
+
pnpm install
|
|
179
|
+
pnpm dev
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Showcase 페이지**:
|
|
183
|
+
- `/` - 홈 (3개 Showcase 링크)
|
|
184
|
+
- `/ui` - UI 컴포넌트 데모
|
|
185
|
+
- `/motion` - Motion 훅 데모
|
|
186
|
+
- `/i18n` - 다국어 지원 데모
|
|
187
|
+
|
|
188
|
+
또는 [Showcase App 소스 코드](../../apps/hua-ux-showcase)를 참고하세요.
|
|
189
|
+
|
|
190
|
+
## 프로젝트 생성
|
|
191
|
+
|
|
192
|
+
스캐폴딩 도구를 사용하여 새 프로젝트를 생성할 수 있습니다:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pnpm create hua-ux my-app
|
|
196
|
+
cd my-app
|
|
197
|
+
pnpm install
|
|
198
|
+
pnpm dev
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
자세한 내용은 [create-hua-ux README](../create-hua-ux/README.md)를 참고하세요.
|
|
202
|
+
|
|
203
|
+
## 패키지 구조
|
|
204
|
+
|
|
205
|
+
**hua-ux**는 다음 패키지들을 통합합니다:
|
|
206
|
+
|
|
207
|
+
- **`@hua-labs/ui`** - UI 컴포넌트 라이브러리
|
|
208
|
+
- Button, Card, Input, Modal 등 50+ 컴포넌트
|
|
209
|
+
- 일관된 스타일링 시스템
|
|
210
|
+
- 다크 모드 지원
|
|
211
|
+
|
|
212
|
+
- **`@hua-labs/motion-core`** - Motion 훅 라이브러리
|
|
213
|
+
- `useFadeIn`, `useSlideUp`, `useScaleIn` 등 기본 모션
|
|
214
|
+
- `useHoverMotion`, `useScrollReveal` 등 인터랙션
|
|
215
|
+
- 프리셋 시스템으로 빠른 설정
|
|
216
|
+
|
|
217
|
+
- **`@hua-labs/i18n-core`** - i18n 핵심 기능
|
|
218
|
+
- 타입 안전한 번역 시스템
|
|
219
|
+
- SSR/CSR 지원
|
|
220
|
+
- 네임스페이스 기반 번역 관리
|
|
221
|
+
|
|
222
|
+
- **`@hua-labs/i18n-core-zustand`** - Zustand 어댑터
|
|
223
|
+
- Zustand와 완벽한 통합
|
|
224
|
+
- 하이드레이션 에러 방지
|
|
225
|
+
- 언어 상태 자동 동기화
|
|
226
|
+
|
|
227
|
+
- **`@hua-labs/state`** - 통합 상태관리 (프레임워크 전용)
|
|
228
|
+
- Zustand 기반 상태관리
|
|
229
|
+
- SSR/Persistence 지원
|
|
230
|
+
- i18n 통합 스토어 제공
|
|
231
|
+
|
|
232
|
+
## 서브패키지
|
|
233
|
+
|
|
234
|
+
### `@hua-labs/hua-ux/framework`
|
|
235
|
+
|
|
236
|
+
프레임워크 레이어 - Next.js를 감싸서 구조와 규칙을 강제하는 레이어
|
|
237
|
+
|
|
238
|
+
**주요 기능**:
|
|
239
|
+
- `HuaUxLayout`: 자동 프로바이더 설정
|
|
240
|
+
- `HuaUxPage`: 페이지 래퍼 (자동 모션)
|
|
241
|
+
- `defineConfig`: 타입 안전한 설정 시스템
|
|
242
|
+
- `useData`, `fetchData`: 데이터 페칭 유틸리티
|
|
243
|
+
- `createI18nMiddleware`: i18n 미들웨어 (Edge Runtime)
|
|
244
|
+
|
|
245
|
+
자세한 내용은 [프레임워크 레이어 문서](./src/framework/README.md)를 참고하세요.
|
|
246
|
+
|
|
247
|
+
### `@hua-labs/hua-ux/presets`
|
|
248
|
+
|
|
249
|
+
사전 구성된 Presets
|
|
250
|
+
|
|
251
|
+
**제공되는 Presets**:
|
|
252
|
+
- `productPreset`: 제품 페이지용 (빠른 전환, 최소 딜레이)
|
|
253
|
+
- `marketingPreset`: 랜딩 페이지용 (드라마틱한 모션, 긴 딜레이)
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { productPreset, marketingPreset } from '@hua-labs/hua-ux/presets';
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 프레임워크 레이어 사용하기
|
|
260
|
+
|
|
261
|
+
프레임워크 레이어를 사용하면 더 간단하게 설정할 수 있습니다:
|
|
262
|
+
|
|
263
|
+
### 1. 설정 파일 생성
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// hua-ux.config.ts
|
|
267
|
+
import { defineConfig } from '@hua-labs/hua-ux/framework';
|
|
268
|
+
|
|
269
|
+
export default defineConfig({
|
|
270
|
+
i18n: {
|
|
271
|
+
defaultLanguage: 'ko',
|
|
272
|
+
supportedLanguages: ['ko', 'en'],
|
|
273
|
+
namespaces: ['common'],
|
|
274
|
+
translationLoader: 'api',
|
|
275
|
+
translationApiPath: '/api/translations',
|
|
276
|
+
},
|
|
277
|
+
motion: {
|
|
278
|
+
defaultPreset: 'product',
|
|
279
|
+
enableAnimations: true,
|
|
280
|
+
},
|
|
281
|
+
state: {
|
|
282
|
+
persist: true,
|
|
283
|
+
ssr: true,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**타입 안전성을 위한 명시적 import (권장)**:
|
|
289
|
+
|
|
290
|
+
프로덕션 환경에서는 설정 파일을 명시적으로 import하여 타입 안전성을 보장하는 것을 권장합니다:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// app/layout.tsx 또는 초기화 파일
|
|
294
|
+
import config from '../hua-ux.config';
|
|
295
|
+
import { setConfig } from '@hua-labs/hua-ux/framework';
|
|
296
|
+
|
|
297
|
+
// 설정을 명시적으로 로드 (타입 안전성 보장)
|
|
298
|
+
setConfig(config);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
이 방법을 사용하면:
|
|
302
|
+
- ✅ 타입 안전성 보장
|
|
303
|
+
- ✅ Next.js 빌드 경고 방지
|
|
304
|
+
- ✅ 런타임 에러 방지
|
|
305
|
+
|
|
306
|
+
### 2. Layout 설정
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
// app/layout.tsx
|
|
310
|
+
import { HuaUxLayout } from '@hua-labs/hua-ux/framework';
|
|
311
|
+
|
|
312
|
+
export default function RootLayout({ children }) {
|
|
313
|
+
return (
|
|
314
|
+
<html lang="ko">
|
|
315
|
+
<body>
|
|
316
|
+
<HuaUxLayout>{children}</HuaUxLayout>
|
|
317
|
+
</body>
|
|
318
|
+
</html>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 3. 페이지 사용
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
// app/page.tsx
|
|
327
|
+
import { HuaUxPage } from '@hua-labs/hua-ux/framework';
|
|
328
|
+
|
|
329
|
+
export default function HomePage() {
|
|
330
|
+
return (
|
|
331
|
+
<HuaUxPage title="Home" description="Welcome page">
|
|
332
|
+
<h1>Welcome</h1>
|
|
333
|
+
</HuaUxPage>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
자세한 내용은 [프레임워크 레이어 문서](./src/framework/README.md)를 참고하세요.
|
|
339
|
+
|
|
340
|
+
## 주요 기능
|
|
341
|
+
|
|
342
|
+
### 🎯 통합 Motion Hook (성능 최적화)
|
|
343
|
+
|
|
344
|
+
**useMotion Hook** - 모든 motion hook을 통합하여 코드 가독성 및 유지보수성 향상:
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
import { useMotion } from '@hua-labs/hua-ux/framework';
|
|
348
|
+
|
|
349
|
+
const motion = useMotion({
|
|
350
|
+
type: 'fadeIn',
|
|
351
|
+
duration: 600,
|
|
352
|
+
autoStart: false,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return <div ref={motion.ref} style={motion.style}>Content</div>;
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**HuaUxPage에서 자동 사용** - 별도 설정 없이 자동으로 최적화된 motion 적용됩니다.
|
|
359
|
+
|
|
360
|
+
### 🛡️ ErrorBoundary (에러 처리 자동화)
|
|
361
|
+
|
|
362
|
+
**HuaUxPage에 기본 내장** - 별도 설정 없이 모든 페이지에서 에러를 자동으로 캐치합니다.
|
|
363
|
+
|
|
364
|
+
**프로덕션 에러 리포팅 지원** - Sentry, LogRocket 등과 통합 가능:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
// 프로덕션 환경에서 에러 리포팅 설정
|
|
368
|
+
window.__ERROR_REPORTER__ = (error, errorInfo) => {
|
|
369
|
+
Sentry.captureException(error, {
|
|
370
|
+
contexts: { react: errorInfo },
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
// 자동으로 ErrorBoundary가 적용됩니다
|
|
377
|
+
<HuaUxPage title="Home">
|
|
378
|
+
<MyComponent /> {/* 에러 발생 시 fallback UI 표시 */}
|
|
379
|
+
</HuaUxPage>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**커스텀 fallback UI**:
|
|
383
|
+
```tsx
|
|
384
|
+
<HuaUxPage
|
|
385
|
+
title="Home"
|
|
386
|
+
errorBoundaryFallback={(error, reset) => (
|
|
387
|
+
<div>
|
|
388
|
+
<h1>에러: {error.message}</h1>
|
|
389
|
+
<button onClick={reset}>다시 시도</button>
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
>
|
|
393
|
+
<MyComponent />
|
|
394
|
+
</HuaUxPage>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**독립적으로 사용** (HuaUxPage 외부):
|
|
398
|
+
```tsx
|
|
399
|
+
import { ErrorBoundary } from '@hua-labs/hua-ux/framework';
|
|
400
|
+
|
|
401
|
+
<ErrorBoundary>
|
|
402
|
+
<MyComponent />
|
|
403
|
+
</ErrorBoundary>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 🎨 브랜딩 (White Labeling)
|
|
407
|
+
|
|
408
|
+
**SSR 지원 CSS 변수 주입** - 서버 사이드에서도 브랜딩 CSS 변수가 즉시 적용되어 FOUC를 방지합니다:
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
// hua-ux.config.ts
|
|
412
|
+
export default defineConfig({
|
|
413
|
+
branding: {
|
|
414
|
+
colors: {
|
|
415
|
+
primary: '#3B82F6',
|
|
416
|
+
secondary: '#8B5CF6',
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
브랜딩 설정을 하면 모든 컴포넌트에 자동으로 적용됩니다.
|
|
423
|
+
|
|
424
|
+
### 🤖 GEO (Generative Engine Optimization)
|
|
425
|
+
|
|
426
|
+
**AI 검색 엔진 최적화** - ChatGPT, Claude, Gemini, Perplexity가 당신의 소프트웨어를 잘 찾고 추천하도록 최적화:
|
|
427
|
+
|
|
428
|
+
#### 기본 사용법
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
import { generateGEOMetadata, renderJSONLD } from '@hua-labs/hua-ux/framework';
|
|
432
|
+
import Script from 'next/script';
|
|
433
|
+
|
|
434
|
+
// GEO 메타데이터 생성
|
|
435
|
+
const geoMeta = generateGEOMetadata({
|
|
436
|
+
name: 'My App',
|
|
437
|
+
description: 'Built with hua-ux framework',
|
|
438
|
+
version: '1.0.0',
|
|
439
|
+
applicationCategory: ['UX Framework', 'Developer Tool'],
|
|
440
|
+
programmingLanguage: ['TypeScript', 'React', 'Next.js'],
|
|
441
|
+
features: ['i18n', 'Motion', 'Accessibility'],
|
|
442
|
+
useCases: ['Multilingual apps', 'Accessible UX'],
|
|
443
|
+
keywords: ['nextjs', 'react', 'ux', 'i18n'],
|
|
444
|
+
codeRepository: 'https://github.com/your-org/your-app',
|
|
445
|
+
license: 'MIT',
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Next.js metadata와 통합
|
|
449
|
+
export const metadata = {
|
|
450
|
+
title: 'My App',
|
|
451
|
+
description: geoMeta.meta.find(m => m.name === 'description')?.content,
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// JSON-LD 추가
|
|
455
|
+
export default function Page() {
|
|
456
|
+
return (
|
|
457
|
+
<>
|
|
458
|
+
<Script {...renderJSONLD(geoMeta.jsonLd[0])} />
|
|
459
|
+
<main>...</main>
|
|
460
|
+
</>
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Layout에서 사용 (앱 전체)
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
// app/layout.tsx
|
|
469
|
+
import { generateGEOMetadata, renderJSONLD } from '@hua-labs/hua-ux/framework';
|
|
470
|
+
import Script from 'next/script';
|
|
471
|
+
|
|
472
|
+
const appGeoMeta = generateGEOMetadata({
|
|
473
|
+
name: 'My App',
|
|
474
|
+
description: 'My amazing application',
|
|
475
|
+
// ... 앱 전체 설정
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
export const metadata = {
|
|
479
|
+
title: appGeoMeta.meta.find(m => m.name === 'description')?.content,
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
export default function RootLayout({ children }) {
|
|
483
|
+
return (
|
|
484
|
+
<html>
|
|
485
|
+
<head>
|
|
486
|
+
<Script {...renderJSONLD(appGeoMeta.jsonLd[0])} />
|
|
487
|
+
</head>
|
|
488
|
+
<body>{children}</body>
|
|
489
|
+
</html>
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
#### FAQ, HowTo, TechArticle 구조화된 데이터
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
import { generateFAQPageLD, generateHowToLD, generateTechArticleLD } from '@hua-labs/hua-ux/framework';
|
|
498
|
+
|
|
499
|
+
// FAQ 페이지
|
|
500
|
+
const faqLD = generateFAQPageLD([
|
|
501
|
+
{ question: 'What is hua-ux?', answer: 'A UX framework for Next.js' },
|
|
502
|
+
]);
|
|
503
|
+
|
|
504
|
+
// 튜토리얼 페이지
|
|
505
|
+
const howToLD = generateHowToLD({
|
|
506
|
+
name: 'How to get started',
|
|
507
|
+
steps: [
|
|
508
|
+
{ name: 'Install', text: 'Run: pnpm create hua-ux my-app' },
|
|
509
|
+
{ name: 'Configure', text: 'Edit hua-ux.config.ts' },
|
|
510
|
+
],
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// 기술 문서
|
|
514
|
+
const articleLD = generateTechArticleLD({
|
|
515
|
+
headline: 'Getting Started with hua-ux',
|
|
516
|
+
datePublished: '2025-12-29',
|
|
517
|
+
author: { name: 'hua-labs' },
|
|
518
|
+
});
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### ♿ 접근성 (Accessibility)
|
|
522
|
+
|
|
523
|
+
WCAG 2.1 준수를 위한 완벽한 도구 세트를 제공합니다.
|
|
524
|
+
|
|
525
|
+
#### 1. Skip to Content (네비게이션 건너뛰기)
|
|
526
|
+
|
|
527
|
+
키보드 사용자를 위한 필수 기능 - Tab 키로 메인 콘텐츠로 바로 이동:
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
// app/layout.tsx
|
|
531
|
+
import { SkipToContent } from '@hua-labs/hua-ux/framework';
|
|
532
|
+
|
|
533
|
+
export default function RootLayout({ children }) {
|
|
534
|
+
return (
|
|
535
|
+
<html>
|
|
536
|
+
<body>
|
|
537
|
+
<SkipToContent />
|
|
538
|
+
<nav>{/* navigation */}</nav>
|
|
539
|
+
<main id="main-content" tabIndex={-1}>
|
|
540
|
+
{children}
|
|
541
|
+
</main>
|
|
542
|
+
</body>
|
|
543
|
+
</html>
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### 2. Focus Management (포커스 관리)
|
|
549
|
+
|
|
550
|
+
페이지 전환 시 자동으로 메인 콘텐츠에 포커스:
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
import { useFocusManagement } from '@hua-labs/hua-ux/framework';
|
|
554
|
+
|
|
555
|
+
function MyPage() {
|
|
556
|
+
const mainRef = useFocusManagement({ autoFocus: true });
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<main ref={mainRef} tabIndex={-1}>
|
|
560
|
+
<h1>Page Title</h1>
|
|
561
|
+
</main>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**모달/드로어용 Focus Trap**:
|
|
567
|
+
```tsx
|
|
568
|
+
import { useFocusTrap } from '@hua-labs/hua-ux/framework';
|
|
569
|
+
|
|
570
|
+
function Modal({ isOpen, onClose }) {
|
|
571
|
+
const modalRef = useFocusTrap({ isActive: isOpen, onEscape: onClose });
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<div ref={modalRef} role="dialog" aria-modal="true">
|
|
575
|
+
<button>Close</button>
|
|
576
|
+
</div>
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### 3. Live Region (스크린 리더 알림)
|
|
582
|
+
|
|
583
|
+
동적 상태 변화를 스크린 리더 사용자에게 알림:
|
|
584
|
+
|
|
585
|
+
```tsx
|
|
586
|
+
import { LiveRegion, useLiveRegion } from '@hua-labs/hua-ux/framework';
|
|
587
|
+
|
|
588
|
+
// 선언적 사용
|
|
589
|
+
function MyForm() {
|
|
590
|
+
const [message, setMessage] = useState('');
|
|
591
|
+
|
|
592
|
+
const handleSubmit = async () => {
|
|
593
|
+
setMessage('저장 중...');
|
|
594
|
+
await saveData();
|
|
595
|
+
setMessage('저장되었습니다!');
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return (
|
|
599
|
+
<div>
|
|
600
|
+
<form onSubmit={handleSubmit}>{/* fields */}</form>
|
|
601
|
+
<LiveRegion message={message} />
|
|
602
|
+
</div>
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Hook 사용 (프로그래밍 방식)
|
|
607
|
+
function MyComponent() {
|
|
608
|
+
const { announce, LiveRegionComponent } = useLiveRegion();
|
|
609
|
+
|
|
610
|
+
const handleClick = () => {
|
|
611
|
+
announce('버튼이 클릭되었습니다');
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
return (
|
|
615
|
+
<div>
|
|
616
|
+
<button onClick={handleClick}>Click me</button>
|
|
617
|
+
{LiveRegionComponent}
|
|
618
|
+
</div>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### ⏳ 로딩 상태 최적화 (Loading State)
|
|
624
|
+
|
|
625
|
+
깜빡임 없는 부드러운 로딩 경험을 제공합니다.
|
|
626
|
+
|
|
627
|
+
#### 1. useDelayedLoading (깜빡임 방지)
|
|
628
|
+
|
|
629
|
+
**문제**: 빠른 API 응답 시 로딩 UI가 깜빡거림
|
|
630
|
+
**해결**: 300ms 이하로 끝나면 로딩 UI를 아예 안보여줌
|
|
631
|
+
|
|
632
|
+
```tsx
|
|
633
|
+
import { useDelayedLoading } from '@hua-labs/hua-ux/framework';
|
|
634
|
+
|
|
635
|
+
function MyComponent() {
|
|
636
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
637
|
+
const showLoading = useDelayedLoading(isLoading);
|
|
638
|
+
|
|
639
|
+
const fetchData = async () => {
|
|
640
|
+
setIsLoading(true);
|
|
641
|
+
await api.getData(); // 빠르게 끝나면 로딩 UI 안보임
|
|
642
|
+
setIsLoading(false);
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
return showLoading ? <Spinner /> : <Content />;
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**편의성 hook**:
|
|
650
|
+
```tsx
|
|
651
|
+
import { useLoadingState } from '@hua-labs/hua-ux/framework';
|
|
652
|
+
|
|
653
|
+
function MyComponent() {
|
|
654
|
+
const { showLoading, startLoading, stopLoading } = useLoadingState();
|
|
655
|
+
|
|
656
|
+
const fetchData = async () => {
|
|
657
|
+
startLoading();
|
|
658
|
+
try {
|
|
659
|
+
await api.getData();
|
|
660
|
+
} finally {
|
|
661
|
+
stopLoading();
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
return showLoading && <Spinner />;
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
#### 2. Skeleton (로딩 중 콘텐츠 미리보기)
|
|
670
|
+
|
|
671
|
+
로딩 시간이 체감적으로 짧게 느껴지고, 레이아웃 시프트를 방지합니다.
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
import { Skeleton, SkeletonGroup } from '@hua-labs/hua-ux/framework';
|
|
675
|
+
|
|
676
|
+
// 텍스트 스켈레톤
|
|
677
|
+
<Skeleton width="80%" />
|
|
678
|
+
<Skeleton width="60%" />
|
|
679
|
+
|
|
680
|
+
// 원형 (아바타)
|
|
681
|
+
<Skeleton variant="circular" width={40} height={40} />
|
|
682
|
+
|
|
683
|
+
// 직사각형 (이미지)
|
|
684
|
+
<Skeleton variant="rectangular" width={300} height={200} />
|
|
685
|
+
|
|
686
|
+
// 카드 스켈레톤
|
|
687
|
+
<div className="card">
|
|
688
|
+
<Skeleton variant="rectangular" height={200} />
|
|
689
|
+
<SkeletonGroup className="p-4">
|
|
690
|
+
<Skeleton width="60%" height={24} />
|
|
691
|
+
<Skeleton width="80%" />
|
|
692
|
+
<Skeleton width="40%" />
|
|
693
|
+
</SkeletonGroup>
|
|
694
|
+
</div>
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
**useDelayedLoading + Skeleton 조합**:
|
|
698
|
+
```tsx
|
|
699
|
+
function MyComponent() {
|
|
700
|
+
const { data, isLoading } = useQuery('data', fetchData);
|
|
701
|
+
const showLoading = useDelayedLoading(isLoading);
|
|
702
|
+
|
|
703
|
+
if (showLoading) {
|
|
704
|
+
return (
|
|
705
|
+
<SkeletonGroup>
|
|
706
|
+
<Skeleton width="60%" height={32} />
|
|
707
|
+
<Skeleton width="80%" />
|
|
708
|
+
<Skeleton width="70%" />
|
|
709
|
+
</SkeletonGroup>
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return <div>{data?.content}</div>;
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
#### 3. SuspenseWrapper (React Suspense 편의성)
|
|
718
|
+
|
|
719
|
+
React Suspense를 더 쉽게 사용할 수 있습니다.
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
import { SuspenseWrapper } from '@hua-labs/hua-ux/framework';
|
|
723
|
+
|
|
724
|
+
// 기본 사용 (자동 Skeleton fallback)
|
|
725
|
+
<SuspenseWrapper>
|
|
726
|
+
<AsyncComponent />
|
|
727
|
+
</SuspenseWrapper>
|
|
728
|
+
|
|
729
|
+
// 커스텀 fallback
|
|
730
|
+
<SuspenseWrapper fallback={<Spinner />}>
|
|
731
|
+
<AsyncComponent />
|
|
732
|
+
</SuspenseWrapper>
|
|
733
|
+
|
|
734
|
+
// Next.js Server Component
|
|
735
|
+
async function Posts() {
|
|
736
|
+
const posts = await fetchPosts();
|
|
737
|
+
return <div>{posts.map(p => <div key={p.id}>{p.title}</div>)}</div>;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export default function PostsPage() {
|
|
741
|
+
return (
|
|
742
|
+
<SuspenseWrapper>
|
|
743
|
+
<Posts />
|
|
744
|
+
</SuspenseWrapper>
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
**HOC 패턴**:
|
|
750
|
+
```tsx
|
|
751
|
+
import { withSuspense } from '@hua-labs/hua-ux/framework';
|
|
752
|
+
|
|
753
|
+
const AsyncPosts = withSuspense(Posts, <Skeleton height={200} />);
|
|
754
|
+
|
|
755
|
+
function MyPage() {
|
|
756
|
+
return <AsyncPosts />;
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## Use Cases
|
|
761
|
+
|
|
762
|
+
### 1. 제품 페이지 (Product Preset)
|
|
763
|
+
|
|
764
|
+
```tsx
|
|
765
|
+
import { productPreset } from '@hua-labs/hua-ux/presets';
|
|
766
|
+
|
|
767
|
+
// 빠른 전환, 최소 딜레이
|
|
768
|
+
const motionConfig = productPreset.motion;
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### 2. 랜딩 페이지 (Marketing Preset)
|
|
772
|
+
|
|
773
|
+
```tsx
|
|
774
|
+
import { marketingPreset } from '@hua-labs/hua-ux/presets';
|
|
775
|
+
|
|
776
|
+
// 드라마틱한 모션, 긴 딜레이
|
|
777
|
+
const motionConfig = marketingPreset.motion;
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### 3. 다국어 지원
|
|
781
|
+
|
|
782
|
+
```tsx
|
|
783
|
+
import { useTranslation } from '@hua-labs/hua-ux';
|
|
784
|
+
|
|
785
|
+
function MyComponent() {
|
|
786
|
+
const { t } = useTranslation();
|
|
787
|
+
return <h1>{t('common:welcome')}</h1>;
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### 4. 상태관리 (State Package)
|
|
792
|
+
|
|
793
|
+
```tsx
|
|
794
|
+
import { createHuaStore } from '@hua-labs/hua-ux';
|
|
795
|
+
// 또는
|
|
796
|
+
import { createHuaStore } from '@hua-labs/state';
|
|
797
|
+
|
|
798
|
+
const useAppStore = createHuaStore((set) => ({
|
|
799
|
+
theme: 'light',
|
|
800
|
+
setTheme: (theme) => set({ theme }),
|
|
801
|
+
}), {
|
|
802
|
+
persist: true,
|
|
803
|
+
ssr: true,
|
|
804
|
+
});
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
## 테스트
|
|
808
|
+
|
|
809
|
+
프레임워크의 주요 기능에 대한 테스트가 포함되어 있습니다:
|
|
810
|
+
|
|
811
|
+
```bash
|
|
812
|
+
cd packages/hua-ux
|
|
813
|
+
pnpm test
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**테스트 커버리지**:
|
|
817
|
+
- ✅ Motion hooks (`useMotion`)
|
|
818
|
+
- ✅ GEO 메타데이터 생성 (`generateGEOMetadata`, `createAIContext`)
|
|
819
|
+
- ✅ 구조화된 데이터 (`generateSoftwareApplicationLD`, `generateFAQPageLD`, etc.)
|
|
820
|
+
- ✅ CSS 변수 생성 (`generateCSSVariables`)
|
|
821
|
+
- ✅ Config 시스템 (`defineConfig`, `getConfig`, `setConfig`)
|
|
822
|
+
- ✅ ErrorBoundary 컴포넌트
|
|
823
|
+
- 🔄 Accessibility 모듈 (구현 완료, 테스트 예정)
|
|
824
|
+
- 🔄 Loading 모듈 (구현 완료, 테스트 예정)
|
|
825
|
+
|
|
826
|
+
## 버전
|
|
827
|
+
|
|
828
|
+
현재 버전: **0.1.0** (Alpha)
|
|
829
|
+
|
|
830
|
+
- `0.x`: Alpha 단계, API 변경 가능
|
|
831
|
+
- `1.x`: 안정화 후
|
|
832
|
+
|
|
833
|
+
## 라이선스
|
|
834
|
+
|
|
835
|
+
MIT
|
|
836
|
+
|
|
837
|
+
## 이슈 및 문의
|
|
838
|
+
|
|
839
|
+
문제가 발생하거나 제안사항이 있으시면 [GitHub Issues](https://github.com/HUA-Labs/HUA-Labs-public/issues)에 등록해주세요.
|