@bug-on/md3-react 3.0.0 → 3.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/.turbo/turbo-build.log +42 -33
- package/CHANGELOG.md +14 -0
- package/dist/index.css +178 -0
- package/dist/index.d.mts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +35 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -18
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.d.mts +1 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +13 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugin.mjs +3 -0
- package/dist/plugin.mjs.map +1 -0
- package/package.json +8 -2
- package/scripts/copy-assets.js +36 -3
- package/src/index.ts +1 -1
- package/src/lib/theme-utils.ts +19 -4
- package/src/plugin.ts +12 -0
- package/src/ui/button.test.tsx +19 -10
- package/src/ui/button.tsx +2 -6
- package/src/ui/navigation-rail.tsx +9 -6
- package/src/ui/shared/constants.ts +13 -0
- package/src/ui/theme-provider/index.tsx +32 -7
- package/tsup.config.ts +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@bug-on/md3-tailwind';
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@bug-on/md3-tailwind';
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var md3Tailwind = require('@bug-on/md3-tailwind');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var md3Tailwind__default = /*#__PURE__*/_interopDefault(md3Tailwind);
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module.exports = md3Tailwind__default.default;
|
|
12
|
+
//# sourceMappingURL=plugin.js.map
|
|
13
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"plugin.js","sourcesContent":[]}
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"plugin.mjs","sourcesContent":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bug-on/md3-react",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Material Design 3 Expressive React components",
|
|
5
5
|
"author": "Bug Ổn",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
"./material-symbols-self-hosted.css": {
|
|
43
43
|
"types": "./dist/material-symbols-self-hosted.css.d.ts",
|
|
44
44
|
"default": "./dist/material-symbols-self-hosted.css"
|
|
45
|
+
},
|
|
46
|
+
"./plugin": {
|
|
47
|
+
"types": "./dist/plugin.d.ts",
|
|
48
|
+
"import": "./dist/plugin.mjs",
|
|
49
|
+
"require": "./dist/plugin.js"
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
"peerDependencies": {
|
|
@@ -64,7 +69,8 @@
|
|
|
64
69
|
"class-variance-authority": "^0.7.1",
|
|
65
70
|
"clsx": "^2.1.1",
|
|
66
71
|
"tailwind-merge": "^3.3.1",
|
|
67
|
-
"@bug-on/md3-tokens": "3.0.
|
|
72
|
+
"@bug-on/md3-tokens": "3.0.1",
|
|
73
|
+
"@bug-on/md3-tailwind": "3.0.1"
|
|
68
74
|
},
|
|
69
75
|
"devDependencies": {
|
|
70
76
|
"@testing-library/jest-dom": "^6.9.1",
|
package/scripts/copy-assets.js
CHANGED
|
@@ -26,11 +26,42 @@ if (fs.existsSync(srcCss)) {
|
|
|
26
26
|
console.log("✅ Copied typography.css to dist/typography.css");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Bundle index.css = md3-tokens CSS + react component base styles.
|
|
30
|
+
// This ensures users only need to import @bug-on/md3-react/index.css
|
|
31
|
+
// and get all required design tokens automatically.
|
|
32
|
+
const tokensCssDir = path.join(__dirname, "../../tokens/dist");
|
|
33
|
+
const tokensColorsCss = path.join(tokensCssDir, "colors.css");
|
|
34
|
+
const tokensShapeCss = path.join(tokensCssDir, "shape.css");
|
|
35
|
+
|
|
36
|
+
const bundleParts = [
|
|
37
|
+
"/* @bug-on/md3-tokens — MD3 System Color Tokens */",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(tokensColorsCss)) {
|
|
41
|
+
bundleParts.push(fs.readFileSync(tokensColorsCss, "utf-8"));
|
|
42
|
+
console.log("✅ Bundled colors.css from md3-tokens into index.css");
|
|
43
|
+
} else {
|
|
44
|
+
console.warn("⚠️ md3-tokens colors.css not found — build tokens first");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (fs.existsSync(tokensShapeCss)) {
|
|
48
|
+
bundleParts.push(fs.readFileSync(tokensShapeCss, "utf-8"));
|
|
49
|
+
console.log("✅ Bundled shape.css from md3-tokens into index.css");
|
|
50
|
+
} else {
|
|
51
|
+
console.warn("⚠️ md3-tokens shape.css not found — build tokens first");
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
if (fs.existsSync(srcIndexCss)) {
|
|
30
|
-
|
|
31
|
-
|
|
55
|
+
bundleParts.push(
|
|
56
|
+
"\n/* @bug-on/md3-react — Component Base Reset */",
|
|
57
|
+
fs.readFileSync(srcIndexCss, "utf-8"),
|
|
58
|
+
);
|
|
32
59
|
}
|
|
33
60
|
|
|
61
|
+
fs.writeFileSync(distIndexCss, bundleParts.join("\n"));
|
|
62
|
+
console.log("✅ Built bundled index.css (tokens + component base) to dist/index.css");
|
|
63
|
+
|
|
64
|
+
|
|
34
65
|
// Copy Material Symbols CSS variants
|
|
35
66
|
const cssVariants = [
|
|
36
67
|
"material-symbols-cdn.css",
|
|
@@ -62,7 +93,9 @@ for (const stub of cssStubs) {
|
|
|
62
93
|
console.log(`✅ Generated ${stub}`);
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
// Prepend "use client" to JS/MJS output files to support React Server Components
|
|
96
|
+
// Prepend "use client" to JS/MJS output files to support React Server Components.
|
|
97
|
+
// NOTE: Using post-build script instead of tsup banner — banner causes esbuild
|
|
98
|
+
// "Module level directives" warnings in library bundling context.
|
|
66
99
|
const distFiles = [
|
|
67
100
|
path.join(__dirname, "../dist/index.js"),
|
|
68
101
|
path.join(__dirname, "../dist/index.mjs"),
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { useRipple as useDOMRipple } from "./hooks/useRipple";
|
|
|
5
5
|
export { MaterialSymbolsPreconnect } from "./lib/material-symbols-preconnect";
|
|
6
6
|
// Theme — MD3 Dynamic Color
|
|
7
7
|
export type { MD3ColorScheme, ThemeMode } from "./lib/theme-utils";
|
|
8
|
-
export { applyTheme, generateM3Theme } from "./lib/theme-utils";
|
|
8
|
+
export { applyTheme, generateM3Theme, resolveMode } from "./lib/theme-utils";
|
|
9
9
|
// Utils
|
|
10
10
|
export { cn } from "./lib/utils";
|
|
11
11
|
// Types
|
package/src/lib/theme-utils.ts
CHANGED
|
@@ -4,7 +4,20 @@ import {
|
|
|
4
4
|
themeFromSourceColor,
|
|
5
5
|
} from "@material/material-color-utilities";
|
|
6
6
|
|
|
7
|
-
export type ThemeMode = "light" | "dark";
|
|
7
|
+
export type ThemeMode = "light" | "dark" | "system";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the effective color scheme from a ThemeMode.
|
|
11
|
+
* When mode is "system", reads the OS preference via matchMedia.
|
|
12
|
+
* Returns "light" as the safe default in SSR environments.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveMode(mode: ThemeMode): "light" | "dark" {
|
|
15
|
+
if (mode !== "system") return mode;
|
|
16
|
+
if (typeof window === "undefined") return "light";
|
|
17
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
18
|
+
? "dark"
|
|
19
|
+
: "light";
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
export interface MD3ColorScheme {
|
|
10
23
|
primary: string;
|
|
@@ -70,7 +83,7 @@ export interface MD3ColorScheme {
|
|
|
70
83
|
*/
|
|
71
84
|
export function generateM3Theme(
|
|
72
85
|
sourceColorHex: string,
|
|
73
|
-
mode:
|
|
86
|
+
mode: "light" | "dark" = "light",
|
|
74
87
|
): MD3ColorScheme {
|
|
75
88
|
const sourceColor = argbFromHex(sourceColorHex);
|
|
76
89
|
const theme = themeFromSourceColor(sourceColor);
|
|
@@ -168,7 +181,8 @@ export function applyTheme(
|
|
|
168
181
|
mode: ThemeMode = "light",
|
|
169
182
|
root: HTMLElement = document.documentElement,
|
|
170
183
|
): void {
|
|
171
|
-
const
|
|
184
|
+
const resolved = resolveMode(mode);
|
|
185
|
+
const colors = generateM3Theme(sourceColorHex, resolved);
|
|
172
186
|
|
|
173
187
|
for (const [key, value] of Object.entries(colors)) {
|
|
174
188
|
const kebabKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
@@ -176,5 +190,6 @@ export function applyTheme(
|
|
|
176
190
|
root.style.setProperty(`--color-m3-${kebabKey}`, value);
|
|
177
191
|
}
|
|
178
192
|
|
|
179
|
-
|
|
193
|
+
// data-theme is always "light" or "dark" — never "system"
|
|
194
|
+
root.setAttribute("data-theme", resolved);
|
|
180
195
|
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Re-export the MD3 Tailwind plugin from @bug-on/md3-tailwind.
|
|
2
|
+
// This allows users to reference the plugin directly from @bug-on/md3-react
|
|
3
|
+
// without installing @bug-on/md3-tailwind separately.
|
|
4
|
+
//
|
|
5
|
+
// Usage in CSS (Tailwind v4):
|
|
6
|
+
// @import "@bug-on/md3-react/index.css";
|
|
7
|
+
// @plugin "@bug-on/md3-react/plugin";
|
|
8
|
+
//
|
|
9
|
+
// Usage in tailwind.config.ts (Tailwind v3):
|
|
10
|
+
// import md3Plugin from "@bug-on/md3-react/plugin";
|
|
11
|
+
// export default { plugins: [md3Plugin] };
|
|
12
|
+
export { default } from "@bug-on/md3-tailwind";
|
package/src/ui/button.test.tsx
CHANGED
|
@@ -161,22 +161,20 @@ describe("Button Component", () => {
|
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
describe("sentence-case label", () => {
|
|
167
|
-
it("converts ALL CAPS to sentence-case", () => {
|
|
164
|
+
describe("label case", () => {
|
|
165
|
+
it("preserves ALL CAPS", () => {
|
|
168
166
|
render(<Button>HELLO WORLD</Button>);
|
|
169
|
-
expect(screen.getByRole("button")).toHaveTextContent("
|
|
167
|
+
expect(screen.getByRole("button")).toHaveTextContent("HELLO WORLD");
|
|
170
168
|
});
|
|
171
169
|
|
|
172
|
-
it("
|
|
170
|
+
it("preserves all-lowercase", () => {
|
|
173
171
|
render(<Button>hello world</Button>);
|
|
174
|
-
expect(screen.getByRole("button")).toHaveTextContent("
|
|
172
|
+
expect(screen.getByRole("button")).toHaveTextContent("hello world");
|
|
175
173
|
});
|
|
176
174
|
|
|
177
|
-
it("
|
|
175
|
+
it("preserves mixed case", () => {
|
|
178
176
|
render(<Button>hElLo WoRlD</Button>);
|
|
179
|
-
expect(screen.getByRole("button")).toHaveTextContent("
|
|
177
|
+
expect(screen.getByRole("button")).toHaveTextContent("hElLo WoRlD");
|
|
180
178
|
});
|
|
181
179
|
});
|
|
182
180
|
|
|
@@ -215,7 +213,7 @@ describe("Button Component", () => {
|
|
|
215
213
|
s.querySelector("[data-testid='test-icon']"),
|
|
216
214
|
);
|
|
217
215
|
const labelIndex = spans.findIndex((s) =>
|
|
218
|
-
s.textContent?.includes("
|
|
216
|
+
s.textContent?.includes("RTL Trailing"),
|
|
219
217
|
);
|
|
220
218
|
// DOM order is still trailing (CSS handles visual flip in RTL via flex)
|
|
221
219
|
expect(iconIndex).toBeGreaterThan(labelIndex);
|
|
@@ -270,6 +268,17 @@ describe("Button Component", () => {
|
|
|
270
268
|
"transition-[background-color,color,border-color,box-shadow,opacity,filter]",
|
|
271
269
|
);
|
|
272
270
|
});
|
|
271
|
+
|
|
272
|
+
it("applies h-full to the label wrapper for vertical alignment", () => {
|
|
273
|
+
render(
|
|
274
|
+
<Button asChild>
|
|
275
|
+
<a href="/test">Aligned Link</a>
|
|
276
|
+
</Button>,
|
|
277
|
+
);
|
|
278
|
+
const link = screen.getByRole("link");
|
|
279
|
+
const labelWrapper = link.querySelector("span.inline-flex.items-center");
|
|
280
|
+
expect(labelWrapper).toHaveClass("h-full");
|
|
281
|
+
});
|
|
273
282
|
});
|
|
274
283
|
|
|
275
284
|
// ── New: loading prop ──────────────────────────────────────────────────────
|
package/src/ui/button.tsx
CHANGED
|
@@ -294,10 +294,6 @@ export type ButtonProps = BaseButtonProps &
|
|
|
294
294
|
|
|
295
295
|
// ─── Helpers ───────────────────────────────────────────────────────────────────
|
|
296
296
|
|
|
297
|
-
function toSentenceCase(text: string): string {
|
|
298
|
-
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
297
|
function resolveLabel(
|
|
302
298
|
children: React.ReactNode,
|
|
303
299
|
asChild: boolean,
|
|
@@ -308,7 +304,7 @@ function resolveLabel(
|
|
|
308
304
|
}>;
|
|
309
305
|
return child.props.children;
|
|
310
306
|
}
|
|
311
|
-
return
|
|
307
|
+
return children;
|
|
312
308
|
}
|
|
313
309
|
|
|
314
310
|
/** Framer Motion-specific props to strip before forwarding to a plain DOM element. */
|
|
@@ -550,7 +546,7 @@ const ButtonComponent = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
550
546
|
|
|
551
547
|
<m.span
|
|
552
548
|
layout="size"
|
|
553
|
-
className="inline-flex items-center gap-[inherit]"
|
|
549
|
+
className="inline-flex items-center h-full gap-[inherit]"
|
|
554
550
|
transition={SPRING_TRANSITION}
|
|
555
551
|
>
|
|
556
552
|
{labelText}
|
|
@@ -5,7 +5,10 @@ import { createPortal } from "react-dom";
|
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
import { Icon } from "./icon";
|
|
7
7
|
import { Ripple, useRippleState } from "./ripple";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
SPRING_TRANSITION,
|
|
10
|
+
SPRING_TRANSITION_EXPRESSIVE,
|
|
11
|
+
} from "./shared/constants";
|
|
9
12
|
import { TouchTarget } from "./shared/touch-target";
|
|
10
13
|
|
|
11
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -137,11 +140,11 @@ function ActivePill({ layoutId, disableInitial = false }: ActivePillProps) {
|
|
|
137
140
|
<m.div
|
|
138
141
|
layoutId={layoutId}
|
|
139
142
|
className="absolute inset-0 bg-m3-secondary-container pointer-events-none"
|
|
140
|
-
style={{ borderRadius: 9999, zIndex: 0 }}
|
|
141
|
-
initial={disableInitial ? false : { opacity: 0 }}
|
|
142
|
-
animate={{ opacity: 1 }}
|
|
143
|
-
exit={{ opacity: 0 }}
|
|
144
|
-
transition={
|
|
143
|
+
style={{ borderRadius: 9999, zIndex: 0, originX: 0.5, originY: 0.5 }}
|
|
144
|
+
initial={disableInitial ? false : { opacity: 0, scale: 0.5 }}
|
|
145
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
146
|
+
exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.15 } }}
|
|
147
|
+
transition={SPRING_TRANSITION_EXPRESSIVE}
|
|
145
148
|
/>
|
|
146
149
|
);
|
|
147
150
|
}
|
|
@@ -48,6 +48,19 @@ export const SPRING_TRANSITION: Transition = {
|
|
|
48
48
|
duration: 0.3,
|
|
49
49
|
} as const;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* MD3 Expressive spring — active indicator expand/collapse.
|
|
53
|
+
* Higher bounce for the "pop" effect per MD3 Expressive spec.
|
|
54
|
+
*
|
|
55
|
+
* - Duration: 400ms
|
|
56
|
+
* - Bounce: 0.35 (spring overshoot → lò xo)
|
|
57
|
+
*/
|
|
58
|
+
export const SPRING_TRANSITION_EXPRESSIVE: Transition = {
|
|
59
|
+
type: "spring",
|
|
60
|
+
bounce: 0.35,
|
|
61
|
+
duration: 0.4,
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
51
64
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
65
|
// Icon Span Motion Variants
|
|
53
66
|
// Used for icon/loading indicator swap animation inside FAB and IconButton.
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
useMemo,
|
|
10
10
|
useState,
|
|
11
11
|
} from "react";
|
|
12
|
-
import { applyTheme, type ThemeMode } from "../../lib/theme-utils";
|
|
12
|
+
import { applyTheme, resolveMode, type ThemeMode } from "../../lib/theme-utils";
|
|
13
13
|
import {
|
|
14
14
|
SnackbarContext,
|
|
15
15
|
SnackbarHost,
|
|
@@ -28,6 +28,8 @@ interface ThemeContextValue {
|
|
|
28
28
|
setSourceColor: (color: string) => void;
|
|
29
29
|
mode: ThemeMode;
|
|
30
30
|
setMode: (mode: ThemeMode) => void;
|
|
31
|
+
/** The resolved color scheme actually applied — always "light" or "dark". */
|
|
32
|
+
effectiveMode: "light" | "dark";
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
@@ -104,7 +106,12 @@ export function MD3ThemeProvider({
|
|
|
104
106
|
) as ThemeMode | null;
|
|
105
107
|
|
|
106
108
|
if (savedColor) setSourceColor(savedColor);
|
|
107
|
-
if (
|
|
109
|
+
if (
|
|
110
|
+
savedMode === "light" ||
|
|
111
|
+
savedMode === "dark" ||
|
|
112
|
+
savedMode === "system"
|
|
113
|
+
)
|
|
114
|
+
setMode(savedMode);
|
|
108
115
|
|
|
109
116
|
setIsHydrated(true);
|
|
110
117
|
}, [persistToLocalStorage]);
|
|
@@ -120,9 +127,24 @@ export function MD3ThemeProvider({
|
|
|
120
127
|
}
|
|
121
128
|
}, [sourceColor, mode, persistToLocalStorage, isHydrated]);
|
|
122
129
|
|
|
130
|
+
// ── System preference subscription ───────────────────────────────────────
|
|
131
|
+
// When mode is "system", listen for OS-level dark/light changes in real time
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (mode !== "system" || typeof window === "undefined") return;
|
|
134
|
+
|
|
135
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
136
|
+
const handleChange = () => applyTheme(sourceColor, "system");
|
|
137
|
+
|
|
138
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
139
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
140
|
+
}, [mode, sourceColor]);
|
|
141
|
+
|
|
142
|
+
// ── Derived effective mode (no extra state needed) ────────────────────────
|
|
143
|
+
const effectiveMode = resolveMode(mode);
|
|
144
|
+
|
|
123
145
|
const themeValue = useMemo<ThemeContextValue>(
|
|
124
|
-
() => ({ sourceColor, setSourceColor, mode, setMode }),
|
|
125
|
-
[sourceColor, mode],
|
|
146
|
+
() => ({ sourceColor, setSourceColor, mode, setMode, effectiveMode }),
|
|
147
|
+
[sourceColor, mode, effectiveMode],
|
|
126
148
|
);
|
|
127
149
|
|
|
128
150
|
// ── Typography value ─────────────────────────────────────────────────────
|
|
@@ -184,7 +206,10 @@ export function useTheme(): ThemeContextValue {
|
|
|
184
206
|
return context;
|
|
185
207
|
}
|
|
186
208
|
|
|
187
|
-
export function useThemeMode(): Pick<
|
|
188
|
-
|
|
189
|
-
|
|
209
|
+
export function useThemeMode(): Pick<
|
|
210
|
+
ThemeContextValue,
|
|
211
|
+
"mode" | "setMode" | "effectiveMode"
|
|
212
|
+
> {
|
|
213
|
+
const { mode, setMode, effectiveMode } = useTheme();
|
|
214
|
+
return { mode, setMode, effectiveMode };
|
|
190
215
|
}
|
package/tsup.config.ts
CHANGED
|
@@ -2,18 +2,18 @@ import type { Options } from "tsup";
|
|
|
2
2
|
import { defineConfig } from "tsup";
|
|
3
3
|
|
|
4
4
|
const config: Options = {
|
|
5
|
-
entry: ["src/index.ts"],
|
|
5
|
+
entry: ["src/index.ts", "src/plugin.ts"],
|
|
6
6
|
format: ["cjs", "esm"],
|
|
7
7
|
dts: true,
|
|
8
8
|
clean: false, // Tắt clean để tránh nuke mất typography.css khi Next.js đang khởi động
|
|
9
9
|
// externalize peer dependencies — user project sẽ cung cấp
|
|
10
|
-
external: ["react", "react-dom", "motion"],
|
|
10
|
+
external: ["react", "react-dom", "motion", "tailwindcss"],
|
|
11
11
|
// Tree-shaking tối đa, không split vì thư viện nhỏ
|
|
12
12
|
treeshake: true,
|
|
13
13
|
splitting: false,
|
|
14
14
|
sourcemap: true,
|
|
15
15
|
outDir: "dist",
|
|
16
|
-
// "use client"
|
|
16
|
+
// "use client" được inject sau build bằng copy-assets.js (banner của tsup gây esbuild warning cho library)
|
|
17
17
|
onSuccess: "node scripts/copy-assets.js",
|
|
18
18
|
};
|
|
19
19
|
|