@m1kapp/kit 0.0.31 → 0.0.33
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 +34 -14
- package/bin/favicon.mjs +73 -24
- package/bin/skills/m1kapp-pwa.md +10 -3
- package/bin/stats.mjs +12 -5
- package/dist/index.d.mts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +3 -3
- package/dist/index.mjs +3 -3
- package/dist/meta.json +5 -5
- package/dist/ogimage.d.mts +3 -1
- package/dist/ogimage.d.ts +3 -1
- package/dist/ogimage.js +1 -1
- package/dist/ogimage.mjs +1 -1
- package/dist/pwa.js +1 -1
- package/dist/pwa.mjs +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,21 +17,25 @@ npm install @m1kapp/kit
|
|
|
17
17
|
|
|
18
18
|
```tsx
|
|
19
19
|
// CSS 자동 주입 — 별도 import 불필요
|
|
20
|
-
import { AppShell, AppShellHeader, AppShellContent, TabBar, Tab } from "@m1kapp/kit";
|
|
20
|
+
import { Watermark, AppShell, AppShellHeader, AppShellContent, Section, TabBar, Tab } from "@m1kapp/kit";
|
|
21
21
|
|
|
22
22
|
export default function App() {
|
|
23
23
|
return (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
// Watermark가 뷰포트 전체 높이/중앙정렬/하단 크레딧을 같이 줍니다.
|
|
25
|
+
// AppShell만 단독으로 쓰면 콘텐츠 높이만큼 쪼그라들어요.
|
|
26
|
+
<Watermark color="#e2603f" text="myapp">
|
|
27
|
+
<AppShell>
|
|
28
|
+
<AppShellHeader>
|
|
29
|
+
<h1>My App</h1>
|
|
30
|
+
</AppShellHeader>
|
|
31
|
+
<AppShellContent>
|
|
32
|
+
<Section className="pt-4">{/* 콘텐츠 — Divider로 섹션 구분 */}</Section>
|
|
33
|
+
</AppShellContent>
|
|
34
|
+
<TabBar>
|
|
35
|
+
<Tab href="/" icon={<HomeIcon />} label="홈" />
|
|
36
|
+
</TabBar>
|
|
37
|
+
</AppShell>
|
|
38
|
+
</Watermark>
|
|
35
39
|
);
|
|
36
40
|
}
|
|
37
41
|
```
|
|
@@ -454,14 +458,16 @@ npx m1kkit track https://myside.app
|
|
|
454
458
|
```
|
|
455
459
|
|
|
456
460
|
```bash
|
|
457
|
-
# 2) .env 에 한 줄 → 이후 자동 집계 (스니펫 붙여넣기 불필요)
|
|
461
|
+
# 2) Next.js면 .env 에 한 줄 → 이후 자동 집계 (스니펫 붙여넣기 불필요)
|
|
458
462
|
NEXT_PUBLIC_M1K_SLUG=your-slug
|
|
459
463
|
```
|
|
460
464
|
|
|
465
|
+
Vite 등 Next.js가 아닌 앱은 `NEXT_PUBLIC_*` env가 안 읽히니 `trackSlug` prop으로 직접 지정하세요:
|
|
466
|
+
|
|
461
467
|
```tsx
|
|
462
468
|
// 끄기 / 명시 지정
|
|
463
469
|
<Watermark track={false}>…</Watermark> // 비콘 끔
|
|
464
|
-
<Watermark trackSlug="your-slug">…</Watermark> // env 대신 직접 지정
|
|
470
|
+
<Watermark trackSlug="your-slug">…</Watermark> // env 대신 직접 지정 (Vite는 이 방식 사용)
|
|
465
471
|
// PoweredByKit 단독 사용 시: <PoweredByKit slug="your-slug" track={false} />
|
|
466
472
|
```
|
|
467
473
|
|
|
@@ -521,6 +527,20 @@ export async function GET() {
|
|
|
521
527
|
<OGImage type="product" title="@m1kapp/kit" tagline="올인원 킷" features={["기능1", "기능2"]} color={c} bg={bg} />
|
|
522
528
|
```
|
|
523
529
|
|
|
530
|
+
### CLI로 한 번에 (Next.js 라우트 없이)
|
|
531
|
+
|
|
532
|
+
Vite 등 서버 없는 SPA라 `app/og/route.tsx`를 못 만든다면, 빌드 없이 CLI로
|
|
533
|
+
파비콘부터 OG 이미지, manifest까지 한 번에 생성하세요:
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
npx m1kkit favicon --appname="My App" --slogan="한 줄 소개" --color=#3b82f6 --domain=myapp.com
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
`favicon.ico` · `apple-touch-icon.png` · `icon-192.png` · `icon-512.png` ·
|
|
540
|
+
`icon-maskable-512.png` · `og-image.png`(1200×630, 위 `default` 템플릿 사용) ·
|
|
541
|
+
`manifest.json` 7개가 `public/`(자동 감지)에 한 번에 생기고, `<head>`에 붙여넣을
|
|
542
|
+
태그 스니펫도 콘솔에 출력됩니다.
|
|
543
|
+
|
|
524
544
|
### 폰트 & 이모지
|
|
525
545
|
|
|
526
546
|
```tsx
|
package/bin/favicon.mjs
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @m1kapp/kit favicon
|
|
3
|
+
* @m1kapp/kit favicon + SEO 이미지 생성 CLI
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* npx @m1kapp/kit favicon
|
|
7
7
|
* npx @m1kapp/kit favicon --text=kit --color=#0f0f1a
|
|
8
8
|
* npx @m1kapp/kit favicon --text=W --color=#7c3aed --out=./static
|
|
9
|
+
* npx @m1kapp/kit favicon --appname="My App" --slogan="한 줄 소개" --domain=myapp.com
|
|
9
10
|
*
|
|
10
11
|
* Options:
|
|
11
|
-
* --text 표시할 텍스트 (기본: package.json name 첫 글자)
|
|
12
|
-
* --
|
|
12
|
+
* --text 파비콘에 표시할 짧은 텍스트 (기본: package.json name 첫 글자)
|
|
13
|
+
* --appname OG 이미지/manifest용 앱 이름 (기본: package.json name)
|
|
14
|
+
* --slogan OG 이미지 부제
|
|
15
|
+
* --domain OG 이미지 하단 도메인 (선택)
|
|
16
|
+
* --color 브랜드색 hex (기본: #0f0f1a)
|
|
13
17
|
* --out 출력 디렉토리 (기본: 자동 감지)
|
|
18
|
+
*
|
|
19
|
+
* 생성 파일: favicon.ico · apple-touch-icon.png · icon-192.png · icon-512.png ·
|
|
20
|
+
* icon-maskable-512.png · og-image.png · manifest.json
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import { ImageResponse } from "@vercel/og";
|
|
@@ -61,48 +68,90 @@ function detectOutDir() {
|
|
|
61
68
|
return cwd;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
function detectText() {
|
|
66
|
-
if (args.text) return args.text;
|
|
71
|
+
function readPkgName() {
|
|
67
72
|
try {
|
|
68
73
|
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"));
|
|
69
|
-
|
|
70
|
-
return name.slice(0, 3); // 최대 3글자
|
|
74
|
+
return (pkg.name ?? "app").replace(/^@[^/]+\//, ""); // scoped 패키지 처리
|
|
71
75
|
} catch {
|
|
72
76
|
return "app";
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
/* ───
|
|
80
|
+
/* ─── Auto-detect short favicon text ─── */
|
|
81
|
+
function detectText() {
|
|
82
|
+
if (args.text) return args.text;
|
|
83
|
+
return readPkgName().slice(0, 3); // 최대 3글자
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ─── Auto-detect full app name (OG/manifest용) ─── */
|
|
87
|
+
function detectAppName() {
|
|
88
|
+
if (args.appname) return args.appname;
|
|
89
|
+
return readPkgName();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ─── Load generators from dist ─── */
|
|
77
93
|
const distPath = path.resolve(__dirname, "../dist/ogimage.mjs");
|
|
78
|
-
const { createFaviconElement } = await import(distPath);
|
|
94
|
+
const { createFaviconElement, OGImage } = await import(distPath);
|
|
79
95
|
|
|
80
96
|
/* ─── Generate ─── */
|
|
81
97
|
const text = detectText();
|
|
98
|
+
const appName = detectAppName();
|
|
99
|
+
const slogan = args.slogan;
|
|
100
|
+
const domain = args.domain;
|
|
82
101
|
const color = args.color ?? "#0f0f1a";
|
|
83
102
|
const outDir = detectOutDir();
|
|
84
103
|
|
|
85
|
-
async function generatePng(sizePx) {
|
|
104
|
+
async function generatePng(sizePx, maskable = false) {
|
|
86
105
|
const res = new ImageResponse(
|
|
87
|
-
h(createFaviconElement, { text, color, size: sizePx }),
|
|
106
|
+
h(createFaviconElement, { text, color, size: sizePx, maskable }),
|
|
88
107
|
{ width: sizePx, height: sizePx }
|
|
89
108
|
);
|
|
90
109
|
return Buffer.from(await res.arrayBuffer());
|
|
91
110
|
}
|
|
92
111
|
|
|
93
|
-
|
|
112
|
+
function write(name, buffer) {
|
|
113
|
+
fs.writeFileSync(path.join(outDir, name), buffer);
|
|
114
|
+
console.log(`✓ ${path.relative(process.cwd(), path.join(outDir, name))}`);
|
|
115
|
+
}
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
const icoBuffer = await pngToIco(icoBuffers);
|
|
97
|
-
fs.writeFileSync(path.join(outDir, "favicon.ico"), icoBuffer);
|
|
98
|
-
console.log(`✓ ${path.relative(process.cwd(), path.join(outDir, "favicon.ico"))}`);
|
|
117
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
99
118
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
console.log(`✓ ${path.relative(process.cwd(), path.join(outDir, "apple-touch-icon.png"))}`);
|
|
119
|
+
const icoBuffers = await Promise.all([16, 32, 48].map((s) => generatePng(s)));
|
|
120
|
+
write("favicon.ico", await pngToIco(icoBuffers));
|
|
103
121
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
write("apple-touch-icon.png", await generatePng(180));
|
|
123
|
+
write("icon-192.png", await generatePng(192));
|
|
124
|
+
write("icon-512.png", await generatePng(512));
|
|
125
|
+
write("icon-maskable-512.png", await generatePng(512, true));
|
|
107
126
|
|
|
108
|
-
|
|
127
|
+
const ogRes = new ImageResponse(
|
|
128
|
+
h(OGImage, { type: "default", title: appName, sub: slogan, appName, color, domain }),
|
|
129
|
+
{ width: 1200, height: 630 }
|
|
130
|
+
);
|
|
131
|
+
write("og-image.png", Buffer.from(await ogRes.arrayBuffer()));
|
|
132
|
+
|
|
133
|
+
const manifest = {
|
|
134
|
+
name: appName,
|
|
135
|
+
short_name: appName,
|
|
136
|
+
start_url: "/",
|
|
137
|
+
display: "standalone",
|
|
138
|
+
background_color: color,
|
|
139
|
+
theme_color: color,
|
|
140
|
+
icons: [
|
|
141
|
+
{ src: "/icon-192.png", sizes: "192x192", type: "image/png" },
|
|
142
|
+
{ src: "/icon-512.png", sizes: "512x512", type: "image/png" },
|
|
143
|
+
{ src: "/icon-maskable-512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
write("manifest.json", JSON.stringify(manifest, null, 2) + "\n");
|
|
147
|
+
|
|
148
|
+
console.log(`\n🎉 appname="${appName}" color="${color}" → ${path.relative(process.cwd(), outDir) || "."}/`);
|
|
149
|
+
console.log(`
|
|
150
|
+
<head>에 붙여넣기:
|
|
151
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
152
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
153
|
+
<link rel="manifest" href="/manifest.json" />
|
|
154
|
+
<meta property="og:image" content="https://<도메인>/og-image.png" />
|
|
155
|
+
<meta property="og:image:width" content="1200" />
|
|
156
|
+
<meta property="og:image:height" content="630" />
|
|
157
|
+
`);
|
package/bin/skills/m1kapp-pwa.md
CHANGED
|
@@ -36,12 +36,19 @@ PWA 설정 현황
|
|
|
36
36
|
|
|
37
37
|
빠진 항목에 대해 확인 후 적용한다.
|
|
38
38
|
|
|
39
|
-
아이콘이 없으면: `npx m1kkit favicon`
|
|
40
|
-
|
|
39
|
+
아이콘이 없으면: `npx m1kkit favicon --appname="[앱 이름]" --color="[테마 컬러]"` 안내
|
|
40
|
+
— favicon.ico/apple-touch-icon/icon-192/icon-512/icon-maskable-512 뿐 아니라
|
|
41
|
+
og-image.png와 manifest.json까지 한 번에 만들어줌.
|
|
42
|
+
|
|
43
|
+
manifest가 없으면:
|
|
44
|
+
- Next.js면 앱 이름·테마 컬러를 물어보고 `createManifest` 적용 (아래).
|
|
45
|
+
- Vite 등 App Router가 없는 프로젝트면 `npx m1kkit favicon`이 만든
|
|
46
|
+
`public/manifest.json`을 그대로 쓰면 됨 — 따로 만들 필요 없음.
|
|
47
|
+
|
|
41
48
|
viewport가 없으면: `mobileViewport` 적용
|
|
42
49
|
|
|
43
50
|
```ts
|
|
44
|
-
// app/manifest.ts
|
|
51
|
+
// app/manifest.ts (Next.js App Router)
|
|
45
52
|
import { createManifest } from "@m1kapp/kit/pwa"
|
|
46
53
|
|
|
47
54
|
export default function manifest() {
|
package/bin/stats.mjs
CHANGED
|
@@ -390,6 +390,7 @@ let totalBranches = 0;
|
|
|
390
390
|
let totalFunctions = 0;
|
|
391
391
|
let maxFile = { path: "", lines: 0 };
|
|
392
392
|
let longFiles = 0; // 200줄 초과 파일 수
|
|
393
|
+
let longFileSeverity = 0; // 200→400줄 0→1, 400→600줄 1→2로 완만하게 누적 (600줄+ 파일당 최대 2)
|
|
393
394
|
const allImports = new Set();
|
|
394
395
|
const ts = loadTypescript();
|
|
395
396
|
const allFns = []; // AST 모드: {name, cc, cog, line, file}
|
|
@@ -417,7 +418,10 @@ for (const file of files) {
|
|
|
417
418
|
fileContents.push({ file: rel, content });
|
|
418
419
|
}
|
|
419
420
|
if (counts.code > maxFile.lines) maxFile = { path: path.relative(process.cwd(), file), lines: counts.code };
|
|
420
|
-
if (counts.code > 200)
|
|
421
|
+
if (counts.code > 200) {
|
|
422
|
+
longFiles++;
|
|
423
|
+
longFileSeverity += Math.min(2, (counts.code - 200) / 200);
|
|
424
|
+
}
|
|
421
425
|
const imports = detectKitImports(content);
|
|
422
426
|
for (const imp of imports) allImports.add(imp);
|
|
423
427
|
}
|
|
@@ -431,7 +435,10 @@ const avgFileLines = files.length > 0 ? Math.round(codeLines / files.length) : 0
|
|
|
431
435
|
// - cog>15 함수 비율 ×3 (최대 25), cog>25 함수 비율 ×5 (최대 15)
|
|
432
436
|
// - 최악 함수: cog 20 초과분 ×1 (최대 15)
|
|
433
437
|
// - 중복 밀도: 3% 초과분 ×2.5 (최대 25) ← 새 축
|
|
434
|
-
// - 200줄 초과
|
|
438
|
+
// - 200줄 초과 파일: 초과분에 비례한 심각도(200→400줄 0→1, 400→600줄 1→2, 600줄+ 캡) 비율 ×1.5 (최대 10)
|
|
439
|
+
// ※ 파일 개수 기준 이진 카운트(200줄 넘으면 무조건 1) 대신 심각도 가중 — 살짝 넘는 파일과
|
|
440
|
+
// 터무니없이 큰 파일을 구분하고, 파일 수 적은 프로젝트가 파일 1개만으로 즉시 만점 감점 맞는 절벽 방지.
|
|
441
|
+
// / 평균 파일 길이 80줄 초과분 /5 (최대 10)
|
|
435
442
|
let qualityScore;
|
|
436
443
|
let cc = null;
|
|
437
444
|
let cognitive = null;
|
|
@@ -465,14 +472,14 @@ if (ts && allFns.length > 0) {
|
|
|
465
472
|
|
|
466
473
|
const over15Pct = (cogOver15.length / allFns.length) * 100;
|
|
467
474
|
const over25Pct = (cogOver25.length / allFns.length) * 100;
|
|
468
|
-
const
|
|
475
|
+
const longFileSeverityPct = (longFileSeverity / files.length) * 100;
|
|
469
476
|
qualityScore = Math.max(0, Math.round(
|
|
470
477
|
100
|
|
471
478
|
- Math.min(25, over15Pct * 3)
|
|
472
479
|
- Math.min(15, over25Pct * 5)
|
|
473
480
|
- Math.min(15, Math.max(0, maxCog - 20))
|
|
474
481
|
- Math.min(25, Math.max(0, duplication.percent - 3) * 2.5)
|
|
475
|
-
- Math.min(10,
|
|
482
|
+
- Math.min(10, longFileSeverityPct * 1.5)
|
|
476
483
|
- Math.min(10, Math.max(0, avgFileLines - 80) / 5)
|
|
477
484
|
));
|
|
478
485
|
} else {
|
|
@@ -480,7 +487,7 @@ if (ts && allFns.length > 0) {
|
|
|
480
487
|
qualityScore = Math.max(0, Math.round(
|
|
481
488
|
100
|
|
482
489
|
- Math.min(40, Math.max(0, branchDensity - 10) * 2)
|
|
483
|
-
- Math.min(30,
|
|
490
|
+
- Math.min(30, longFileSeverity * 2.5)
|
|
484
491
|
- Math.min(30, Math.max(0, avgFileLines - 80) / 4)
|
|
485
492
|
));
|
|
486
493
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -65,6 +65,19 @@ interface AppShellProps {
|
|
|
65
65
|
/**
|
|
66
66
|
* Mobile app-like container with rounded corners, shadow, and ring.
|
|
67
67
|
* Centers content and constrains width for a phone-like viewport.
|
|
68
|
+
*
|
|
69
|
+
* `AppShell` fills `h-full` of its parent — it does not size itself. Wrap it
|
|
70
|
+
* in `<Watermark>` (gives `h-dvh` + centering + the `PoweredByKit` credit
|
|
71
|
+
* strip) rather than a hand-rolled height/centering div; without a sized
|
|
72
|
+
* parent the shell just collapses to content height.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* <Watermark color="#e2603f" text="myapp">
|
|
76
|
+
* <AppShell>
|
|
77
|
+
* <AppShellHeader>...</AppShellHeader>
|
|
78
|
+
* <AppShellContent><Section>...</Section></AppShellContent>
|
|
79
|
+
* </AppShell>
|
|
80
|
+
* </Watermark>
|
|
68
81
|
*/
|
|
69
82
|
declare function AppShell({ children, className, maxWidth, maxHeight, accent, accentFg, style, }: AppShellProps): react_jsx_runtime.JSX.Element;
|
|
70
83
|
interface AppShellHeaderProps {
|
|
@@ -80,7 +93,9 @@ interface AppShellContentProps {
|
|
|
80
93
|
className?: string;
|
|
81
94
|
}
|
|
82
95
|
/**
|
|
83
|
-
* Scrollable main content area.
|
|
96
|
+
* Scrollable main content area. Carries no padding of its own — build
|
|
97
|
+
* children from `<Section>` (px-4) blocks separated by `<Divider spacing="sm" />`,
|
|
98
|
+
* not a hand-rolled `<div className="p-4 flex flex-col gap-4">`.
|
|
84
99
|
*/
|
|
85
100
|
declare function AppShellContent({ children, className }: AppShellContentProps): react_jsx_runtime.JSX.Element;
|
|
86
101
|
|
|
@@ -111,6 +126,13 @@ interface TabBarProps {
|
|
|
111
126
|
}
|
|
112
127
|
/**
|
|
113
128
|
* Sticky bottom navigation tab bar.
|
|
129
|
+
*
|
|
130
|
+
* Carries no safe-area padding of its own — TabBar is meant to sit inside
|
|
131
|
+
* `AppShell`, which sits inside `Watermark`. `Watermark` appends its
|
|
132
|
+
* `PoweredByKit` credit strip right after `AppShell`, and that strip is the
|
|
133
|
+
* one that owns the home-indicator safe area (`variant="overlay"`). If
|
|
134
|
+
* TabBar also padded itself for the safe area, the reservation would be
|
|
135
|
+
* doubled — a blank gap between the tab icons and the credit strip.
|
|
114
136
|
*/
|
|
115
137
|
declare function TabBar({ children, className }: TabBarProps): react_jsx_runtime.JSX.Element;
|
|
116
138
|
interface TabRootProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,19 @@ interface AppShellProps {
|
|
|
65
65
|
/**
|
|
66
66
|
* Mobile app-like container with rounded corners, shadow, and ring.
|
|
67
67
|
* Centers content and constrains width for a phone-like viewport.
|
|
68
|
+
*
|
|
69
|
+
* `AppShell` fills `h-full` of its parent — it does not size itself. Wrap it
|
|
70
|
+
* in `<Watermark>` (gives `h-dvh` + centering + the `PoweredByKit` credit
|
|
71
|
+
* strip) rather than a hand-rolled height/centering div; without a sized
|
|
72
|
+
* parent the shell just collapses to content height.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* <Watermark color="#e2603f" text="myapp">
|
|
76
|
+
* <AppShell>
|
|
77
|
+
* <AppShellHeader>...</AppShellHeader>
|
|
78
|
+
* <AppShellContent><Section>...</Section></AppShellContent>
|
|
79
|
+
* </AppShell>
|
|
80
|
+
* </Watermark>
|
|
68
81
|
*/
|
|
69
82
|
declare function AppShell({ children, className, maxWidth, maxHeight, accent, accentFg, style, }: AppShellProps): react_jsx_runtime.JSX.Element;
|
|
70
83
|
interface AppShellHeaderProps {
|
|
@@ -80,7 +93,9 @@ interface AppShellContentProps {
|
|
|
80
93
|
className?: string;
|
|
81
94
|
}
|
|
82
95
|
/**
|
|
83
|
-
* Scrollable main content area.
|
|
96
|
+
* Scrollable main content area. Carries no padding of its own — build
|
|
97
|
+
* children from `<Section>` (px-4) blocks separated by `<Divider spacing="sm" />`,
|
|
98
|
+
* not a hand-rolled `<div className="p-4 flex flex-col gap-4">`.
|
|
84
99
|
*/
|
|
85
100
|
declare function AppShellContent({ children, className }: AppShellContentProps): react_jsx_runtime.JSX.Element;
|
|
86
101
|
|
|
@@ -111,6 +126,13 @@ interface TabBarProps {
|
|
|
111
126
|
}
|
|
112
127
|
/**
|
|
113
128
|
* Sticky bottom navigation tab bar.
|
|
129
|
+
*
|
|
130
|
+
* Carries no safe-area padding of its own — TabBar is meant to sit inside
|
|
131
|
+
* `AppShell`, which sits inside `Watermark`. `Watermark` appends its
|
|
132
|
+
* `PoweredByKit` credit strip right after `AppShell`, and that strip is the
|
|
133
|
+
* one that owns the home-indicator safe area (`variant="overlay"`). If
|
|
134
|
+
* TabBar also padded itself for the safe area, the reservation would be
|
|
135
|
+
* doubled — a blank gap between the tab icons and the credit strip.
|
|
114
136
|
*/
|
|
115
137
|
declare function TabBar({ children, className }: TabBarProps): react_jsx_runtime.JSX.Element;
|
|
116
138
|
interface TabRootProps {
|