diff --git a/apps/storybook/.flowbite-react/config.json b/apps/storybook/.flowbite-react/config.json
index c987f54cc..f390506a7 100644
--- a/apps/storybook/.flowbite-react/config.json
+++ b/apps/storybook/.flowbite-react/config.json
@@ -5,5 +5,6 @@
"path": "src/components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 3
}
diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.tsx
similarity index 57%
rename from apps/storybook/.storybook/preview.ts
rename to apps/storybook/.storybook/preview.tsx
index bd89877ad..6094bedf3 100644
--- a/apps/storybook/.storybook/preview.ts
+++ b/apps/storybook/.storybook/preview.tsx
@@ -1,5 +1,8 @@
import { withThemeByClassName } from "@storybook/addon-themes";
-import type { Preview } from "@storybook/react";
+import type { Decorator, Preview } from "@storybook/react";
+import { ThemeConfig } from "flowbite-react";
+import React from "react";
+import config from "../.flowbite-react/config.json";
import "./style.css";
@@ -17,7 +20,13 @@ const preview: Preview = {
},
};
-export const decorators = [
+export const decorators: Decorator[] = [
+ (Story) => (
+ <>
+
+
+ >
+ ),
withThemeByClassName({
themes: {
light: "light",
diff --git a/apps/web/.flowbite-react/config.json b/apps/web/.flowbite-react/config.json
index 5145e8553..7f63698c2 100644
--- a/apps/web/.flowbite-react/config.json
+++ b/apps/web/.flowbite-react/config.json
@@ -5,5 +5,6 @@
"path": "components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 3
}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 65fb77e9d..80e6efcc4 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,7 +1,8 @@
-import { ThemeModeScript } from "flowbite-react";
+import { ThemeConfig, ThemeModeScript } from "flowbite-react";
import { Inter as InterFont } from "next/font/google";
import type { Metadata, Viewport } from "next/types";
import type { PropsWithChildren } from "react";
+import config from "~/.flowbite-react/config.json";
import { FathomScript } from "~/components/fathom-script";
import "~/styles/globals.css";
@@ -60,6 +61,7 @@ export default function RootLayout({ children }: PropsWithChildren) {
+
{children}
diff --git a/apps/web/content/docs/customize/config.mdx b/apps/web/content/docs/customize/config.mdx
index 1d4415414..e77e7c5a1 100644
--- a/apps/web/content/docs/customize/config.mdx
+++ b/apps/web/content/docs/customize/config.mdx
@@ -40,7 +40,8 @@ The configuration file follows this JSON Schema:
},
"prefix": {
"description": "Optional prefix to apply to all Tailwind CSS classes",
- "type": "string"
+ "type": "string",
+ "default": ""
},
"rsc": {
"description": "Whether to include the 'use client' directive for React Server Components",
@@ -51,9 +52,15 @@ The configuration file follows this JSON Schema:
"description": "Whether to use TypeScript (.tsx) or JavaScript (.jsx) for component creation",
"type": "boolean",
"default": true
+ },
+ "version": {
+ "description": "The version of Tailwind CSS to use",
+ "type": "number",
+ "enum": [3, 4],
+ "default": 4
}
},
- "required": ["components", "dark", "path", "prefix", "rsc", "tsx"]
+ "required": ["components", "dark", "path", "prefix", "rsc", "tsx", "version"]
}
```
@@ -101,6 +108,13 @@ For detailed instructions on configuring and using prefixes, see the [Prefix](/d
- Default: `true`
- Description: Whether to use TypeScript (.tsx) or JavaScript (.jsx) for component creation. When set to `false`, components will be created with .jsx extension.
+#### `version`
+
+- Type: `number`
+- Options: `3`, `4`
+- Default: `4`
+- Description: The version of Tailwind CSS to use.
+
## Automatic Class Generation
The automatic class generation system works in two modes:
@@ -120,10 +134,12 @@ Example config for automatic mode:
{
"$schema": "https://unpkg.com/flowbite-react/schema.json",
"components": [],
+ "dark": true,
"path": "src/components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 4
}
```
@@ -142,10 +158,12 @@ Example config for manual mode:
{
"$schema": "https://unpkg.com/flowbite-react/schema.json",
"components": ["Button", "Card", "Modal"],
+ "dark": true,
"path": "src/components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 4
}
```
diff --git a/apps/web/content/docs/customize/custom-components.mdx b/apps/web/content/docs/customize/custom-components.mdx
index 261f4b305..69f63d8c0 100644
--- a/apps/web/content/docs/customize/custom-components.mdx
+++ b/apps/web/content/docs/customize/custom-components.mdx
@@ -25,10 +25,12 @@ You can customize these options in your config file `.flowbite-react/config.json
{
"$schema": "https://unpkg.com/flowbite-react/schema.json",
"components": [],
+ "dark": true,
"path": "src/components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 4
}
```
diff --git a/apps/web/content/docs/customize/dark-mode.mdx b/apps/web/content/docs/customize/dark-mode.mdx
index 4b055fd68..4c4b2d25b 100644
--- a/apps/web/content/docs/customize/dark-mode.mdx
+++ b/apps/web/content/docs/customize/dark-mode.mdx
@@ -86,7 +86,8 @@ Additionally, you must disable dark mode in your build configuration by setting
"path": "src/components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 4
}
```
diff --git a/apps/web/content/docs/customize/prefix.mdx b/apps/web/content/docs/customize/prefix.mdx
index 12a14a5d4..bf5dec651 100644
--- a/apps/web/content/docs/customize/prefix.mdx
+++ b/apps/web/content/docs/customize/prefix.mdx
@@ -19,7 +19,8 @@ To set a custom prefix for Flowbite React components, modify the `prefix` proper
"path": "components",
"prefix": "tw",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 4
}
```
diff --git a/apps/web/content/docs/getting-started/cli.mdx b/apps/web/content/docs/getting-started/cli.mdx
index a9b5050c2..1f7d91211 100644
--- a/apps/web/content/docs/getting-started/cli.mdx
+++ b/apps/web/content/docs/getting-started/cli.mdx
@@ -12,7 +12,6 @@ The Flowbite React CLI provides a comprehensive set of tools for:
- Managing development workflows
- Handling class generation
- Configuring your development environment
-- Patching Tailwind CSS configurations
- Providing help and documentation
## Installation & Setup
@@ -149,15 +148,6 @@ import { AccordionPanel } from "flowbite-react";
...
```
-### `flowbite-react patch`
-
-Patches Tailwind CSS to expose its version number for compatibility detection:
-
-- Creates a JavaScript file that exports the Tailwind CSS version
-- Necessary because package.json cannot be reliably read by all bundlers
-- Makes the version accessible via `import version from "tailwindcss/version"`
-- Enables Flowbite React to adapt its behavior based on the installed Tailwind version
-
### `flowbite-react register`
Registers the Flowbite React process for development:
@@ -232,7 +222,8 @@ The CLI creates a `.flowbite-react/config.json` file with several configuration
"path": "src/components",
"prefix": "",
"rsc": true,
- "tsx": true
+ "tsx": true,
+ "version": 4
}
```
@@ -260,6 +251,10 @@ Whether to include the `"use client"` directive for React Server Components. Def
Whether to use TypeScript (.tsx) or JavaScript (.jsx) for component creation. Default is `true`.
+#### `version`
+
+The version of Tailwind CSS to use. Default is `4`.
+
### VSCode Integration
The CLI sets up VSCode for optimal development:
diff --git a/bun.lock b/bun.lock
index 6b9ee8d64..304f5aa56 100644
--- a/bun.lock
+++ b/bun.lock
@@ -109,7 +109,7 @@
},
"packages/ui": {
"name": "flowbite-react",
- "version": "0.11.5",
+ "version": "0.11.7",
"bin": {
"flowbite-react": "./dist/cli/bin.js",
},
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 455c5ec27..60b3f6fac 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -212,7 +212,6 @@
"format": "prettier . --write",
"format:check": "prettier . --check",
"generate-metadata": "bun scripts/generate-metadata.ts",
- "postinstall": "bun run src/cli/bin.ts patch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepack": "clean-package",
diff --git a/packages/ui/schema.json b/packages/ui/schema.json
index f64531788..0d43ff966 100644
--- a/packages/ui/schema.json
+++ b/packages/ui/schema.json
@@ -128,7 +128,8 @@
},
"prefix": {
"description": "Optional prefix to apply to all Tailwind CSS classes. \nSee https://flowbite-react.com/docs/customize/config#prefix for more details.",
- "type": "string"
+ "type": "string",
+ "default": ""
},
"rsc": {
"description": "Whether to include the 'use client' directive for React Server Components. \nSee https://flowbite-react.com/docs/customize/config#rsc for more details.",
@@ -139,7 +140,13 @@
"description": "Whether to use TypeScript (.tsx) or JavaScript (.jsx) for component creation. \nSee https://flowbite-react.com/docs/customize/config#tsx for more details.",
"type": "boolean",
"default": true
+ },
+ "version": {
+ "description": "The version of Tailwind CSS to use. \nSee https://flowbite-react.com/docs/customize/config#version for more details.",
+ "type": "number",
+ "enum": [3, 4],
+ "default": 4
}
},
- "required": ["components", "dark", "path", "prefix", "rsc", "tsx"]
+ "required": ["components", "dark", "path", "prefix", "rsc", "tsx", "version"]
}
diff --git a/packages/ui/scripts/generate-metadata.ts b/packages/ui/scripts/generate-metadata.ts
index c49c43281..7bee09691 100644
--- a/packages/ui/scripts/generate-metadata.ts
+++ b/packages/ui/scripts/generate-metadata.ts
@@ -235,6 +235,7 @@ async function generateSchema(components: string[]): Promise {
description:
"Optional prefix to apply to all Tailwind CSS classes. \nSee https://flowbite-react.com/docs/customize/config#prefix for more details.",
type: "string",
+ default: "",
},
rsc: {
description:
@@ -248,8 +249,15 @@ async function generateSchema(components: string[]): Promise {
type: "boolean",
default: true,
},
+ version: {
+ description:
+ "The version of Tailwind CSS to use. \nSee https://flowbite-react.com/docs/customize/config#version for more details.",
+ type: "number",
+ enum: [3, 4],
+ default: 4,
+ },
},
- required: ["components", "dark", "path", "prefix", "rsc", "tsx"],
+ required: ["components", "dark", "path", "prefix", "rsc", "tsx", "version"],
};
defaultSchema.properties.components.items.enum.push(...components);
diff --git a/packages/ui/src/cli/commands/build.ts b/packages/ui/src/cli/commands/build.ts
index 35916958a..c699dde02 100644
--- a/packages/ui/src/cli/commands/build.ts
+++ b/packages/ui/src/cli/commands/build.ts
@@ -1,7 +1,9 @@
+import { syncTailwindVersion } from "../utils/sync-tailwind-version";
import { generateClassList } from "./generate-class-list";
import { setupOutputDirectory } from "./setup-output-directory";
export async function build() {
await setupOutputDirectory();
+ await syncTailwindVersion();
await generateClassList();
}
diff --git a/packages/ui/src/cli/commands/dev.ts b/packages/ui/src/cli/commands/dev.ts
index 714ff8e27..240d4551d 100644
--- a/packages/ui/src/cli/commands/dev.ts
+++ b/packages/ui/src/cli/commands/dev.ts
@@ -13,8 +13,10 @@ import { buildClassList } from "../utils/build-class-list";
import { extractComponentImports } from "../utils/extract-component-imports";
import { getClassList } from "../utils/get-class-list";
import { getConfig } from "../utils/get-config";
+import { syncTailwindVersion } from "../utils/sync-tailwind-version";
export async function dev() {
+ await syncTailwindVersion();
const config = await getConfig();
if (config.components.length) {
@@ -46,6 +48,7 @@ export async function dev() {
components: config.components.length ? config.components : newImportedComponents,
dark: config.dark,
prefix: config.prefix,
+ version: config.version,
});
if (!isEqual(classList, newClassList)) {
diff --git a/packages/ui/src/cli/commands/ensure-tailwind.ts b/packages/ui/src/cli/commands/ensure-tailwind.ts
index 28537837b..e14ea2d93 100644
--- a/packages/ui/src/cli/commands/ensure-tailwind.ts
+++ b/packages/ui/src/cli/commands/ensure-tailwind.ts
@@ -1,10 +1,13 @@
import { getPackageJson } from "../utils/get-package-json";
+/**
+ * Requires Tailwind CSS to be installed in the project.
+ */
export async function ensureTailwind() {
+ // TODO: runtime check
const packageJson = await getPackageJson();
- const tailwindVersion = packageJson?.dependencies?.["tailwindcss"] || packageJson?.devDependencies?.["tailwindcss"];
- if (!tailwindVersion) {
+ if (!(packageJson?.dependencies?.["tailwindcss"] || packageJson?.devDependencies?.["tailwindcss"])) {
console.error("Install Tailwind CSS first.\n\nSee: https://tailwindcss.com/docs/installation");
process.exit(1);
}
diff --git a/packages/ui/src/cli/commands/help.ts b/packages/ui/src/cli/commands/help.ts
index 538b4d8a1..f370b3c02 100644
--- a/packages/ui/src/cli/commands/help.ts
+++ b/packages/ui/src/cli/commands/help.ts
@@ -10,7 +10,6 @@ Commands:
init Initialize Flowbite React with config files and necessary setup
install Install Flowbite React using your detected package manager
migrate Run code transformations to update to latest patterns and APIs
- patch Patch Tailwind CSS to expose version number for compatibility
register Register Flowbite React process for development with automatic class generation
setup Setup additional features and configurations
diff --git a/packages/ui/src/cli/commands/init.ts b/packages/ui/src/cli/commands/init.ts
index 02d5ee548..7fc260132 100644
--- a/packages/ui/src/cli/commands/init.ts
+++ b/packages/ui/src/cli/commands/init.ts
@@ -1,11 +1,9 @@
import { ensureTailwind } from "./ensure-tailwind";
-import { installFlowbiteReact } from "./install";
-import { patchTailwind } from "./patch";
+import { installPackage } from "./install";
import { setupClassList } from "./setup-class-list";
import { setupConfig } from "./setup-config";
import { setupGitIgnore } from "./setup-gitignore";
import { setupOutputDirectory } from "./setup-output-directory";
-import { setupPatch } from "./setup-patch";
import { setupPlugin } from "./setup-plugin";
import { setupRegister } from "./setup-register";
import { setupTailwind } from "./setup-tailwind";
@@ -13,44 +11,18 @@ import { setupVSCode } from "./setup-vscode";
export async function init() {
try {
- // require `tailwindcss`
await ensureTailwind();
-
- // patch `tailwindcss`
- await patchTailwind();
-
- // install `flowbite-react`
- await installFlowbiteReact();
-
- // setup patch script in `package.json`
- await setupPatch();
-
- // setup `tailwindcss`
- await setupTailwind();
-
- // setup `.flowbite-react` directory
+ await installPackage();
await setupOutputDirectory();
-
- // setup `.flowbite-react/class-list.json` file
+ await setupGitIgnore();
await setupClassList();
-
- // setup `.flowbite-react/config.json` file
await setupConfig();
-
- // setup `.flowbite-react/.gitignore` file
- await setupGitIgnore();
-
- // setup VSCode intellisense
await setupVSCode();
-
- // setup plugin based on bundler
+ await setupTailwind();
const hasBundler = await setupPlugin();
-
if (!hasBundler) {
- // setup register script in `package.json`
await setupRegister();
}
-
console.log("\n✅ Flowbite React has been successfully initialized!");
} catch (error) {
console.error("An error occurred during initialization:", error);
diff --git a/packages/ui/src/cli/commands/install.ts b/packages/ui/src/cli/commands/install.ts
index d8a39574c..3fe4122d8 100644
--- a/packages/ui/src/cli/commands/install.ts
+++ b/packages/ui/src/cli/commands/install.ts
@@ -3,11 +3,17 @@ import { detect } from "package-manager-detector/detect";
import { execCommand } from "../utils/exec-command";
import { getPackageJson } from "../utils/get-package-json";
-export async function installFlowbiteReact() {
+/**
+ * Installs `flowbite-react` package using the detected package manager.
+ */
+export async function installPackage() {
+ const packageName = "flowbite-react";
+
try {
const packageJson = await getPackageJson();
- if (packageJson.dependencies?.["flowbite-react"] || packageJson.devDependencies?.["flowbite-react"]) {
+ if (packageJson.dependencies?.[packageName] || packageJson.devDependencies?.[packageName]) {
+ // TODO: prompt to bump the version to latest
return;
}
@@ -19,12 +25,11 @@ export async function installFlowbiteReact() {
pm ??= { agent: "npm", name: "npm" };
- const packageName = "flowbite-react";
const { command = "", args } = resolveCommand(pm.agent, "add", [packageName]) ?? {};
console.log(`Installing ${packageName} using ${pm.name}...`);
execCommand(command, args);
} catch (error) {
- console.error("Failed to install flowbite-react:", error);
+ console.error(`Failed to install ${packageName}:`, error);
}
}
diff --git a/packages/ui/src/cli/commands/patch.ts b/packages/ui/src/cli/commands/patch.ts
deleted file mode 100644
index e2945048b..000000000
--- a/packages/ui/src/cli/commands/patch.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import fs from "fs/promises";
-import path from "path";
-
-/**
- * Patches Tailwind CSS installation to ensure version files exist and are correctly configured.
- *
- * This function:
- * - Resolves the Tailwind CSS module path
- * - Reads the Tailwind package.json to get the actual version
- * - Creates or updates version files (version.js, version.mjs, version.d.ts, version.d.mts)
- * - Updates package.json exports if necessary
- *
- * @returns {Promise} A promise that resolves when patching is complete
- */
-export async function patchTailwind(): Promise {
- try {
- let tailwindPath: string | undefined;
-
- try {
- let tailwindModulePath;
- if (typeof require !== "undefined") {
- tailwindModulePath = require.resolve("tailwindcss/package.json", {
- paths: [process.cwd()],
- });
- tailwindPath = path.resolve(path.dirname(tailwindModulePath));
- } else {
- const { createRequire } = await import("module");
- const require = createRequire(import.meta.url);
- tailwindModulePath = require.resolve("tailwindcss/package.json", {
- paths: [process.cwd()],
- });
- tailwindPath = path.resolve(path.dirname(tailwindModulePath));
- }
- } catch {
- console.warn("Could not resolve Tailwind CSS module path. Skipping version patch.");
- return;
- }
-
- const tailwindPackageJsonPath = path.join(tailwindPath, "package.json");
- let tailwindPackageJson: {
- version: string;
- exports?: Record;
- };
-
- try {
- const packageJsonContent = await fs.readFile(tailwindPackageJsonPath, "utf-8");
- tailwindPackageJson = JSON.parse(packageJsonContent);
- } catch {
- console.warn("Could not read Tailwind CSS `package.json`. Skipping version patch.");
- return;
- }
-
- const actualVersion = tailwindPackageJson.version;
-
- // Check if version files exist and have the correct version
- const versionFilePath = path.join(tailwindPath, "version.js");
- const versionMjsFilePath = path.join(tailwindPath, "version.mjs");
- const versionDtsFilePath = path.join(tailwindPath, "version.d.ts");
- const versionDmtsFilePath = path.join(tailwindPath, "version.d.mts");
-
- let patchesApplied = false;
-
- // create `version.js`, `version.mjs`, `version.d.ts` and `version.d.mts` files if needed
- try {
- let filesCreated = false;
-
- // Check and create `version.js` file (CJS)
- if (await shouldUpdateFile(versionFilePath, actualVersion)) {
- const versionContent = `"use strict";\n\nconst version = "${actualVersion}";\nmodule.exports = version;\n`;
- await fs.writeFile(versionFilePath, versionContent, "utf-8");
- filesCreated = true;
- }
-
- // Check and create `version.mjs` file (ESM)
- if (await shouldUpdateFile(versionMjsFilePath, actualVersion)) {
- const versionMjsContent = `const version = "${actualVersion}";\nexport default version;\n`;
- await fs.writeFile(versionMjsFilePath, versionMjsContent, "utf-8");
- filesCreated = true;
- }
-
- // Check and create `version.d.ts` file
- if (await shouldUpdateFile(versionDtsFilePath)) {
- const versionDtsContent = `declare const version: string;\nexport = version;\n`;
- await fs.writeFile(versionDtsFilePath, versionDtsContent, "utf-8");
- filesCreated = true;
- }
-
- // Check and create `version.d.mts` file
- if (await shouldUpdateFile(versionDmtsFilePath)) {
- const versionDmtsContent = `declare const version: string;\nexport default version;\n`;
- await fs.writeFile(versionDmtsFilePath, versionDmtsContent, "utf-8");
- filesCreated = true;
- }
-
- if (filesCreated) {
- patchesApplied = true;
- }
- } catch {
- console.warn("Could not create Tailwind CSS version files. Skipping version patch.");
- }
-
- // patch package.json.exports
- try {
- if (tailwindPackageJson.exports) {
- if (!tailwindPackageJson.exports["./version"] || !tailwindPackageJson.exports["./version.js"]) {
- tailwindPackageJson.exports = {
- ...tailwindPackageJson.exports,
- "./version": {
- require: "./version.js",
- import: "./version.mjs",
- },
- "./version.js": {
- require: "./version.js",
- import: "./version.mjs",
- },
- };
- await fs.writeFile(tailwindPackageJsonPath, JSON.stringify(tailwindPackageJson, null, 2), "utf-8");
- patchesApplied = true;
- }
- }
- } catch {
- console.warn("Could not patch Tailwind CSS `package.json.exports`. Skipping version patch.");
- }
-
- if (patchesApplied) {
- console.log("Patched Tailwind CSS");
- }
- } catch (error) {
- console.error("Failed to patch Tailwind CSS:", error);
- }
-}
-
-/**
- * Determines whether a file should be updated based on its existence and content.
- *
- * @param filePath - The path to the file to check
- * @param actualVersion - Optional version string to check for in the file content
- * @returns {Promise} True if the file doesn't exist or doesn't contain the actual version
- */
-async function shouldUpdateFile(filePath: string, actualVersion?: string): Promise {
- try {
- const content = await fs.readFile(filePath, "utf-8");
- if (actualVersion !== undefined) {
- return !content.includes(actualVersion);
- }
- return false;
- } catch {
- // File doesn't exist
- return true;
- }
-}
diff --git a/packages/ui/src/cli/commands/setup-class-list.ts b/packages/ui/src/cli/commands/setup-class-list.ts
index b77448320..61ea29e41 100644
--- a/packages/ui/src/cli/commands/setup-class-list.ts
+++ b/packages/ui/src/cli/commands/setup-class-list.ts
@@ -1,6 +1,11 @@
import fs from "fs/promises";
import { classListFilePath } from "../consts";
+/**
+ * Sets up the `.flowbite-react/class-list.json` file in the project.
+ *
+ * This function checks if the `.flowbite-react/class-list.json` file exists and creates it if it does not.
+ */
export async function setupClassList() {
try {
await fs.access(classListFilePath);
diff --git a/packages/ui/src/cli/commands/setup-config.ts b/packages/ui/src/cli/commands/setup-config.ts
index 7e40e18d0..ce9373784 100644
--- a/packages/ui/src/cli/commands/setup-config.ts
+++ b/packages/ui/src/cli/commands/setup-config.ts
@@ -1,7 +1,13 @@
import fs from "fs/promises";
import { configFilePath } from "../consts";
import type { Config } from "../utils/get-config";
+import { getTailwindVersion } from "../utils/get-tailwind-version";
+/**
+ * Sets up the `.flowbite-react/config.json` file in the project.
+ *
+ * This function checks if the `.flowbite-react/config.json` file exists and creates it if it does not.
+ */
export async function setupConfig() {
try {
await fs.access(configFilePath);
@@ -10,10 +16,12 @@ export async function setupConfig() {
$schema: "https://unpkg.com/flowbite-react/schema.json",
components: [],
dark: true,
- prefix: "",
path: "src/components",
- tsx: true,
+ // TODO: infer from project
+ prefix: "",
rsc: true,
+ tsx: true,
+ version: await getTailwindVersion(),
};
console.log(`Creating ${configFilePath} file...`);
await fs.writeFile(configFilePath, JSON.stringify(defaultConfig, null, 2), { flag: "w" });
diff --git a/packages/ui/src/cli/commands/setup-gitignore.ts b/packages/ui/src/cli/commands/setup-gitignore.ts
index 237b1f8fe..40c90063c 100644
--- a/packages/ui/src/cli/commands/setup-gitignore.ts
+++ b/packages/ui/src/cli/commands/setup-gitignore.ts
@@ -2,6 +2,12 @@ import fs from "fs/promises";
import path from "path";
import { classListFile, outputDir, processIdFile } from "../consts";
+/**
+ * Sets up the `.flowbite-react/.gitignore` file in the project.
+ *
+ * This function checks if the `.gitignore` file exists in the `.flowbite-react` directory
+ * and adds the necessary files to it if they are not already present.
+ */
export async function setupGitIgnore() {
const gitIgnoreFilePath = path.join(outputDir, ".gitignore");
diff --git a/packages/ui/src/cli/commands/setup-output-directory.ts b/packages/ui/src/cli/commands/setup-output-directory.ts
index 6136d11eb..1cb080346 100644
--- a/packages/ui/src/cli/commands/setup-output-directory.ts
+++ b/packages/ui/src/cli/commands/setup-output-directory.ts
@@ -1,6 +1,11 @@
import fs from "fs/promises";
import { outputDir } from "../consts";
+/**
+ * Sets up the `.flowbite-react` directory in the project.
+ *
+ * This function checks if the `.flowbite-react` directory exists and creates it if it does not.
+ */
export async function setupOutputDirectory() {
try {
await fs.access(outputDir);
diff --git a/packages/ui/src/cli/commands/setup-patch.ts b/packages/ui/src/cli/commands/setup-patch.ts
deleted file mode 100644
index d13f08c39..000000000
--- a/packages/ui/src/cli/commands/setup-patch.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import fs from "fs/promises";
-import cjson from "comment-json";
-import { packageJsonFile } from "../consts";
-import { getPackageJson } from "../utils/get-package-json";
-
-export async function setupPatch() {
- try {
- const patchCommand = "flowbite-react patch";
- const packageJson = await getPackageJson();
-
- if (!packageJson.scripts) {
- packageJson.scripts = {};
- }
-
- if (!packageJson.scripts.postinstall?.includes(patchCommand)) {
- console.log(`Adding postinstall patch script to ${packageJsonFile}...`);
- if (packageJson.scripts.postinstall) {
- packageJson.scripts.postinstall += ` && ${patchCommand}`;
- } else {
- packageJson.scripts.postinstall = patchCommand;
- }
- await fs.writeFile(packageJsonFile, cjson.stringify(packageJson, null, 2), { flag: "w" });
- }
- } catch (error) {
- console.error(`Failed to setup ${packageJsonFile}:`, error);
- }
-}
diff --git a/packages/ui/src/cli/commands/setup-plugin.ts b/packages/ui/src/cli/commands/setup-plugin.ts
index 277277710..73cd8573b 100644
--- a/packages/ui/src/cli/commands/setup-plugin.ts
+++ b/packages/ui/src/cli/commands/setup-plugin.ts
@@ -1,5 +1,11 @@
import fs from "fs/promises";
+/**
+ * Sets up the plugin for the project based on the bundler.
+ *
+ * This function checks for the existence of configuration files for various bundlers and frameworks
+ * and sets up the appropriate plugin for each.
+ */
export async function setupPlugin() {
const configFileMap = {
astro: ["astro.config.cjs", "astro.config.mjs", "astro.config.ts", "astro.config.js"],
diff --git a/packages/ui/src/cli/commands/setup-register.ts b/packages/ui/src/cli/commands/setup-register.ts
index 7afa569b8..1cb7f6d79 100644
--- a/packages/ui/src/cli/commands/setup-register.ts
+++ b/packages/ui/src/cli/commands/setup-register.ts
@@ -3,6 +3,13 @@ import cjson from "comment-json";
import { packageJsonFile } from "../consts";
import { getPackageJson } from "../utils/get-package-json";
+/**
+ * Sets up the register script in the project's package.json file.
+ *
+ * This function checks if the postinstall script already exists in the package.json file.
+ * If it does not exist, it adds the register command to the postinstall script.
+ * If it does exist, it appends the register command to the existing postinstall script.
+ */
export async function setupRegister() {
try {
const registerCommand = "flowbite-react register";
diff --git a/packages/ui/src/cli/commands/setup-tailwind.ts b/packages/ui/src/cli/commands/setup-tailwind.ts
index 09acd52c5..05dca47b8 100644
--- a/packages/ui/src/cli/commands/setup-tailwind.ts
+++ b/packages/ui/src/cli/commands/setup-tailwind.ts
@@ -6,6 +6,12 @@ import { addToConfig } from "../utils/add-to-config";
import { findFiles } from "../utils/find-files";
import { joinNormalizedPath } from "../utils/normalize-path";
+/**
+ * Sets up Tailwind CSS configuration for the project.
+ *
+ * This function checks if Tailwind CSS is installed in the project and then
+ * attempts to add the necessary configuration for Tailwind CSS v4 or v3.
+ */
export async function setupTailwind() {
try {
const found = !!((await setupTailwindV4()) || (await setupTailwindV3()));
@@ -18,6 +24,12 @@ export async function setupTailwind() {
}
}
+/**
+ * Sets up Tailwind CSS v4 configuration for the project.
+ *
+ * This function searches for Tailwind CSS files in the project and attempts to
+ * add the necessary configuration for Tailwind CSS v4.
+ */
async function setupTailwindV4() {
try {
const cssFiles = await findFiles({
diff --git a/packages/ui/src/cli/commands/setup-vscode.ts b/packages/ui/src/cli/commands/setup-vscode.ts
index d8fe2d514..b6151d85c 100644
--- a/packages/ui/src/cli/commands/setup-vscode.ts
+++ b/packages/ui/src/cli/commands/setup-vscode.ts
@@ -3,6 +3,12 @@ import path from "path";
import cjson from "comment-json";
import { vscodeDir } from "../consts";
+/**
+ * Sets up the VSCode configuration for the project.
+ *
+ * This function checks if the `.vscode` directory exists and creates it if it does not.
+ * It then sets up the `settings.json` and `extensions.json` files with the necessary configuration for Flowbite React.
+ */
export async function setupVSCode() {
try {
await fs.access(vscodeDir);
@@ -15,6 +21,12 @@ export async function setupVSCode() {
await setupVSCodeExtensions();
}
+/**
+ * Sets up the VSCode settings for the project.
+ *
+ * This function checks if the `settings.json` file exists and creates it if it does not.
+ * It then sets up the `files.associations`, `tailwindCSS.classAttributes`, and `tailwindCSS.experimental.classRegex` settings.
+ */
async function setupVSCodeSettings() {
try {
const vscodeSettingsFilePath = path.join(vscodeDir, "settings.json");
diff --git a/packages/ui/src/cli/main.ts b/packages/ui/src/cli/main.ts
index c30e36107..d4de29320 100644
--- a/packages/ui/src/cli/main.ts
+++ b/packages/ui/src/cli/main.ts
@@ -23,17 +23,13 @@ export async function main(argv: string[]) {
await init();
}
if (command === "install") {
- const { installFlowbiteReact } = await import("./commands/install");
- await installFlowbiteReact();
+ const { installPackage } = await import("./commands/install");
+ await installPackage();
}
if (command === "migrate") {
const { migrate } = await import("./commands/migrate");
await migrate();
}
- if (command === "patch") {
- const { patchTailwind } = await import("./commands/patch");
- await patchTailwind();
- }
if (command === "register") {
const { register } = await import("./commands/register");
await register();
@@ -54,19 +50,7 @@ export async function main(argv: string[]) {
}
if (
- ![
- "build",
- "create",
- "dev",
- "help",
- "--help",
- "init",
- "install",
- "migrate",
- "patch",
- "register",
- "setup",
- ].includes(command)
+ !["build", "create", "dev", "help", "--help", "init", "install", "migrate", "register", "setup"].includes(command)
) {
console.error(`Unknown command: ${command}`);
const { help } = await import("./commands/help");
diff --git a/packages/ui/src/cli/utils/build-class-list.ts b/packages/ui/src/cli/utils/build-class-list.ts
index fd597ccf3..3ae93d8f3 100644
--- a/packages/ui/src/cli/utils/build-class-list.ts
+++ b/packages/ui/src/cli/utils/build-class-list.ts
@@ -1,7 +1,6 @@
import { applyPrefix } from "../../helpers/apply-prefix";
import { applyPrefixV3 } from "../../helpers/apply-prefix-v3";
import { convertUtilitiesToV4 } from "../../helpers/convert-utilities-to-v4";
-import { getTailwindVersion } from "../../helpers/get-tailwind-version";
import { stripDark } from "../../helpers/strip-dark";
import { CLASS_LIST_MAP, COMPONENT_TO_CLASS_LIST_MAP } from "../../metadata/class-list";
import { DEPENDENCY_LIST_MAP } from "../../metadata/dependency-list";
@@ -19,13 +18,13 @@ export function buildClassList({
components,
dark,
prefix,
+ version,
}: {
components: string[];
dark: boolean;
prefix: string;
+ version: 3 | 4;
}): string[] {
- const version = getTailwindVersion();
-
let classList: string[] = [];
if (components.includes("*")) {
diff --git a/packages/ui/src/cli/utils/get-config.ts b/packages/ui/src/cli/utils/get-config.ts
index 9959e26e8..ca1a800b5 100644
--- a/packages/ui/src/cli/utils/get-config.ts
+++ b/packages/ui/src/cli/utils/get-config.ts
@@ -9,6 +9,7 @@ export interface Config {
prefix: string;
rsc: boolean;
tsx: boolean;
+ version: 3 | 4;
}
/**
@@ -28,6 +29,7 @@ export async function getConfig(): Promise {
prefix: "",
rsc: true,
tsx: true,
+ version: 4,
};
try {
@@ -55,6 +57,9 @@ export async function getConfig(): Promise {
if (parsed.tsx !== undefined && typeof parsed.tsx === "boolean") {
config.tsx = parsed.tsx;
}
+ if (parsed.version !== undefined && typeof parsed.version === "number") {
+ config.version = parsed.version;
+ }
return config;
} catch {
diff --git a/packages/ui/src/cli/utils/get-package-json.ts b/packages/ui/src/cli/utils/get-package-json.ts
index 901dcc872..b65087b19 100644
--- a/packages/ui/src/cli/utils/get-package-json.ts
+++ b/packages/ui/src/cli/utils/get-package-json.ts
@@ -5,9 +5,9 @@ import { packageJsonFile } from "../consts";
export interface PackageJson {
name: string;
version: string;
+ scripts: Record;
dependencies: Record;
devDependencies: Record;
- scripts: Record;
}
/**
* Reads and parses the package.json file.
diff --git a/packages/ui/src/cli/utils/get-tailwind-version.ts b/packages/ui/src/cli/utils/get-tailwind-version.ts
new file mode 100644
index 000000000..83f91d416
--- /dev/null
+++ b/packages/ui/src/cli/utils/get-tailwind-version.ts
@@ -0,0 +1,27 @@
+import { createRequire } from "module";
+
+/**
+ * Gets the version of Tailwind CSS used in the project.
+ *
+ * This function attempts to read the version from the `tailwindcss/package.json` file.
+ * If the file is not found, it will try to import the file asynchronously.
+ *
+ * @throws {Error} If the detected Tailwind CSS major version is not 3 or 4
+ * @returns {Promise<3 | 4>} `3` if the version is 3.x, `4` if the version is 4.x
+ */
+export async function getTailwindVersion(): Promise<3 | 4> {
+ let tailwindcssPackageJson: { version: string };
+ try {
+ const require = createRequire(import.meta.url);
+ tailwindcssPackageJson = require("tailwindcss/package.json");
+ } catch {
+ tailwindcssPackageJson = (await import("tailwindcss/package.json")).default;
+ }
+
+ const major = parseInt(tailwindcssPackageJson.version.split(".")[0], 10);
+ if (major === 3 || major === 4) {
+ return major;
+ }
+
+ throw new Error(`Unsupported Tailwind CSS major version: ${major}`);
+}
diff --git a/packages/ui/src/cli/utils/sync-tailwind-version.ts b/packages/ui/src/cli/utils/sync-tailwind-version.ts
new file mode 100644
index 000000000..7e1cef64d
--- /dev/null
+++ b/packages/ui/src/cli/utils/sync-tailwind-version.ts
@@ -0,0 +1,34 @@
+import fs from "fs/promises";
+import path from "path";
+import { configFilePath } from "../consts";
+import { getConfig } from "./get-config";
+import { getTailwindVersion } from "./get-tailwind-version";
+
+/**
+ * Detects the installed Tailwind CSS major version and updates the local
+ * `.flowbite-react/config.json` file if the version is missing or out of sync.
+ */
+export async function syncTailwindVersion(): Promise {
+ try {
+ const detectedVersion = await getTailwindVersion();
+ const config = await getConfig();
+
+ if (config.version !== detectedVersion) {
+ console.log(
+ `Tailwind CSS version mismatch (Detected: ${detectedVersion}, Config: ${config.version}). Updating ${configFilePath}...`,
+ );
+ config.version = detectedVersion;
+
+ try {
+ await fs.mkdir(path.dirname(configFilePath), { recursive: true });
+ await fs.writeFile(configFilePath, JSON.stringify(config, null, 2), { flag: "w" });
+ console.log(`Successfully updated ${configFilePath} with Tailwind CSS version ${detectedVersion}.`);
+ } catch (writeError) {
+ console.error(`Error writing updated configuration to ${configFilePath}:`, writeError);
+ throw writeError;
+ }
+ }
+ } catch (error) {
+ console.error("Error syncing Tailwind CSS version:", error);
+ }
+}
diff --git a/packages/ui/src/components/Datepicker/theme.ts b/packages/ui/src/components/Datepicker/theme.ts
index 974563bb9..540a377a6 100644
--- a/packages/ui/src/components/Datepicker/theme.ts
+++ b/packages/ui/src/components/Datepicker/theme.ts
@@ -30,8 +30,8 @@ export const datePickerTheme = createTheme({
footer: {
base: "mt-2 flex space-x-2",
button: {
- base: "w-full rounded-lg px-5 py-2 text-center text-sm font-medium focus:ring-4 focus:ring-cyan-300",
- today: "bg-cyan-700 text-white hover:bg-cyan-800 dark:bg-cyan-600 dark:hover:bg-cyan-700",
+ base: "w-full rounded-lg px-5 py-2 text-center text-sm font-medium focus:ring-4 focus:ring-primary-300",
+ today: "bg-primary-700 text-white hover:bg-primary-800 dark:bg-primary-600 dark:hover:bg-primary-700",
clear:
"border border-gray-300 bg-white text-gray-900 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600",
},
@@ -47,7 +47,7 @@ export const datePickerTheme = createTheme({
base: "grid w-64 grid-cols-7",
item: {
base: "block flex-1 cursor-pointer rounded-lg border-0 text-center text-sm font-semibold leading-9 text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
- selected: "bg-cyan-700 text-white hover:bg-cyan-600",
+ selected: "bg-primary-700 text-white hover:bg-primary-600",
disabled: "text-gray-500",
},
},
@@ -57,7 +57,7 @@ export const datePickerTheme = createTheme({
base: "grid w-64 grid-cols-4",
item: {
base: "block flex-1 cursor-pointer rounded-lg border-0 text-center text-sm font-semibold leading-9 text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
- selected: "bg-cyan-700 text-white hover:bg-cyan-600",
+ selected: "bg-primary-700 text-white hover:bg-primary-600",
disabled: "text-gray-500",
},
},
@@ -67,7 +67,7 @@ export const datePickerTheme = createTheme({
base: "grid w-64 grid-cols-4",
item: {
base: "block flex-1 cursor-pointer rounded-lg border-0 text-center text-sm font-semibold leading-9 text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
- selected: "bg-cyan-700 text-white hover:bg-cyan-600",
+ selected: "bg-primary-700 text-white hover:bg-primary-600",
disabled: "text-gray-500",
},
},
@@ -77,7 +77,7 @@ export const datePickerTheme = createTheme({
base: "grid w-64 grid-cols-4",
item: {
base: "block flex-1 cursor-pointer rounded-lg border-0 text-center text-sm font-semibold leading-9 text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
- selected: "bg-cyan-700 text-white hover:bg-cyan-600",
+ selected: "bg-primary-700 text-white hover:bg-primary-600",
disabled: "text-gray-500",
},
},
diff --git a/packages/ui/src/helpers/get-tailwind-version.ts b/packages/ui/src/helpers/get-tailwind-version.ts
deleted file mode 100644
index 8b9a9a007..000000000
--- a/packages/ui/src/helpers/get-tailwind-version.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import version from "tailwindcss/version.js";
-
-/**
- * Gets the major version number of the installed Tailwind CSS
- *
- * @returns The major version number (3 or 4) or undefined if not found
- */
-export function getTailwindVersion(): 3 | 4 | undefined {
- try {
- return parseInt(version.split(".")[0], 10) as 3 | 4;
- } catch (_) {
- return;
- }
-}
diff --git a/packages/ui/src/helpers/resolve-theme.test.ts b/packages/ui/src/helpers/resolve-theme.test.ts
index cc292cfb0..262450aa6 100644
--- a/packages/ui/src/helpers/resolve-theme.test.ts
+++ b/packages/ui/src/helpers/resolve-theme.test.ts
@@ -18,7 +18,7 @@ describe("resolveTheme", () => {
});
it("should apply prefix with version 3 format", () => {
- setStore({ prefix: "tw-" });
+ setStore({ prefix: "tw-", version: 3 });
const base = { color: "text-red-400" };
diff --git a/packages/ui/src/helpers/resolve-theme.ts b/packages/ui/src/helpers/resolve-theme.ts
index e465a8c00..e8c06c4ab 100644
--- a/packages/ui/src/helpers/resolve-theme.ts
+++ b/packages/ui/src/helpers/resolve-theme.ts
@@ -1,13 +1,12 @@
import { deepmerge } from "deepmerge-ts";
import { klona } from "klona/json";
import { useRef } from "react";
-import { getDark, getPrefix } from "../store";
+import { getDark, getPrefix, getVersion } from "../store";
import type { ApplyTheme, DeepPartialApplyTheme, DeepPartialBoolean } from "../types";
import { applyPrefix } from "./apply-prefix";
import { applyPrefixV3 } from "./apply-prefix-v3";
import { convertUtilitiesToV4 } from "./convert-utilities-to-v4";
import { deepMergeStrings } from "./deep-merge";
-import { getTailwindVersion } from "./get-tailwind-version";
import { isEqual } from "./is-equal";
import { stripDark } from "./strip-dark";
import { twMerge } from "./tailwind-merge";
@@ -67,7 +66,7 @@ export function resolveTheme(
): T {
const dark = getDark();
const prefix = getPrefix();
- const version = getTailwindVersion();
+ const version = getVersion();
const _custom = custom?.length ? custom?.filter((value) => value !== undefined) : undefined;
const _clearThemeList = clearThemeList?.length ? clearThemeList?.filter((value) => value !== undefined) : undefined;
diff --git a/packages/ui/src/helpers/tailwind-merge.ts b/packages/ui/src/helpers/tailwind-merge.ts
index cbbe55fb5..b45ca96a7 100644
--- a/packages/ui/src/helpers/tailwind-merge.ts
+++ b/packages/ui/src/helpers/tailwind-merge.ts
@@ -1,13 +1,12 @@
import { extendTailwindMerge as extendTailwindMerge_v2 } from "tailwind-merge-v2";
import { extendTailwindMerge as extendTailwindMerge_v3, type ClassNameValue } from "tailwind-merge-v3";
-import { getPrefix } from "../store";
-import { getTailwindVersion } from "./get-tailwind-version";
+import { getPrefix, getVersion } from "../store";
const cache = new Map>();
export function twMerge(...classLists: ClassNameValue[]): string {
const prefix = getPrefix();
- const version = getTailwindVersion();
+ const version = getVersion();
const cacheKey = `${prefix}.${version}`;
const cacheValue = cache.get(cacheKey);
diff --git a/packages/ui/src/store/index.ts b/packages/ui/src/store/index.ts
index a78235c7f..948c26fa0 100644
--- a/packages/ui/src/store/index.ts
+++ b/packages/ui/src/store/index.ts
@@ -22,12 +22,19 @@ export type StoreProps = DeepPartial<{
* @default undefined
*/
prefix: string;
+ /**
+ * The version of Tailwind CSS to use
+ *
+ * @default 4
+ */
+ version: 3 | 4;
}>;
const store: StoreProps = {
dark: undefined,
mode: undefined,
prefix: undefined,
+ version: undefined,
};
export function setStore(data: StoreProps) {
@@ -44,6 +51,13 @@ export function setStore(data: StoreProps) {
if ("prefix" in data) {
store.prefix = data.prefix;
}
+ if ("version" in data) {
+ if (data.version === 3 || data.version === 4) {
+ store.version = data.version;
+ } else {
+ console.warn(`Invalid version value: ${data.version}.\nAvailable values: 3, 4`);
+ }
+ }
}
export function getDark(): StoreProps["dark"] {
@@ -57,3 +71,7 @@ export function getMode(): StoreProps["mode"] {
export function getPrefix(): StoreProps["prefix"] {
return store.prefix;
}
+
+export function getVersion(): StoreProps["version"] {
+ return store.version;
+}