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; +}