@unikit-bridge/ui 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +99 -2
- package/docs/tokens.md +63 -0
- package/package.json +75 -4
- package/src/bridge/native.tsx +170 -0
- package/src/bridge/types.ts +96 -0
- package/src/bridge/uniwind-env.d.ts +1 -0
- package/src/bridge/web.tsx +166 -0
- package/src/components/badge.tsx +65 -0
- package/src/components/button.tsx +106 -0
- package/src/components/card.tsx +112 -0
- package/src/components/input.tsx +39 -0
- package/src/components/label.tsx +33 -0
- package/src/index.ts +45 -0
- package/src/lib/utils.ts +7 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/presets.ts +186 -0
- package/src/theme/provider.tsx +82 -0
- package/src/theme/tokens.ts +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 loopy-lim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @unikit-bridge/ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One typed component set that **renders React Native views on native and plain DOM on web** — the same `className` strings, the same look, no platform branches in your code.
|
|
4
|
+
|
|
5
|
+
- **Native (iOS/Android)** — components render React Native primitives styled by [uniwind](https://uniwind.dev) (Tailwind CSS v4 bindings, 2× faster than NativeWind).
|
|
6
|
+
- **Web (React DOM)** — the very same components render `div`/`span`/`button`/`input` styled by **standard Tailwind CSS v4**. No react-native-web, no extra styling engine: your Tailwind CSS is the engine.
|
|
7
|
+
- **Design tokens as a single source of truth** — light/dark themes are CSS variables generated from one `tokens.ts`; switching schemes is instant on both platforms (`UnikitThemeProvider`).
|
|
8
|
+
- **shadcn-style variants** — built with `class-variance-authority`; `cn()` (clsx + tailwind-merge) is exported for custom styling.
|
|
9
|
+
|
|
10
|
+
## How the bridge works
|
|
11
|
+
|
|
12
|
+
`@unikit-bridge/ui/primitives` resolves through [package export conditions](https://nodejs.org/api/packages.html#conditional-exports):
|
|
13
|
+
|
|
14
|
+
| Platform | Condition | Implementation |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Metro (React Native) | `react-native` | RN `View`/`Text`/`Pressable`/`TextInput` + uniwind |
|
|
17
|
+
| webpack / Turbopack / Vite (web) | `default` | `div`/`span`/`button`/`input` + standard Tailwind CSS |
|
|
18
|
+
|
|
19
|
+
Components import from that one entry point; each bundler picks its side. RN-specific props (`hitSlop`, …) are ignored on web, and web-idiom props (`disabled`, `onChangeText`, …) are translated on native.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @unikit-bridge/ui class-variance-authority clsx tailwind-merge
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then add the engine side you need:
|
|
28
|
+
|
|
29
|
+
- **Native**: `bun add uniwind react-native` (Tailwind v4 comes with uniwind's toolchain) — see the [uniwind setup](https://docs.uniwind.dev) (Metro `withUniwindConfig` + `global.css`).
|
|
30
|
+
- **Web**: `bun add tailwindcss` (v4) and make sure your global CSS scans this package, e.g. `@source '../node_modules/@unikit-bridge/ui/src';`.
|
|
31
|
+
|
|
32
|
+
### Required CSS (both platforms)
|
|
33
|
+
|
|
34
|
+
Add the token mapping + values to your global CSS (or install them via the shadcn registry: `bunx shadcn add <registry>/r/theme.json`):
|
|
35
|
+
|
|
36
|
+
```css
|
|
37
|
+
@import "tailwindcss";
|
|
38
|
+
|
|
39
|
+
/* web only — uniwind registers these itself */
|
|
40
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
41
|
+
@custom-variant web (&:where(:root, :root *));
|
|
42
|
+
|
|
43
|
+
@theme inline {
|
|
44
|
+
--color-background: hsl(var(--background));
|
|
45
|
+
--color-foreground: hsl(var(--foreground));
|
|
46
|
+
/* … full 20-token mapping — see docs/tokens.md */
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:root { --background: 0 0% 100%; /* … */ }
|
|
50
|
+
.dark { --background: 240 10% 3.9%; /* … */ }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
On web, put this in the app that compiles your Tailwind CSS (e.g. `app/globals.css` in Next.js, added to `transpilePackages: ["@unikit-bridge/ui"]`). On native, put it in the `global.css` referenced by uniwind's `cssEntryFile`.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
Wrap your app in the theme provider, then use the components — identical JSX on both platforms:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { UnikitThemeProvider, Button, Card, CardContent } from "@unikit-bridge/ui";
|
|
61
|
+
|
|
62
|
+
export default function App() {
|
|
63
|
+
return (
|
|
64
|
+
<UnikitThemeProvider defaultColorScheme="light">
|
|
65
|
+
<Card>
|
|
66
|
+
<CardContent className="gap-2">
|
|
67
|
+
<Button onPress={() => {}}>Press me</Button>
|
|
68
|
+
</CardContent>
|
|
69
|
+
</Card>
|
|
70
|
+
</UnikitThemeProvider>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Theming
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useUnikitTheme } from "@unikit-bridge/ui";
|
|
79
|
+
|
|
80
|
+
function ThemeToggle() {
|
|
81
|
+
const { colorScheme, toggleColorScheme } = useUnikitTheme();
|
|
82
|
+
return (
|
|
83
|
+
<Button variant="outline" size="sm" onPress={toggleColorScheme}>
|
|
84
|
+
{colorScheme === "light" ? "Dark" : "Light"} mode
|
|
85
|
+
</Button>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The provider holds the scheme and binds it to each engine: `Uniwind.setTheme()` on native, a `.dark` class on `<html>` on web. Prebuilt token presets (`themePresets`) and raw token access (`getThemeTokens`, `lightTokens`, `darkTokens`) are exported for advanced theming.
|
|
91
|
+
|
|
92
|
+
## Components
|
|
93
|
+
|
|
94
|
+
`Button` · `Card` (+ Header / Title / Description / Content / Footer) · `Input` · `Label` · `Badge`
|
|
95
|
+
|
|
96
|
+
All components accept a `className` and forward variant helpers (`buttonVariants`, `cardVariants`, …) for composition.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/docs/tokens.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# UniKit 디자인 토큰 카탈로그
|
|
2
|
+
|
|
3
|
+
> ⛔ 이 파일은 `bun run generate:design`(packages/unikit)이 생성합니다 — 수동 수정 금지.
|
|
4
|
+
> 값의 출처는 [`src/theme/tokens.ts`](../src/theme/tokens.ts)(기본)와 [`src/theme/presets.ts`](../src/theme/presets.ts)(프리셋 3종)입니다. 값 형식은 CSS `hsl()` 함수 인자로 바로 쓰는 HSL 채널 문자열("H S% L%")입니다.
|
|
5
|
+
|
|
6
|
+
## 토큰 값 표 (20종 = 색 19 + radius)
|
|
7
|
+
|
|
8
|
+
| 토큰 | 라이트 | 다크 | 역할 | 대표 사용 |
|
|
9
|
+
|---|---|---|---|---|
|
|
10
|
+
| `--background` | `0 0% 100%` | `240 10% 3.9%` | 화면 전체 기본 배경 | `bg-background (provider 래퍼)` |
|
|
11
|
+
| `--foreground` | `240 10% 3.9%` | `0 0% 98%` | 기본 텍스트·아이콘 전경색 | `text-foreground` |
|
|
12
|
+
| `--card` | `0 0% 100%` | `240 10% 3.9%` | 카드 표면 배경 | `bg-card` |
|
|
13
|
+
| `--card-foreground` | `240 10% 3.9%` | `0 0% 98%` | 카드 위 텍스트 전경색 | `text-card-foreground` |
|
|
14
|
+
| `--popover` | `0 0% 100%` | `240 10% 3.9%` | 팝오버·드롭다운 표면 — v0.1 컴포넌트 미사용, 확장용 | `bg-popover (예약)` |
|
|
15
|
+
| `--popover-foreground` | `240 10% 3.9%` | `0 0% 98%` | 팝오버 위 텍스트 — v0.1 미사용, 확장용 | `text-popover-foreground (예약)` |
|
|
16
|
+
| `--primary` | `240 5.9% 10%` | `0 0% 98%` | 주요 행동 색(기본 버튼 배경) | `bg-primary` |
|
|
17
|
+
| `--primary-foreground` | `0 0% 98%` | `240 5.9% 10%` | 주요 행동 위 텍스트 | `text-primary-foreground` |
|
|
18
|
+
| `--secondary` | `240 4.8% 95.9%` | `240 3.7% 15.9%` | 보조 행동 색(secondary 버튼·배지) | `bg-secondary` |
|
|
19
|
+
| `--secondary-foreground` | `240 5.9% 10%` | `0 0% 98%` | 보조 행동 위 텍스트 | `text-secondary-foreground` |
|
|
20
|
+
| `--muted` | `240 4.8% 95.9%` | `240 3.7% 15.9%` | 강조를 낮춘 영역 배경 | `bg-muted` |
|
|
21
|
+
| `--muted-foreground` | `240 3.8% 46.1%` | `240 5% 64.9%` | 강조를 낮춘 텍스트(설명문) | `text-muted-foreground` |
|
|
22
|
+
| `--accent` | `240 4.8% 95.9%` | `240 3.7% 15.9%` | 호버·눌림 강조 배경(outline/ghost active) | `active:bg-accent` |
|
|
23
|
+
| `--accent-foreground` | `240 5.9% 10%` | `0 0% 98%` | 강조 배경 위 텍스트 | `active:text-accent-foreground` |
|
|
24
|
+
| `--destructive` | `0 72.2% 50.6%` | `0 62.8% 30.6%` | 위험·삭제 행동 색 | `bg-destructive` |
|
|
25
|
+
| `--destructive-foreground` | `0 0% 98%` | `0 0% 98%` | 위험 행동 위 텍스트 | `text-destructive-foreground` |
|
|
26
|
+
| `--border` | `240 5.9% 90%` | `240 3.7% 15.9%` | 일반 테두리(card outline 등) | `border-border` |
|
|
27
|
+
| `--input` | `240 5.9% 90%` | `240 3.7% 15.9%` | 입력창 테두리 | `border-input` |
|
|
28
|
+
| `--ring` | `240 5.9% 10%` | `240 4.9% 83.9%` | 키보드 포커스 링 | `focus-visible:ring-ring` |
|
|
29
|
+
| `--radius` | `0.5rem` | `0.5rem` | 모서리 반경 기준값(스킴 무관 공통) | `rounded-md (scale 파생)` |
|
|
30
|
+
|
|
31
|
+
## 프리셋 테마 (기본 + 3종)
|
|
32
|
+
|
|
33
|
+
값 출처는 Tailwind 공식 팔레트 계산 + WCAG 대비 검증(22-design-plan §2.2 규칙). 재적용: 다른 프리셋을 같은 방식으로 add하면 기존 값이 그 자리에서 교체됩니다(같은 프리셋 재add는 값 불변).
|
|
34
|
+
|
|
35
|
+
| 항목 | 중립 계열 | 라이트 primary | 핵심 대비비¹ | 설치(한 줄) |
|
|
36
|
+
|---|---|---|---|---|
|
|
37
|
+
| `theme` (Zinc (기본)) | zinc | zinc-900 | 16.96:1 | `bunx shadcn add <레지스트리>/r/theme.json -y` |
|
|
38
|
+
| `theme-blue` (Blue) | slate | blue-600 | 5.17:1 | `bunx shadcn add <레지스트리>/r/theme-blue.json -y` |
|
|
39
|
+
| `theme-emerald` (Emerald) | zinc | emerald-700 | 5.49:1 | `bunx shadcn add <레지스트리>/r/theme-emerald.json -y` |
|
|
40
|
+
| `theme-amber` (Amber) | stone | amber-700 | 5.01:1 | `bunx shadcn add <레지스트리>/r/theme-amber.json -y` |
|
|
41
|
+
|
|
42
|
+
¹ 라이트 primary↔primary-foreground(흰 전경)의 WCAG 대비비 — audit:design가 매 실행 재계산(기준 ≥4.5:1).
|
|
43
|
+
프리셋 전체 값(20종×라이트/다크)은 [`src/theme/presets.ts`](../src/theme/presets.ts)와 각 `/r/theme-<이름>.json`에서 관찰할 수 있습니다.
|
|
44
|
+
|
|
45
|
+
## 일관성 규칙 (audit:design가 기계 검사)
|
|
46
|
+
|
|
47
|
+
1. **원색 리터럴 금지** — 컴포넌트 소스에 `#fff`, `rgb(...)`, `oklch(...)` 등 직접 색 값을 쓰지 않는다. 반드시 위 토큰을 참조하는 유틸리티 클래스(`bg-primary` 등)를 쓴다.
|
|
48
|
+
2. **Tailwind 기본 팔레트 금지** — `bg-zinc-500`, `text-white` 류는 테마 전환(라이트↔다크)이 되지 않으므로 금지.
|
|
49
|
+
3. **임의값 클래스 금지** — `h-[37px]`, `bg-[#fff]` 류 대신 스케일 클래스(`h-10`)와 토큰을 쓴다.
|
|
50
|
+
4. **재테마** — 소비자는 아래 한 줄로 토큰을 받고, 값 커스터마이즈는 해당 CSS 변수값만 바꾼다(컴포넌트 수정 불필요). 런타임 스킴 전환은 `UnikitThemeProvider`가 담당한다.
|
|
51
|
+
```bash
|
|
52
|
+
bunx shadcn add <레지스트리>/r/theme.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 사본이 생성되는 곳
|
|
56
|
+
|
|
57
|
+
| 사본 | 생성 위치 | 비고 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| 웹 전역 CSS | `apps/web/app/globals.css` :root/.dark 블록 | 표준 Tailwind v4 — 기본 테마만 |
|
|
60
|
+
| 네이티브 전역 CSS | `apps/expo/global.css` @variant light/dark 블록 | uniwind(Tailwind v4) — 기본 테마만 |
|
|
61
|
+
| 토큰→유틸리티 매핑 | 두 앱 전역 CSS `@theme inline` 블록 | 토큰 키에서 자동 생성 — 두 플랫폼 동일 바이트 |
|
|
62
|
+
| 레지스트리 theme 항목 4종 | `registry.json` cssVars light/dark | 기본 + 프리셋 3 — 소비자 `shadcn add` 전달 |
|
|
63
|
+
| 이 카탈로그 | `packages/unikit/docs/tokens.md` | 값 드리프트는 audit (f)가 차단 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unikit-bridge/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bridge UI kit — one component set that renders React Native views on native (uniwind) and plain DOM on web (Tailwind CSS v4). Same className, same look.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/loopy-lim/UniKit.git",
|
|
9
|
+
"directory": "packages/unikit"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/loopy-lim/UniKit#readme",
|
|
12
|
+
"bugs": "https://github.com/loopy-lim/UniKit/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react-native",
|
|
15
|
+
"expo",
|
|
16
|
+
"uniwind",
|
|
17
|
+
"react",
|
|
18
|
+
"tailwind",
|
|
19
|
+
"tailwindcss",
|
|
20
|
+
"design-system",
|
|
21
|
+
"ui-components",
|
|
22
|
+
"universal",
|
|
23
|
+
"cross-platform",
|
|
24
|
+
"shadcn"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./src/index.ts",
|
|
28
|
+
"./primitives": {
|
|
29
|
+
"react-native": "./src/bridge/native.tsx",
|
|
30
|
+
"default": "./src/bridge/web.tsx"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "./src/index.ts",
|
|
34
|
+
"types": "./src/index.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"src",
|
|
37
|
+
"docs",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "tsc --noEmit",
|
|
47
|
+
"generate:design": "bun scripts/generate-design.ts",
|
|
48
|
+
"audit:design": "bun scripts/audit-design.ts",
|
|
49
|
+
"check:consumer": "bun scripts/check-consumer-theme.ts"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
53
|
+
"class-variance-authority": "^0.7.0",
|
|
54
|
+
"clsx": "^2.1.0",
|
|
55
|
+
"tailwind-merge": "^3.0.0",
|
|
56
|
+
"react-native": ">=0.81.0",
|
|
57
|
+
"uniwind": ">=1.13.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react-native": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"uniwind": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"typescript": "~5.9.0",
|
|
69
|
+
"@types/react": "~19.3.0",
|
|
70
|
+
"react": "19.3.0",
|
|
71
|
+
"react-native": "0.88.1",
|
|
72
|
+
"uniwind": "~1.13.2",
|
|
73
|
+
"class-variance-authority": "~0.7.1",
|
|
74
|
+
"clsx": "~2.1.1",
|
|
75
|
+
"tailwind-merge": "~3.7.0"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 네이티브 브리지 — React Native(iOS/Android) + uniwind 소비자용.
|
|
3
|
+
*
|
|
4
|
+
* RN 프리미티브를 그대로 재노출한다. className은 uniwind가 빌드 타임에
|
|
5
|
+
* RN 스타일로 컴파일한다(metro.config.js의 withUniwindConfig — 앱 책임).
|
|
6
|
+
* RN 관용 프로퍼티(hitSlop 등)는 네이티브 그대로 전달된다.
|
|
7
|
+
*
|
|
8
|
+
* 웹 관용 프로퍼티는 RN 관용으로 치환한다:
|
|
9
|
+
* disabled(버튼)→ RN 표준 disabled · onChangeText는 RN 표준 그대로
|
|
10
|
+
* aria-* 프로퍼티는 RN 0.7x+가 네이티브 관용으로 지원한다.
|
|
11
|
+
*/
|
|
12
|
+
import { forwardRef, type Ref } from "react";
|
|
13
|
+
import {
|
|
14
|
+
Pressable as RNPressable,
|
|
15
|
+
Text as RNText,
|
|
16
|
+
TextInput as RNTextInput,
|
|
17
|
+
View as RNView,
|
|
18
|
+
type PressableInstance,
|
|
19
|
+
type TextInstance,
|
|
20
|
+
type TextInputInstance,
|
|
21
|
+
type ViewInstance,
|
|
22
|
+
} from "react-native";
|
|
23
|
+
import { Uniwind } from "uniwind";
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
ApplyColorScheme,
|
|
27
|
+
BridgePressableRef,
|
|
28
|
+
BridgeTextRef,
|
|
29
|
+
BridgeTextInputRef,
|
|
30
|
+
BridgeViewRef,
|
|
31
|
+
PressableProps,
|
|
32
|
+
TextProps,
|
|
33
|
+
TextInputProps,
|
|
34
|
+
ViewProps,
|
|
35
|
+
} from "./types";
|
|
36
|
+
|
|
37
|
+
// 공유 타입 전량 재수출 — 소비자는 @unikit-bridge/ui/primitives에서 타입까지 한 번에 import
|
|
38
|
+
export type * from "./types";
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// View — RN View 재노출(타입만 브리지 계약으로 좁힌다)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
const View = forwardRef<BridgeViewRef, ViewProps>(
|
|
44
|
+
({ className, children, nativeID, testID, ...rest }, ref) => (
|
|
45
|
+
<RNView
|
|
46
|
+
ref={ref as Ref<ViewInstance>}
|
|
47
|
+
nativeID={nativeID}
|
|
48
|
+
testID={testID}
|
|
49
|
+
className={className}
|
|
50
|
+
{...rest}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</RNView>
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
View.displayName = "View";
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Text — RN Text 재노출
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
const Text = forwardRef<BridgeTextRef, TextProps>(
|
|
62
|
+
({ className, children, nativeID, testID, onPress, ...rest }, ref) => (
|
|
63
|
+
<RNText
|
|
64
|
+
ref={ref as Ref<TextInstance>}
|
|
65
|
+
nativeID={nativeID}
|
|
66
|
+
testID={testID}
|
|
67
|
+
onPress={onPress}
|
|
68
|
+
className={className}
|
|
69
|
+
{...rest}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</RNText>
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
Text.displayName = "Text";
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Pressable — RN Pressable 재노출. onPress는 RN 시그니처(이벤트 인자)를
|
|
79
|
+
// 무인자로 좁혀 브리지 계약과 맞춘다(컴포넌트·데모는 무인자 콜백만 쓴다).
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
const Pressable = forwardRef<BridgePressableRef, PressableProps>(
|
|
82
|
+
(
|
|
83
|
+
{
|
|
84
|
+
className,
|
|
85
|
+
children,
|
|
86
|
+
onPress,
|
|
87
|
+
disabled,
|
|
88
|
+
accessibilityLabel,
|
|
89
|
+
accessibilityRole,
|
|
90
|
+
hitSlop,
|
|
91
|
+
nativeID,
|
|
92
|
+
testID,
|
|
93
|
+
...rest
|
|
94
|
+
},
|
|
95
|
+
ref,
|
|
96
|
+
) => (
|
|
97
|
+
<RNPressable
|
|
98
|
+
ref={ref as Ref<PressableInstance>}
|
|
99
|
+
nativeID={nativeID}
|
|
100
|
+
testID={testID}
|
|
101
|
+
onPress={onPress ? () => onPress() : undefined}
|
|
102
|
+
disabled={disabled}
|
|
103
|
+
accessibilityLabel={accessibilityLabel}
|
|
104
|
+
accessibilityRole={accessibilityRole}
|
|
105
|
+
hitSlop={hitSlop}
|
|
106
|
+
className={className}
|
|
107
|
+
{...rest}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</RNPressable>
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
Pressable.displayName = "Pressable";
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// TextInput — RN TextInput 재노출. 웹 관용 disabled는 네이티브 표준 속성이
|
|
117
|
+
// 아니므로 여기서는 버린다(입력 차단 치환 editable={false}는 Input이 수행).
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
const TextInput = forwardRef<BridgeTextInputRef, TextInputProps>(
|
|
120
|
+
(
|
|
121
|
+
{
|
|
122
|
+
className,
|
|
123
|
+
value,
|
|
124
|
+
defaultValue,
|
|
125
|
+
placeholder,
|
|
126
|
+
editable,
|
|
127
|
+
disabled: _disabled, // 웹 관용 — 네이티브는 위 editable 치환으로 이미 반영됨
|
|
128
|
+
onChangeText,
|
|
129
|
+
nativeID,
|
|
130
|
+
testID,
|
|
131
|
+
accessibilityLabelledBy,
|
|
132
|
+
autoFocus,
|
|
133
|
+
secureTextEntry,
|
|
134
|
+
inputMode,
|
|
135
|
+
...rest
|
|
136
|
+
},
|
|
137
|
+
ref,
|
|
138
|
+
) => (
|
|
139
|
+
<RNTextInput
|
|
140
|
+
ref={ref as Ref<TextInputInstance>}
|
|
141
|
+
nativeID={nativeID}
|
|
142
|
+
testID={testID}
|
|
143
|
+
accessibilityLabelledBy={accessibilityLabelledBy}
|
|
144
|
+
className={className}
|
|
145
|
+
value={value}
|
|
146
|
+
defaultValue={defaultValue}
|
|
147
|
+
placeholder={placeholder}
|
|
148
|
+
editable={editable}
|
|
149
|
+
onChangeText={onChangeText}
|
|
150
|
+
autoFocus={autoFocus}
|
|
151
|
+
secureTextEntry={secureTextEntry}
|
|
152
|
+
inputMode={inputMode}
|
|
153
|
+
{...rest}
|
|
154
|
+
/>
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
TextInput.displayName = "TextInput";
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// 테마 바인딩
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
/** 색 스킴 → uniwind 활성 테마. setTheme은 Appearance.setColorScheme도 호출해
|
|
163
|
+
* Alert·Modal 등 시스템 UI가 앱 테마를 따르게 한다.
|
|
164
|
+
* 정적 import가 안전한 이유: 웹 번들은 exports "default" 조건으로 web.tsx만
|
|
165
|
+
* 로드하므로 이 파일(와 uniwind 의존)은 네이티브 그래프에만 존재한다. */
|
|
166
|
+
const applyColorScheme: ApplyColorScheme = (colorScheme) => {
|
|
167
|
+
Uniwind.setTheme(colorScheme);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export { View, Text, Pressable, TextInput, applyColorScheme };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 브리지 프리미티브의 공개 타입 — 플랫폼 공통 부분집합.
|
|
3
|
+
*
|
|
4
|
+
* `@unikit-bridge/ui/primitives`는 package.json exports의 조건분기로 해석된다:
|
|
5
|
+
* - 네이티브(Metro, "react-native" 조건) → native.tsx — RN View/Text/Pressable/TextInput
|
|
6
|
+
* + uniwind(className을 RN 스타일로 컴파일)
|
|
7
|
+
* - 웹(Next/Vite 등, "default" 조건) → web.tsx — div/span/button/input에 className을
|
|
8
|
+
* 그대로 싣고 표준 Tailwind CSS v4가 스타일링한다
|
|
9
|
+
*
|
|
10
|
+
* 두 구현 모두 이 파일의 타입을 충족해야 한다(컴포넌트 소스는 플랫폼 무관하게
|
|
11
|
+
* 한 벌로 컴파일된다). 프로퍼티는 v0.1 컴포넌트·데모가 실제 쓰는 공통 부분집합이며,
|
|
12
|
+
* 플랫폼 고유 프로퍼티(hitSlop 등)는 웹에서 무시된다.
|
|
13
|
+
*/
|
|
14
|
+
import type { ReactNode } from "react";
|
|
15
|
+
|
|
16
|
+
export type BridgeColorScheme = "light" | "dark";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Props
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export type ViewProps = {
|
|
23
|
+
className?: string;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
/** 네이티브 nativeID — 웹에서는 id로 전달된다 */
|
|
26
|
+
nativeID?: string;
|
|
27
|
+
testID?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type TextProps = {
|
|
31
|
+
className?: string;
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
nativeID?: string;
|
|
34
|
+
testID?: string;
|
|
35
|
+
onPress?: () => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type PressableProps = {
|
|
39
|
+
className?: string;
|
|
40
|
+
children?: ReactNode;
|
|
41
|
+
onPress?: () => void;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
accessibilityLabel?: string;
|
|
44
|
+
accessibilityRole?: "button" | "link" | "search" | "image" | "text";
|
|
45
|
+
/** RN 0.7x+ 관용 — 웹 DOM에서도 동일 속성으로 전달된다 */
|
|
46
|
+
"aria-disabled"?: boolean;
|
|
47
|
+
/** RN 전용 터치 타깃 확장(px) — 웹에서는 무시 */
|
|
48
|
+
hitSlop?: number | { top: number; bottom: number; left: number; right: number };
|
|
49
|
+
nativeID?: string;
|
|
50
|
+
testID?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type TextInputProps = {
|
|
54
|
+
className?: string;
|
|
55
|
+
value?: string;
|
|
56
|
+
defaultValue?: string;
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
/** RN 관용 — 웹에서는 readOnly로 치환 */
|
|
59
|
+
editable?: boolean;
|
|
60
|
+
/** 웹 관용 disabled — 네이티브에서는 editable={false}로 치환된다(Input이 처리) */
|
|
61
|
+
disabled?: boolean;
|
|
62
|
+
onChangeText?: (text: string) => void;
|
|
63
|
+
nativeID?: string;
|
|
64
|
+
testID?: string;
|
|
65
|
+
/** 웹 DOM에서 aria-labelledby로 전달 — 입력창↔라벨 연결 */
|
|
66
|
+
accessibilityLabelledBy?: string;
|
|
67
|
+
/** RN 0.7x+ 관용 — 웹 DOM에서도 동일 속성으로 전달된다 */
|
|
68
|
+
"aria-disabled"?: boolean;
|
|
69
|
+
autoFocus?: boolean;
|
|
70
|
+
secureTextEntry?: boolean;
|
|
71
|
+
inputMode?: "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search";
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Refs — 컴포넌트가 실제로 호출하는 메서드만 계약으로 선언(구조적 타이핑).
|
|
76
|
+
// 네이티브 인스턴스(RN)와 DOM 요소(웹) 모두 이 인터페이스를 충족한다.
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
export interface BridgeViewRef {}
|
|
80
|
+
export interface BridgeTextRef {}
|
|
81
|
+
export interface BridgePressableRef {}
|
|
82
|
+
export interface BridgeTextInputRef {
|
|
83
|
+
focus(): void;
|
|
84
|
+
blur(): void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// 테마 엔진 바인딩
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 색 스킴을 스타일링 엔진에 반영한다(UnikitThemeProvider가 호출).
|
|
93
|
+
* - native.tsx → uniwind `Uniwind.setTheme()`(native 표준 다이얼로그까지 동기)
|
|
94
|
+
* - web.tsx → document.documentElement의 `.dark` 클래스 토글(표준 Tailwind dark 변형)
|
|
95
|
+
*/
|
|
96
|
+
export type ApplyColorScheme = (colorScheme: BridgeColorScheme) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="uniwind/types" />
|