@m1kapp/ui 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 m1k
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 ADDED
@@ -0,0 +1,153 @@
1
+ # @m1k/ui
2
+
3
+ > Mobile-first app shell for side projects.
4
+ > Build apps that feel like native — in minutes.
5
+
6
+ ```
7
+ ┌─────────────────────┐
8
+ │ Header (sticky) │
9
+ ├─────────────────────┤
10
+ │ │
11
+ │ Content (scroll) │
12
+ │ │
13
+ ├─────────────────────┤
14
+ │ TabBar (sticky) │
15
+ └─────────────────────┘
16
+ rounded, shadow
17
+ floating on color
18
+ ```
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @m1k/ui
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import {
30
+ Watermark,
31
+ AppShell,
32
+ AppShellHeader,
33
+ AppShellContent,
34
+ TabBar,
35
+ Tab,
36
+ Section,
37
+ SectionHeader,
38
+ Divider,
39
+ StatChip,
40
+ EmptyState,
41
+ } from "@m1k/ui";
42
+
43
+ export default function App() {
44
+ const [tab, setTab] = useState("home");
45
+
46
+ return (
47
+ <Watermark color="#3b82f6" text="myapp">
48
+ <AppShell>
49
+ <AppShellHeader>
50
+ <span className="text-2xl font-black">myapp</span>
51
+ </AppShellHeader>
52
+
53
+ <AppShellContent>
54
+ <Section>
55
+ <h1 className="text-xl font-bold">Hello</h1>
56
+ </Section>
57
+
58
+ <Divider />
59
+
60
+ <Section className="flex gap-3">
61
+ <StatChip label="Today" value={42} />
62
+ <StatChip label="Total" value={1234} />
63
+ </Section>
64
+ </AppShellContent>
65
+
66
+ <TabBar>
67
+ <Tab
68
+ active={tab === "home"}
69
+ onClick={() => setTab("home")}
70
+ label="Home"
71
+ icon={<span>🏠</span>}
72
+ />
73
+ <Tab
74
+ active={tab === "profile"}
75
+ onClick={() => setTab("profile")}
76
+ label="Profile"
77
+ icon={<span>👤</span>}
78
+ />
79
+ </TabBar>
80
+ </AppShell>
81
+ </Watermark>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ## Components
87
+
88
+ ### Layout
89
+
90
+ | Component | Description |
91
+ |-----------|-------------|
92
+ | `Watermark` | Full-screen colored background with text pattern. The "floating app" look. |
93
+ | `AppShell` | Mobile app container (rounded, shadow, ring) |
94
+ | `AppShellHeader` | Sticky top header with blur backdrop |
95
+ | `AppShellContent` | Scrollable main content area |
96
+ | `TabBar` | Sticky bottom navigation |
97
+ | `Tab` | Individual tab button |
98
+
99
+ ### Content
100
+
101
+ | Component | Description |
102
+ |-----------|-------------|
103
+ | `Section` | Padded content section (px-4) |
104
+ | `SectionHeader` | Small uppercase section title |
105
+ | `Divider` | Horizontal separator line |
106
+ | `StatChip` | Compact stat display (label + number) |
107
+ | `EmptyState` | Placeholder with icon and message |
108
+
109
+ ## Props
110
+
111
+ ### `Watermark`
112
+
113
+ | Prop | Type | Default | Description |
114
+ |------|------|---------|-------------|
115
+ | `color` | `string` | `#0f172a` | Background color |
116
+ | `text` | `string` | `m1k` | Watermark pattern text |
117
+ | `maxWidth` | `number` | `430` | Max width of content area |
118
+ | `padding` | `number` | `12` | Padding around the shell |
119
+
120
+ ### `AppShell`
121
+
122
+ | Prop | Type | Default | Description |
123
+ |------|------|---------|-------------|
124
+ | `maxWidth` | `number` | `430` | Max width |
125
+ | `className` | `string` | | Additional classes |
126
+
127
+ ### `Tab`
128
+
129
+ | Prop | Type | Description |
130
+ |------|------|-------------|
131
+ | `active` | `boolean` | Whether tab is selected |
132
+ | `onClick` | `() => void` | Click handler |
133
+ | `icon` | `ReactNode` | Tab icon |
134
+ | `label` | `string` | Tab label text |
135
+ | `activeColor` | `string?` | Color when active |
136
+
137
+ ## Requirements
138
+
139
+ - React 18+
140
+ - Tailwind CSS 4+
141
+
142
+ ## Philosophy
143
+
144
+ In the AI era, building a side project takes a day.
145
+ But making it *feel* like an app still takes effort.
146
+
147
+ `@m1k/ui` gives you the mobile app shell — header, scrollable content, bottom tabs, floating on a colored background — so you can focus on what matters: your idea.
148
+
149
+ Built and battle-tested in [m1k](https://m1k.app).
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,113 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, CSSProperties } from 'react';
3
+
4
+ interface AppShellProps {
5
+ children: ReactNode;
6
+ className?: string;
7
+ /** Max width of the shell. Default: 430px */
8
+ maxWidth?: number;
9
+ style?: CSSProperties;
10
+ }
11
+ /**
12
+ * Mobile app-like container with rounded corners, shadow, and ring.
13
+ * Centers content and constrains width for a phone-like viewport.
14
+ */
15
+ declare function AppShell({ children, className, maxWidth, style, }: AppShellProps): react_jsx_runtime.JSX.Element;
16
+ interface AppShellHeaderProps {
17
+ children: ReactNode;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Sticky top header with blur backdrop.
22
+ */
23
+ declare function AppShellHeader({ children, className }: AppShellHeaderProps): react_jsx_runtime.JSX.Element;
24
+ interface AppShellContentProps {
25
+ children: ReactNode;
26
+ className?: string;
27
+ }
28
+ /**
29
+ * Scrollable main content area.
30
+ */
31
+ declare function AppShellContent({ children, className }: AppShellContentProps): react_jsx_runtime.JSX.Element;
32
+
33
+ interface TabBarProps {
34
+ children: ReactNode;
35
+ className?: string;
36
+ }
37
+ /**
38
+ * Sticky bottom navigation tab bar.
39
+ */
40
+ declare function TabBar({ children, className }: TabBarProps): react_jsx_runtime.JSX.Element;
41
+ interface TabProps {
42
+ active: boolean;
43
+ onClick: () => void;
44
+ icon: ReactNode;
45
+ label: string;
46
+ /** Active tab color. Default: current text color */
47
+ activeColor?: string;
48
+ }
49
+ /**
50
+ * Individual tab button for the TabBar.
51
+ */
52
+ declare function Tab({ active, onClick, icon, label, activeColor }: TabProps): react_jsx_runtime.JSX.Element;
53
+
54
+ interface SectionProps {
55
+ children: ReactNode;
56
+ className?: string;
57
+ }
58
+ /**
59
+ * Padded section wrapper (px-4).
60
+ */
61
+ declare function Section({ children, className }: SectionProps): react_jsx_runtime.JSX.Element;
62
+ interface SectionHeaderProps {
63
+ children: ReactNode;
64
+ }
65
+ /**
66
+ * Small uppercase section title.
67
+ */
68
+ declare function SectionHeader({ children }: SectionHeaderProps): react_jsx_runtime.JSX.Element;
69
+
70
+ /**
71
+ * Horizontal divider line with margin.
72
+ */
73
+ declare function Divider({ className }: {
74
+ className?: string;
75
+ }): react_jsx_runtime.JSX.Element;
76
+
77
+ interface StatChipProps {
78
+ label: string;
79
+ value: number;
80
+ className?: string;
81
+ }
82
+ /**
83
+ * Compact stat display chip with label and number.
84
+ */
85
+ declare function StatChip({ label, value, className }: StatChipProps): react_jsx_runtime.JSX.Element;
86
+
87
+ interface EmptyStateProps {
88
+ message: string;
89
+ icon?: ReactNode;
90
+ }
91
+ /**
92
+ * Empty state placeholder with icon and message.
93
+ */
94
+ declare function EmptyState({ message, icon }: EmptyStateProps): react_jsx_runtime.JSX.Element;
95
+
96
+ interface WatermarkProps {
97
+ children: ReactNode;
98
+ /** Background color */
99
+ color?: string;
100
+ /** Watermark text. Default: "m1k" */
101
+ text?: string;
102
+ /** Max width of content area. Default: 430 */
103
+ maxWidth?: number;
104
+ /** Padding around the app shell. Default: 12px */
105
+ padding?: number;
106
+ }
107
+ /**
108
+ * Full-screen colored background with repeating text watermark pattern.
109
+ * Wraps the AppShell and provides the "floating app" look.
110
+ */
111
+ declare function Watermark({ children, color, text, maxWidth, padding, }: WatermarkProps): react_jsx_runtime.JSX.Element;
112
+
113
+ export { AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Divider, EmptyState, type EmptyStateProps, Section, SectionHeader, type SectionHeaderProps, type SectionProps, StatChip, type StatChipProps, Tab, TabBar, type TabBarProps, type TabProps, Watermark, type WatermarkProps };
@@ -0,0 +1,113 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, CSSProperties } from 'react';
3
+
4
+ interface AppShellProps {
5
+ children: ReactNode;
6
+ className?: string;
7
+ /** Max width of the shell. Default: 430px */
8
+ maxWidth?: number;
9
+ style?: CSSProperties;
10
+ }
11
+ /**
12
+ * Mobile app-like container with rounded corners, shadow, and ring.
13
+ * Centers content and constrains width for a phone-like viewport.
14
+ */
15
+ declare function AppShell({ children, className, maxWidth, style, }: AppShellProps): react_jsx_runtime.JSX.Element;
16
+ interface AppShellHeaderProps {
17
+ children: ReactNode;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Sticky top header with blur backdrop.
22
+ */
23
+ declare function AppShellHeader({ children, className }: AppShellHeaderProps): react_jsx_runtime.JSX.Element;
24
+ interface AppShellContentProps {
25
+ children: ReactNode;
26
+ className?: string;
27
+ }
28
+ /**
29
+ * Scrollable main content area.
30
+ */
31
+ declare function AppShellContent({ children, className }: AppShellContentProps): react_jsx_runtime.JSX.Element;
32
+
33
+ interface TabBarProps {
34
+ children: ReactNode;
35
+ className?: string;
36
+ }
37
+ /**
38
+ * Sticky bottom navigation tab bar.
39
+ */
40
+ declare function TabBar({ children, className }: TabBarProps): react_jsx_runtime.JSX.Element;
41
+ interface TabProps {
42
+ active: boolean;
43
+ onClick: () => void;
44
+ icon: ReactNode;
45
+ label: string;
46
+ /** Active tab color. Default: current text color */
47
+ activeColor?: string;
48
+ }
49
+ /**
50
+ * Individual tab button for the TabBar.
51
+ */
52
+ declare function Tab({ active, onClick, icon, label, activeColor }: TabProps): react_jsx_runtime.JSX.Element;
53
+
54
+ interface SectionProps {
55
+ children: ReactNode;
56
+ className?: string;
57
+ }
58
+ /**
59
+ * Padded section wrapper (px-4).
60
+ */
61
+ declare function Section({ children, className }: SectionProps): react_jsx_runtime.JSX.Element;
62
+ interface SectionHeaderProps {
63
+ children: ReactNode;
64
+ }
65
+ /**
66
+ * Small uppercase section title.
67
+ */
68
+ declare function SectionHeader({ children }: SectionHeaderProps): react_jsx_runtime.JSX.Element;
69
+
70
+ /**
71
+ * Horizontal divider line with margin.
72
+ */
73
+ declare function Divider({ className }: {
74
+ className?: string;
75
+ }): react_jsx_runtime.JSX.Element;
76
+
77
+ interface StatChipProps {
78
+ label: string;
79
+ value: number;
80
+ className?: string;
81
+ }
82
+ /**
83
+ * Compact stat display chip with label and number.
84
+ */
85
+ declare function StatChip({ label, value, className }: StatChipProps): react_jsx_runtime.JSX.Element;
86
+
87
+ interface EmptyStateProps {
88
+ message: string;
89
+ icon?: ReactNode;
90
+ }
91
+ /**
92
+ * Empty state placeholder with icon and message.
93
+ */
94
+ declare function EmptyState({ message, icon }: EmptyStateProps): react_jsx_runtime.JSX.Element;
95
+
96
+ interface WatermarkProps {
97
+ children: ReactNode;
98
+ /** Background color */
99
+ color?: string;
100
+ /** Watermark text. Default: "m1k" */
101
+ text?: string;
102
+ /** Max width of content area. Default: 430 */
103
+ maxWidth?: number;
104
+ /** Padding around the app shell. Default: 12px */
105
+ padding?: number;
106
+ }
107
+ /**
108
+ * Full-screen colored background with repeating text watermark pattern.
109
+ * Wraps the AppShell and provides the "floating app" look.
110
+ */
111
+ declare function Watermark({ children, color, text, maxWidth, padding, }: WatermarkProps): react_jsx_runtime.JSX.Element;
112
+
113
+ export { AppShell, AppShellContent, type AppShellContentProps, AppShellHeader, type AppShellHeaderProps, type AppShellProps, Divider, EmptyState, type EmptyStateProps, Section, SectionHeader, type SectionHeaderProps, type SectionProps, StatChip, type StatChipProps, Tab, TabBar, type TabBarProps, type TabProps, Watermark, type WatermarkProps };
package/dist/index.js ADDED
@@ -0,0 +1,194 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ AppShell: () => AppShell,
25
+ AppShellContent: () => AppShellContent,
26
+ AppShellHeader: () => AppShellHeader,
27
+ Divider: () => Divider,
28
+ EmptyState: () => EmptyState,
29
+ Section: () => Section,
30
+ SectionHeader: () => SectionHeader,
31
+ StatChip: () => StatChip,
32
+ Tab: () => Tab,
33
+ TabBar: () => TabBar,
34
+ Watermark: () => Watermark
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/components/app-shell.tsx
39
+ var import_jsx_runtime = require("react/jsx-runtime");
40
+ function AppShell({
41
+ children,
42
+ className = "",
43
+ maxWidth = 430,
44
+ style
45
+ }) {
46
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
47
+ "div",
48
+ {
49
+ className: `w-full h-full flex flex-col bg-white dark:bg-zinc-950 shadow-2xl ring-1 ring-black/5 dark:ring-white/10 rounded-2xl overflow-hidden ${className}`,
50
+ style: { maxWidth, ...style },
51
+ children
52
+ }
53
+ );
54
+ }
55
+ function AppShellHeader({ children, className = "" }) {
56
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
57
+ "header",
58
+ {
59
+ className: `sticky top-0 z-20 px-4 py-3 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-800 bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-t-2xl ${className}`,
60
+ children
61
+ }
62
+ );
63
+ }
64
+ function AppShellContent({ children, className = "" }) {
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `flex-1 overflow-y-auto ${className}`, children });
66
+ }
67
+
68
+ // src/components/tab-bar.tsx
69
+ var import_jsx_runtime2 = require("react/jsx-runtime");
70
+ function TabBar({ children, className = "" }) {
71
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
72
+ "nav",
73
+ {
74
+ className: `sticky bottom-0 z-20 border-t border-zinc-200 dark:border-zinc-800 flex bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-b-2xl ${className}`,
75
+ children
76
+ }
77
+ );
78
+ }
79
+ function Tab({ active, onClick, icon, label, activeColor }) {
80
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
81
+ "button",
82
+ {
83
+ onClick,
84
+ className: `flex-1 flex flex-col items-center gap-0.5 py-2.5 transition-colors ${!active ? "text-zinc-300 dark:text-zinc-600" : ""}`,
85
+ style: active ? { color: activeColor } : void 0,
86
+ children: [
87
+ icon,
88
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] font-medium", children: label })
89
+ ]
90
+ }
91
+ );
92
+ }
93
+
94
+ // src/components/section.tsx
95
+ var import_jsx_runtime3 = require("react/jsx-runtime");
96
+ function Section({ children, className = "" }) {
97
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { className: `px-4 ${className}`, children });
98
+ }
99
+ function SectionHeader({ children }) {
100
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "text-[11px] font-semibold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider mb-3", children });
101
+ }
102
+
103
+ // src/components/divider.tsx
104
+ var import_jsx_runtime4 = require("react/jsx-runtime");
105
+ function Divider({ className = "" }) {
106
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `mx-4 my-6 h-px bg-zinc-200 dark:bg-zinc-800 ${className}` });
107
+ }
108
+
109
+ // src/components/stat-chip.tsx
110
+ var import_jsx_runtime5 = require("react/jsx-runtime");
111
+ function StatChip({ label, value, className = "" }) {
112
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `flex-1 rounded-xl bg-zinc-100 dark:bg-zinc-900 px-3 py-3 text-center ${className}`, children: [
113
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] text-zinc-500 dark:text-zinc-400 font-medium mb-0.5", children: label }),
114
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-lg font-bold tabular-nums text-zinc-900 dark:text-white", children: value.toLocaleString() })
115
+ ] });
116
+ }
117
+
118
+ // src/components/empty-state.tsx
119
+ var import_jsx_runtime6 = require("react/jsx-runtime");
120
+ function EmptyState({ message, icon }) {
121
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 gap-2", children: [
122
+ icon || /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className: "text-zinc-200 dark:text-zinc-700", children: [
123
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
124
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 15h8" }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "9", cy: "9", r: "1", fill: "currentColor", stroke: "none" }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "15", cy: "9", r: "1", fill: "currentColor", stroke: "none" })
127
+ ] }),
128
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-zinc-400 dark:text-zinc-500", children: message })
129
+ ] });
130
+ }
131
+
132
+ // src/components/watermark.tsx
133
+ var import_jsx_runtime7 = require("react/jsx-runtime");
134
+ function buildSvgPattern(text, opacity = 0.08) {
135
+ const w = 220;
136
+ const h = 120;
137
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">
138
+ <text x="10" y="45" font-family="system-ui,sans-serif" font-size="44" font-weight="900" fill="white" opacity="${opacity}">${text}</text>
139
+ <text x="120" y="100" font-family="system-ui,sans-serif" font-size="44" font-weight="900" fill="white" opacity="${opacity}">${text}</text>
140
+ </svg>`;
141
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
142
+ }
143
+ function Watermark({
144
+ children,
145
+ color = "#0f172a",
146
+ text = "m1k",
147
+ maxWidth = 430,
148
+ padding = 12
149
+ }) {
150
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
151
+ "div",
152
+ {
153
+ className: "h-dvh w-full relative overflow-hidden",
154
+ style: { backgroundColor: color, transition: "background-color 0.5s ease" },
155
+ children: [
156
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
157
+ "div",
158
+ {
159
+ className: "absolute inset-0 pointer-events-none select-none",
160
+ style: {
161
+ backgroundImage: buildSvgPattern(text),
162
+ backgroundRepeat: "repeat",
163
+ transform: "rotate(-12deg) scale(1.5)",
164
+ transformOrigin: "center center"
165
+ }
166
+ }
167
+ ),
168
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
169
+ "div",
170
+ {
171
+ className: "relative z-10 h-full flex items-center justify-center mx-auto",
172
+ style: { maxWidth, padding },
173
+ children
174
+ }
175
+ )
176
+ ]
177
+ }
178
+ );
179
+ }
180
+ // Annotate the CommonJS export names for ESM import in node:
181
+ 0 && (module.exports = {
182
+ AppShell,
183
+ AppShellContent,
184
+ AppShellHeader,
185
+ Divider,
186
+ EmptyState,
187
+ Section,
188
+ SectionHeader,
189
+ StatChip,
190
+ Tab,
191
+ TabBar,
192
+ Watermark
193
+ });
194
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/components/app-shell.tsx","../src/components/tab-bar.tsx","../src/components/section.tsx","../src/components/divider.tsx","../src/components/stat-chip.tsx","../src/components/empty-state.tsx","../src/components/watermark.tsx"],"sourcesContent":["export { AppShell, AppShellHeader, AppShellContent } from \"./components/app-shell\";\nexport type { AppShellProps, AppShellHeaderProps, AppShellContentProps } from \"./components/app-shell\";\n\nexport { TabBar, Tab } from \"./components/tab-bar\";\nexport type { TabBarProps, TabProps } from \"./components/tab-bar\";\n\nexport { Section, SectionHeader } from \"./components/section\";\nexport type { SectionProps, SectionHeaderProps } from \"./components/section\";\n\nexport { Divider } from \"./components/divider\";\n\nexport { StatChip } from \"./components/stat-chip\";\nexport type { StatChipProps } from \"./components/stat-chip\";\n\nexport { EmptyState } from \"./components/empty-state\";\nexport type { EmptyStateProps } from \"./components/empty-state\";\n\nexport { Watermark } from \"./components/watermark\";\nexport type { WatermarkProps } from \"./components/watermark\";\n","import { type ReactNode, type CSSProperties } from \"react\";\n\nexport interface AppShellProps {\n children: ReactNode;\n className?: string;\n /** Max width of the shell. Default: 430px */\n maxWidth?: number;\n style?: CSSProperties;\n}\n\n/**\n * Mobile app-like container with rounded corners, shadow, and ring.\n * Centers content and constrains width for a phone-like viewport.\n */\nexport function AppShell({\n children,\n className = \"\",\n maxWidth = 430,\n style,\n}: AppShellProps) {\n return (\n <div\n className={`w-full h-full flex flex-col bg-white dark:bg-zinc-950 shadow-2xl ring-1 ring-black/5 dark:ring-white/10 rounded-2xl overflow-hidden ${className}`}\n style={{ maxWidth, ...style }}\n >\n {children}\n </div>\n );\n}\n\nexport interface AppShellHeaderProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Sticky top header with blur backdrop.\n */\nexport function AppShellHeader({ children, className = \"\" }: AppShellHeaderProps) {\n return (\n <header\n className={`sticky top-0 z-20 px-4 py-3 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-800 bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-t-2xl ${className}`}\n >\n {children}\n </header>\n );\n}\n\nexport interface AppShellContentProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Scrollable main content area.\n */\nexport function AppShellContent({ children, className = \"\" }: AppShellContentProps) {\n return (\n <div className={`flex-1 overflow-y-auto ${className}`}>\n {children}\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface TabBarProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Sticky bottom navigation tab bar.\n */\nexport function TabBar({ children, className = \"\" }: TabBarProps) {\n return (\n <nav\n className={`sticky bottom-0 z-20 border-t border-zinc-200 dark:border-zinc-800 flex bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-b-2xl ${className}`}\n >\n {children}\n </nav>\n );\n}\n\nexport interface TabProps {\n active: boolean;\n onClick: () => void;\n icon: ReactNode;\n label: string;\n /** Active tab color. Default: current text color */\n activeColor?: string;\n}\n\n/**\n * Individual tab button for the TabBar.\n */\nexport function Tab({ active, onClick, icon, label, activeColor }: TabProps) {\n return (\n <button\n onClick={onClick}\n className={`flex-1 flex flex-col items-center gap-0.5 py-2.5 transition-colors ${\n !active ? \"text-zinc-300 dark:text-zinc-600\" : \"\"\n }`}\n style={active ? { color: activeColor } : undefined}\n >\n {icon}\n <span className=\"text-[10px] font-medium\">{label}</span>\n </button>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface SectionProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Padded section wrapper (px-4).\n */\nexport function Section({ children, className = \"\" }: SectionProps) {\n return <section className={`px-4 ${className}`}>{children}</section>;\n}\n\nexport interface SectionHeaderProps {\n children: ReactNode;\n}\n\n/**\n * Small uppercase section title.\n */\nexport function SectionHeader({ children }: SectionHeaderProps) {\n return (\n <h2 className=\"text-[11px] font-semibold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider mb-3\">\n {children}\n </h2>\n );\n}\n","/**\n * Horizontal divider line with margin.\n */\nexport function Divider({ className = \"\" }: { className?: string }) {\n return <div className={`mx-4 my-6 h-px bg-zinc-200 dark:bg-zinc-800 ${className}`} />;\n}\n","export interface StatChipProps {\n label: string;\n value: number;\n className?: string;\n}\n\n/**\n * Compact stat display chip with label and number.\n */\nexport function StatChip({ label, value, className = \"\" }: StatChipProps) {\n return (\n <div className={`flex-1 rounded-xl bg-zinc-100 dark:bg-zinc-900 px-3 py-3 text-center ${className}`}>\n <p className=\"text-[10px] text-zinc-500 dark:text-zinc-400 font-medium mb-0.5\">\n {label}\n </p>\n <p className=\"text-lg font-bold tabular-nums text-zinc-900 dark:text-white\">\n {value.toLocaleString()}\n </p>\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface EmptyStateProps {\n message: string;\n icon?: ReactNode;\n}\n\n/**\n * Empty state placeholder with icon and message.\n */\nexport function EmptyState({ message, icon }: EmptyStateProps) {\n return (\n <div className=\"flex flex-col items-center justify-center py-12 gap-2\">\n {icon || (\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className=\"text-zinc-200 dark:text-zinc-700\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 15h8\" />\n <circle cx=\"9\" cy=\"9\" r=\"1\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"15\" cy=\"9\" r=\"1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n )}\n <p className=\"text-sm text-zinc-400 dark:text-zinc-500\">{message}</p>\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface WatermarkProps {\n children: ReactNode;\n /** Background color */\n color?: string;\n /** Watermark text. Default: \"m1k\" */\n text?: string;\n /** Max width of content area. Default: 430 */\n maxWidth?: number;\n /** Padding around the app shell. Default: 12px */\n padding?: number;\n}\n\nfunction buildSvgPattern(text: string, opacity: number = 0.08): string {\n const w = 220;\n const h = 120;\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">\n <text x=\"10\" y=\"45\" font-family=\"system-ui,sans-serif\" font-size=\"44\" font-weight=\"900\" fill=\"white\" opacity=\"${opacity}\">${text}</text>\n <text x=\"120\" y=\"100\" font-family=\"system-ui,sans-serif\" font-size=\"44\" font-weight=\"900\" fill=\"white\" opacity=\"${opacity}\">${text}</text>\n </svg>`;\n return `url(\"data:image/svg+xml,${encodeURIComponent(svg)}\")`;\n}\n\n/**\n * Full-screen colored background with repeating text watermark pattern.\n * Wraps the AppShell and provides the \"floating app\" look.\n */\nexport function Watermark({\n children,\n color = \"#0f172a\",\n text = \"m1k\",\n maxWidth = 430,\n padding = 12,\n}: WatermarkProps) {\n return (\n <div\n className=\"h-dvh w-full relative overflow-hidden\"\n style={{ backgroundColor: color, transition: \"background-color 0.5s ease\" }}\n >\n <div\n className=\"absolute inset-0 pointer-events-none select-none\"\n style={{\n backgroundImage: buildSvgPattern(text),\n backgroundRepeat: \"repeat\",\n transform: \"rotate(-12deg) scale(1.5)\",\n transformOrigin: \"center center\",\n }}\n />\n <div\n className=\"relative z-10 h-full flex items-center justify-center mx-auto\"\n style={{ maxWidth, padding }}\n >\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,158 @@
1
+ "use client";
2
+
3
+ // src/components/app-shell.tsx
4
+ import { jsx } from "react/jsx-runtime";
5
+ function AppShell({
6
+ children,
7
+ className = "",
8
+ maxWidth = 430,
9
+ style
10
+ }) {
11
+ return /* @__PURE__ */ jsx(
12
+ "div",
13
+ {
14
+ className: `w-full h-full flex flex-col bg-white dark:bg-zinc-950 shadow-2xl ring-1 ring-black/5 dark:ring-white/10 rounded-2xl overflow-hidden ${className}`,
15
+ style: { maxWidth, ...style },
16
+ children
17
+ }
18
+ );
19
+ }
20
+ function AppShellHeader({ children, className = "" }) {
21
+ return /* @__PURE__ */ jsx(
22
+ "header",
23
+ {
24
+ className: `sticky top-0 z-20 px-4 py-3 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-800 bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-t-2xl ${className}`,
25
+ children
26
+ }
27
+ );
28
+ }
29
+ function AppShellContent({ children, className = "" }) {
30
+ return /* @__PURE__ */ jsx("div", { className: `flex-1 overflow-y-auto ${className}`, children });
31
+ }
32
+
33
+ // src/components/tab-bar.tsx
34
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
35
+ function TabBar({ children, className = "" }) {
36
+ return /* @__PURE__ */ jsx2(
37
+ "nav",
38
+ {
39
+ className: `sticky bottom-0 z-20 border-t border-zinc-200 dark:border-zinc-800 flex bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-b-2xl ${className}`,
40
+ children
41
+ }
42
+ );
43
+ }
44
+ function Tab({ active, onClick, icon, label, activeColor }) {
45
+ return /* @__PURE__ */ jsxs(
46
+ "button",
47
+ {
48
+ onClick,
49
+ className: `flex-1 flex flex-col items-center gap-0.5 py-2.5 transition-colors ${!active ? "text-zinc-300 dark:text-zinc-600" : ""}`,
50
+ style: active ? { color: activeColor } : void 0,
51
+ children: [
52
+ icon,
53
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] font-medium", children: label })
54
+ ]
55
+ }
56
+ );
57
+ }
58
+
59
+ // src/components/section.tsx
60
+ import { jsx as jsx3 } from "react/jsx-runtime";
61
+ function Section({ children, className = "" }) {
62
+ return /* @__PURE__ */ jsx3("section", { className: `px-4 ${className}`, children });
63
+ }
64
+ function SectionHeader({ children }) {
65
+ return /* @__PURE__ */ jsx3("h2", { className: "text-[11px] font-semibold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider mb-3", children });
66
+ }
67
+
68
+ // src/components/divider.tsx
69
+ import { jsx as jsx4 } from "react/jsx-runtime";
70
+ function Divider({ className = "" }) {
71
+ return /* @__PURE__ */ jsx4("div", { className: `mx-4 my-6 h-px bg-zinc-200 dark:bg-zinc-800 ${className}` });
72
+ }
73
+
74
+ // src/components/stat-chip.tsx
75
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
76
+ function StatChip({ label, value, className = "" }) {
77
+ return /* @__PURE__ */ jsxs2("div", { className: `flex-1 rounded-xl bg-zinc-100 dark:bg-zinc-900 px-3 py-3 text-center ${className}`, children: [
78
+ /* @__PURE__ */ jsx5("p", { className: "text-[10px] text-zinc-500 dark:text-zinc-400 font-medium mb-0.5", children: label }),
79
+ /* @__PURE__ */ jsx5("p", { className: "text-lg font-bold tabular-nums text-zinc-900 dark:text-white", children: value.toLocaleString() })
80
+ ] });
81
+ }
82
+
83
+ // src/components/empty-state.tsx
84
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
85
+ function EmptyState({ message, icon }) {
86
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center py-12 gap-2", children: [
87
+ icon || /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className: "text-zinc-200 dark:text-zinc-700", children: [
88
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
89
+ /* @__PURE__ */ jsx6("path", { d: "M8 15h8" }),
90
+ /* @__PURE__ */ jsx6("circle", { cx: "9", cy: "9", r: "1", fill: "currentColor", stroke: "none" }),
91
+ /* @__PURE__ */ jsx6("circle", { cx: "15", cy: "9", r: "1", fill: "currentColor", stroke: "none" })
92
+ ] }),
93
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-zinc-400 dark:text-zinc-500", children: message })
94
+ ] });
95
+ }
96
+
97
+ // src/components/watermark.tsx
98
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
99
+ function buildSvgPattern(text, opacity = 0.08) {
100
+ const w = 220;
101
+ const h = 120;
102
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">
103
+ <text x="10" y="45" font-family="system-ui,sans-serif" font-size="44" font-weight="900" fill="white" opacity="${opacity}">${text}</text>
104
+ <text x="120" y="100" font-family="system-ui,sans-serif" font-size="44" font-weight="900" fill="white" opacity="${opacity}">${text}</text>
105
+ </svg>`;
106
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
107
+ }
108
+ function Watermark({
109
+ children,
110
+ color = "#0f172a",
111
+ text = "m1k",
112
+ maxWidth = 430,
113
+ padding = 12
114
+ }) {
115
+ return /* @__PURE__ */ jsxs4(
116
+ "div",
117
+ {
118
+ className: "h-dvh w-full relative overflow-hidden",
119
+ style: { backgroundColor: color, transition: "background-color 0.5s ease" },
120
+ children: [
121
+ /* @__PURE__ */ jsx7(
122
+ "div",
123
+ {
124
+ className: "absolute inset-0 pointer-events-none select-none",
125
+ style: {
126
+ backgroundImage: buildSvgPattern(text),
127
+ backgroundRepeat: "repeat",
128
+ transform: "rotate(-12deg) scale(1.5)",
129
+ transformOrigin: "center center"
130
+ }
131
+ }
132
+ ),
133
+ /* @__PURE__ */ jsx7(
134
+ "div",
135
+ {
136
+ className: "relative z-10 h-full flex items-center justify-center mx-auto",
137
+ style: { maxWidth, padding },
138
+ children
139
+ }
140
+ )
141
+ ]
142
+ }
143
+ );
144
+ }
145
+ export {
146
+ AppShell,
147
+ AppShellContent,
148
+ AppShellHeader,
149
+ Divider,
150
+ EmptyState,
151
+ Section,
152
+ SectionHeader,
153
+ StatChip,
154
+ Tab,
155
+ TabBar,
156
+ Watermark
157
+ };
158
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/app-shell.tsx","../src/components/tab-bar.tsx","../src/components/section.tsx","../src/components/divider.tsx","../src/components/stat-chip.tsx","../src/components/empty-state.tsx","../src/components/watermark.tsx"],"sourcesContent":["import { type ReactNode, type CSSProperties } from \"react\";\n\nexport interface AppShellProps {\n children: ReactNode;\n className?: string;\n /** Max width of the shell. Default: 430px */\n maxWidth?: number;\n style?: CSSProperties;\n}\n\n/**\n * Mobile app-like container with rounded corners, shadow, and ring.\n * Centers content and constrains width for a phone-like viewport.\n */\nexport function AppShell({\n children,\n className = \"\",\n maxWidth = 430,\n style,\n}: AppShellProps) {\n return (\n <div\n className={`w-full h-full flex flex-col bg-white dark:bg-zinc-950 shadow-2xl ring-1 ring-black/5 dark:ring-white/10 rounded-2xl overflow-hidden ${className}`}\n style={{ maxWidth, ...style }}\n >\n {children}\n </div>\n );\n}\n\nexport interface AppShellHeaderProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Sticky top header with blur backdrop.\n */\nexport function AppShellHeader({ children, className = \"\" }: AppShellHeaderProps) {\n return (\n <header\n className={`sticky top-0 z-20 px-4 py-3 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-800 bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-t-2xl ${className}`}\n >\n {children}\n </header>\n );\n}\n\nexport interface AppShellContentProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Scrollable main content area.\n */\nexport function AppShellContent({ children, className = \"\" }: AppShellContentProps) {\n return (\n <div className={`flex-1 overflow-y-auto ${className}`}>\n {children}\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface TabBarProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Sticky bottom navigation tab bar.\n */\nexport function TabBar({ children, className = \"\" }: TabBarProps) {\n return (\n <nav\n className={`sticky bottom-0 z-20 border-t border-zinc-200 dark:border-zinc-800 flex bg-white/90 dark:bg-zinc-950/90 backdrop-blur-md rounded-b-2xl ${className}`}\n >\n {children}\n </nav>\n );\n}\n\nexport interface TabProps {\n active: boolean;\n onClick: () => void;\n icon: ReactNode;\n label: string;\n /** Active tab color. Default: current text color */\n activeColor?: string;\n}\n\n/**\n * Individual tab button for the TabBar.\n */\nexport function Tab({ active, onClick, icon, label, activeColor }: TabProps) {\n return (\n <button\n onClick={onClick}\n className={`flex-1 flex flex-col items-center gap-0.5 py-2.5 transition-colors ${\n !active ? \"text-zinc-300 dark:text-zinc-600\" : \"\"\n }`}\n style={active ? { color: activeColor } : undefined}\n >\n {icon}\n <span className=\"text-[10px] font-medium\">{label}</span>\n </button>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface SectionProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Padded section wrapper (px-4).\n */\nexport function Section({ children, className = \"\" }: SectionProps) {\n return <section className={`px-4 ${className}`}>{children}</section>;\n}\n\nexport interface SectionHeaderProps {\n children: ReactNode;\n}\n\n/**\n * Small uppercase section title.\n */\nexport function SectionHeader({ children }: SectionHeaderProps) {\n return (\n <h2 className=\"text-[11px] font-semibold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider mb-3\">\n {children}\n </h2>\n );\n}\n","/**\n * Horizontal divider line with margin.\n */\nexport function Divider({ className = \"\" }: { className?: string }) {\n return <div className={`mx-4 my-6 h-px bg-zinc-200 dark:bg-zinc-800 ${className}`} />;\n}\n","export interface StatChipProps {\n label: string;\n value: number;\n className?: string;\n}\n\n/**\n * Compact stat display chip with label and number.\n */\nexport function StatChip({ label, value, className = \"\" }: StatChipProps) {\n return (\n <div className={`flex-1 rounded-xl bg-zinc-100 dark:bg-zinc-900 px-3 py-3 text-center ${className}`}>\n <p className=\"text-[10px] text-zinc-500 dark:text-zinc-400 font-medium mb-0.5\">\n {label}\n </p>\n <p className=\"text-lg font-bold tabular-nums text-zinc-900 dark:text-white\">\n {value.toLocaleString()}\n </p>\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface EmptyStateProps {\n message: string;\n icon?: ReactNode;\n}\n\n/**\n * Empty state placeholder with icon and message.\n */\nexport function EmptyState({ message, icon }: EmptyStateProps) {\n return (\n <div className=\"flex flex-col items-center justify-center py-12 gap-2\">\n {icon || (\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className=\"text-zinc-200 dark:text-zinc-700\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 15h8\" />\n <circle cx=\"9\" cy=\"9\" r=\"1\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"15\" cy=\"9\" r=\"1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n )}\n <p className=\"text-sm text-zinc-400 dark:text-zinc-500\">{message}</p>\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface WatermarkProps {\n children: ReactNode;\n /** Background color */\n color?: string;\n /** Watermark text. Default: \"m1k\" */\n text?: string;\n /** Max width of content area. Default: 430 */\n maxWidth?: number;\n /** Padding around the app shell. Default: 12px */\n padding?: number;\n}\n\nfunction buildSvgPattern(text: string, opacity: number = 0.08): string {\n const w = 220;\n const h = 120;\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\">\n <text x=\"10\" y=\"45\" font-family=\"system-ui,sans-serif\" font-size=\"44\" font-weight=\"900\" fill=\"white\" opacity=\"${opacity}\">${text}</text>\n <text x=\"120\" y=\"100\" font-family=\"system-ui,sans-serif\" font-size=\"44\" font-weight=\"900\" fill=\"white\" opacity=\"${opacity}\">${text}</text>\n </svg>`;\n return `url(\"data:image/svg+xml,${encodeURIComponent(svg)}\")`;\n}\n\n/**\n * Full-screen colored background with repeating text watermark pattern.\n * Wraps the AppShell and provides the \"floating app\" look.\n */\nexport function Watermark({\n children,\n color = \"#0f172a\",\n text = \"m1k\",\n maxWidth = 430,\n padding = 12,\n}: WatermarkProps) {\n return (\n <div\n className=\"h-dvh w-full relative overflow-hidden\"\n style={{ backgroundColor: color, transition: \"background-color 0.5s ease\" }}\n >\n <div\n className=\"absolute inset-0 pointer-events-none select-none\"\n style={{\n backgroundImage: buildSvgPattern(text),\n backgroundRepeat: \"repeat\",\n transform: \"rotate(-12deg) scale(1.5)\",\n transformOrigin: \"center center\",\n }}\n />\n <div\n className=\"relative z-10 h-full flex items-center justify-center mx-auto\"\n style={{ maxWidth, padding }}\n >\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsx","jsx","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@m1kapp/ui",
3
+ "version": "0.1.0",
4
+ "description": "Mobile-first app shell UI components for side projects",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "lint": "tsc --noEmit",
22
+ "demo": "cd demo && npm run dev",
23
+ "demo:build": "cd demo && npm run build"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=18",
27
+ "react-dom": ">=18"
28
+ },
29
+ "devDependencies": {
30
+ "react": "^19.0.0",
31
+ "react-dom": "^19.0.0",
32
+ "typescript": "^5.0.0",
33
+ "tsup": "^8.0.0",
34
+ "@types/react": "^19.0.0",
35
+ "@types/react-dom": "^19.0.0",
36
+ "tailwindcss": "^4.0.0"
37
+ },
38
+ "keywords": [
39
+ "react",
40
+ "ui",
41
+ "mobile",
42
+ "app-shell",
43
+ "tailwind",
44
+ "side-project",
45
+ "m1k"
46
+ ],
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/yoominho/m1k-ui"
51
+ },
52
+ "homepage": "https://m1k.app"
53
+ }