@codav/slug-generator 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,217 +1,217 @@
1
- # @codav/slug-generator
2
-
3
- A lightweight, reusable and TypeScript-first React component for generating clean, URL-friendly slugs directly from titles.
4
-
5
- Built for modern React and Next.js applications with a simple API, zero unnecessary complexity, and easy integration.
6
-
7
- ## ✨ Features
8
-
9
- - Generate slugs directly from titles
10
- - Supports Persian, English, numbers and Unicode characters
11
- - Automatically removes unnecessary characters
12
- - Converts spaces into `-`
13
- - Prevents duplicate hyphens
14
- - Removes leading and trailing hyphens
15
- - Fully controlled React component
16
- - Written in TypeScript
17
- - React 18 and React 19 compatible
18
- - Next.js compatible
19
- - Customizable labels and button text
20
- - Supports custom classes
21
- - Lightweight package architecture
22
- - ESM and CommonJS builds
23
- - Built-in TypeScript declarations
24
-
25
- ## 📦 Installation
26
-
27
- ```bash
28
- npm install @codav/slug-generator
29
- ```
30
-
31
- Or:
32
-
33
- ```bash
34
- yarn add @codav/slug-generator
35
- ```
36
-
37
- ```bash
38
- pnpm add @codav/slug-generator
39
- ```
40
-
41
- ## 🚀 Basic Usage
42
-
43
- ```tsx
44
- "use client";
45
-
46
- import { useState } from "react";
47
- import { SlugGenerator } from "@codav/slug-generator";
48
-
49
- export default function Example() {
50
- const [title, setTitle] = useState("");
51
- const [slug, setSlug] = useState("");
52
-
53
- return (
54
- <SlugGenerator
55
- title={title}
56
- value={slug}
57
- onChange={setSlug}
58
- />
59
- );
60
- }
61
- ```
62
-
63
- ## 🧩 With a Title Input
64
-
65
- ```tsx
66
- "use client";
67
-
68
- import { useState } from "react";
69
- import { SlugGenerator } from "@codav/slug-generator";
70
-
71
- export default function BlogForm() {
72
- const [title, setTitle] = useState("");
73
- const [slug, setSlug] = useState("");
74
-
75
- return (
76
- <div>
77
- <input
78
- value={title}
79
- onChange={(event) => setTitle(event.target.value)}
80
- placeholder="Article title"
81
- />
82
-
83
- <SlugGenerator
84
- title={title}
85
- value={slug}
86
- onChange={setSlug}
87
- label="Slug"
88
- buttonText="Generate Slug"
89
- />
90
- </div>
91
- );
92
- }
93
- ```
94
-
95
- ## 🌍 Persian Support
96
-
97
- The component is designed to work with Unicode text, making it suitable for Persian and other non-Latin languages.
98
-
99
- For example:
100
-
101
- ```text
102
- آموزش لاراول برای مبتدیان
103
- ```
104
-
105
- can become:
106
-
107
- ```text
108
- آموزش-لاراول-برای-مبتدیان
109
- ```
110
-
111
- English titles are supported as well:
112
-
113
- ```text
114
- Learn Laravel From Scratch
115
- ```
116
-
117
- becomes:
118
-
119
- ```text
120
- learn-laravel-from-scratch
121
- ```
122
-
123
- ## ⚙️ API
124
-
125
- | Prop | Type | Default | Description |
126
- |---|---|---|---|
127
- | `title` | `string` | — | Source title used to generate the slug |
128
- | `value` | `string` | — | Current slug value |
129
- | `onChange` | `(slug: string) => void` | — | Called when the slug changes |
130
- | `label` | `string` | `"Slug"` | Input label |
131
- | `placeholder` | `string` | `"your-slug"` | Input placeholder |
132
- | `buttonText` | `string` | `"Generate"` | Generate button text |
133
- | `disabled` | `boolean` | `false` | Disables the component |
134
- | `className` | `string` | `""` | Custom CSS class |
135
- | `icon` | `ReactNode` | — | Optional label icon |
136
-
137
- ## 🏗️ Build From Source
138
-
139
- Clone the repository:
140
-
141
- ```bash
142
- git clone https://github.com/YOUR_USERNAME/slug-generator.git
143
- cd slug-generator
144
- ```
145
-
146
- Install dependencies:
147
-
148
- ```bash
149
- npm install
150
- ```
151
-
152
- Build the package:
153
-
154
- ```bash
155
- npm run build
156
- ```
157
-
158
- Development mode:
159
-
160
- ```bash
161
- npm run dev
162
- ```
163
-
164
- ## 📁 Package Architecture
165
-
166
- ```text
167
- slug-generator/
168
- ├── src/
169
- │ ├── SlugGenerator.tsx
170
- │ ├── index.ts
171
- │ └── types.ts
172
- ├── package.json
173
- ├── tsconfig.json
174
- ├── tsup.config.ts
175
- └── README.md
176
- ```
177
-
178
- ## 🎯 Design Philosophy
179
-
180
- `@codav/slug-generator` is intentionally focused on one responsibility:
181
-
182
- > Convert user-provided titles into clean, URL-friendly slugs through a reusable React component.
183
-
184
- The package is designed to remain independent from any specific CMS, blog system, backend framework, database or application architecture.
185
-
186
- This makes it suitable for:
187
-
188
- - Blogs
189
- - Products
190
- - Categories
191
- - Pages
192
- - Courses
193
- - Documentation
194
- - News systems
195
- - E-commerce platforms
196
- - Admin dashboards
197
- - Content management systems
198
-
199
- ## 🛠️ Tech Stack
200
-
201
- - React
202
- - TypeScript
203
- - tsup
204
- - ESM
205
- - CommonJS
206
-
207
- ## 📄 License
208
-
209
- MIT
210
-
211
- ## 👨‍💻 Author
212
-
213
- Developed and maintained by **CODAV**.
214
-
215
- ---
216
-
217
- If you find this package useful, consider giving the repository a ⭐ on GitHub.
1
+ # @codav/slug-generator
2
+
3
+ A lightweight, reusable and TypeScript-first React component for generating clean, URL-friendly slugs directly from titles.
4
+
5
+ Built for modern React and Next.js applications with a simple API, zero unnecessary complexity, and easy integration.
6
+
7
+ ## ✨ Features
8
+
9
+ - Generate slugs directly from titles
10
+ - Supports Persian, English, numbers and Unicode characters
11
+ - Automatically removes unnecessary characters
12
+ - Converts spaces into `-`
13
+ - Prevents duplicate hyphens
14
+ - Removes leading and trailing hyphens
15
+ - Fully controlled React component
16
+ - Written in TypeScript
17
+ - React 18 and React 19 compatible
18
+ - Next.js compatible
19
+ - Customizable labels and button text
20
+ - Supports custom classes
21
+ - Lightweight package architecture
22
+ - ESM and CommonJS builds
23
+ - Built-in TypeScript declarations
24
+
25
+ ## 📦 Installation
26
+
27
+ ```bash
28
+ npm install @codav/slug-generator
29
+ ```
30
+
31
+ Or:
32
+
33
+ ```bash
34
+ yarn add @codav/slug-generator
35
+ ```
36
+
37
+ ```bash
38
+ pnpm add @codav/slug-generator
39
+ ```
40
+
41
+ ## 🚀 Basic Usage
42
+
43
+ ```tsx
44
+ "use client";
45
+
46
+ import { useState } from "react";
47
+ import { SlugGenerator } from "@codav/slug-generator";
48
+
49
+ export default function Example() {
50
+ const [title, setTitle] = useState("");
51
+ const [slug, setSlug] = useState("");
52
+
53
+ return (
54
+ <SlugGenerator
55
+ title={title}
56
+ value={slug}
57
+ onChange={setSlug}
58
+ />
59
+ );
60
+ }
61
+ ```
62
+
63
+ ## 🧩 With a Title Input
64
+
65
+ ```tsx
66
+ "use client";
67
+
68
+ import { useState } from "react";
69
+ import { SlugGenerator } from "@codav/slug-generator";
70
+
71
+ export default function BlogForm() {
72
+ const [title, setTitle] = useState("");
73
+ const [slug, setSlug] = useState("");
74
+
75
+ return (
76
+ <div>
77
+ <input
78
+ value={title}
79
+ onChange={(event) => setTitle(event.target.value)}
80
+ placeholder="Article title"
81
+ />
82
+
83
+ <SlugGenerator
84
+ title={title}
85
+ value={slug}
86
+ onChange={setSlug}
87
+ label="Slug"
88
+ buttonText="Generate Slug"
89
+ />
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ## 🌍 Persian Support
96
+
97
+ The component is designed to work with Unicode text, making it suitable for Persian and other non-Latin languages.
98
+
99
+ For example:
100
+
101
+ ```text
102
+ آموزش لاراول برای مبتدیان
103
+ ```
104
+
105
+ can become:
106
+
107
+ ```text
108
+ آموزش-لاراول-برای-مبتدیان
109
+ ```
110
+
111
+ English titles are supported as well:
112
+
113
+ ```text
114
+ Learn Laravel From Scratch
115
+ ```
116
+
117
+ becomes:
118
+
119
+ ```text
120
+ learn-laravel-from-scratch
121
+ ```
122
+
123
+ ## ⚙️ API
124
+
125
+ | Prop | Type | Default | Description |
126
+ |---|---|---|---|
127
+ | `title` | `string` | — | Source title used to generate the slug |
128
+ | `value` | `string` | — | Current slug value |
129
+ | `onChange` | `(slug: string) => void` | — | Called when the slug changes |
130
+ | `label` | `string` | `"Slug"` | Input label |
131
+ | `placeholder` | `string` | `"your-slug"` | Input placeholder |
132
+ | `buttonText` | `string` | `"Generate"` | Generate button text |
133
+ | `disabled` | `boolean` | `false` | Disables the component |
134
+ | `className` | `string` | `""` | Custom CSS class |
135
+ | `icon` | `ReactNode` | — | Optional label icon |
136
+
137
+ ## 🏗️ Build From Source
138
+
139
+ Clone the repository:
140
+
141
+ ```bash
142
+ git clone https://github.com/MohammadMahdiAhmadi1382/slug-generator.git
143
+ cd slug-generator
144
+ ```
145
+
146
+ Install dependencies:
147
+
148
+ ```bash
149
+ npm install
150
+ ```
151
+
152
+ Build the package:
153
+
154
+ ```bash
155
+ npm run build
156
+ ```
157
+
158
+ Development mode:
159
+
160
+ ```bash
161
+ npm run dev
162
+ ```
163
+
164
+ ## 📁 Package Architecture
165
+
166
+ ```text
167
+ slug-generator/
168
+ ├── src/
169
+ │ ├── SlugGenerator.tsx
170
+ │ ├── index.ts
171
+ │ └── types.ts
172
+ ├── package.json
173
+ ├── tsconfig.json
174
+ ├── tsup.config.ts
175
+ └── README.md
176
+ ```
177
+
178
+ ## 🎯 Design Philosophy
179
+
180
+ `@codav/slug-generator` is intentionally focused on one responsibility:
181
+
182
+ > Convert user-provided titles into clean, URL-friendly slugs through a reusable React component.
183
+
184
+ The package is designed to remain independent from any specific CMS, blog system, backend framework, database or application architecture.
185
+
186
+ This makes it suitable for:
187
+
188
+ - Blogs
189
+ - Products
190
+ - Categories
191
+ - Pages
192
+ - Courses
193
+ - Documentation
194
+ - News systems
195
+ - E-commerce platforms
196
+ - Admin dashboards
197
+ - Content management systems
198
+
199
+ ## 🛠️ Tech Stack
200
+
201
+ - React
202
+ - TypeScript
203
+ - tsup
204
+ - ESM
205
+ - CommonJS
206
+
207
+ ## 📄 License
208
+
209
+ MIT
210
+
211
+ ## 👨‍💻 Author
212
+
213
+ Developed and maintained by **CODAV**.
214
+
215
+ ---
216
+
217
+ If you find this package useful, consider giving the repository a ⭐ on GitHub.
package/dist/index.d.mts CHANGED
@@ -11,8 +11,11 @@ type SlugGeneratorProps = {
11
11
  disabled?: boolean;
12
12
  className?: string;
13
13
  icon?: ReactNode;
14
+ dir?: "ltr" | "rtl" | "auto";
15
+ generateOnTitleChange?: boolean;
16
+ transliteratePersian?: boolean;
14
17
  };
15
18
 
16
- declare function SlugGenerator({ title, value, onChange, label, placeholder, buttonText, disabled, className, icon, }: SlugGeneratorProps): react.JSX.Element;
19
+ declare function SlugGenerator({ title, value, onChange, label, placeholder, buttonText, disabled, className, icon, dir, transliteratePersian, }: SlugGeneratorProps): react.JSX.Element;
17
20
 
18
21
  export { SlugGenerator, type SlugGeneratorProps };
package/dist/index.d.ts CHANGED
@@ -11,8 +11,11 @@ type SlugGeneratorProps = {
11
11
  disabled?: boolean;
12
12
  className?: string;
13
13
  icon?: ReactNode;
14
+ dir?: "ltr" | "rtl" | "auto";
15
+ generateOnTitleChange?: boolean;
16
+ transliteratePersian?: boolean;
14
17
  };
15
18
 
16
- declare function SlugGenerator({ title, value, onChange, label, placeholder, buttonText, disabled, className, icon, }: SlugGeneratorProps): react.JSX.Element;
19
+ declare function SlugGenerator({ title, value, onChange, label, placeholder, buttonText, disabled, className, icon, dir, transliteratePersian, }: SlugGeneratorProps): react.JSX.Element;
17
20
 
18
21
  export { SlugGenerator, type SlugGeneratorProps };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -26,7 +36,60 @@ module.exports = __toCommonJS(index_exports);
26
36
 
27
37
  // src/SlugGenerator.tsx
28
38
  var import_lucide_react = require("lucide-react");
39
+ var import_clsx = __toESM(require("clsx"));
29
40
  var import_jsx_runtime = require("react/jsx-runtime");
41
+ var PERSIAN_MAP = {
42
+ \u0627: "a",
43
+ \u0622: "a",
44
+ \u0628: "b",
45
+ \u067E: "p",
46
+ \u062A: "t",
47
+ \u062B: "s",
48
+ \u062C: "j",
49
+ \u0686: "ch",
50
+ \u062D: "h",
51
+ \u062E: "kh",
52
+ \u062F: "d",
53
+ \u0630: "z",
54
+ \u0631: "r",
55
+ \u0632: "z",
56
+ \u0698: "zh",
57
+ \u0633: "s",
58
+ \u0634: "sh",
59
+ \u0635: "s",
60
+ \u0636: "z",
61
+ \u0637: "t",
62
+ \u0638: "z",
63
+ \u0639: "a",
64
+ \u063A: "gh",
65
+ \u0641: "f",
66
+ \u0642: "gh",
67
+ \u06A9: "k",
68
+ \u06AF: "g",
69
+ \u0644: "l",
70
+ \u0645: "m",
71
+ \u0646: "n",
72
+ \u0648: "v",
73
+ \u0647: "h",
74
+ \u06CC: "y",
75
+ \u0626: "y",
76
+ \u0621: "a",
77
+ \u06C0: "h",
78
+ \u0629: "h",
79
+ \u064A: "y",
80
+ \u0643: "k"
81
+ };
82
+ var ARABIC_DIACRITICS = /[\u064B-\u065F\u0670]/g;
83
+ function transliterate(text) {
84
+ return text.normalize("NFKC").replace(ARABIC_DIACRITICS, "").split("").map((char) => PERSIAN_MAP[char] ?? char).join("");
85
+ }
86
+ function createSlug(title, transliteratePersian = true) {
87
+ let source = title.normalize("NFKC").replace(ARABIC_DIACRITICS, "");
88
+ if (transliteratePersian) {
89
+ source = transliterate(source);
90
+ }
91
+ return source.toLowerCase().trim().replace(/['’`"]/g, "").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
92
+ }
30
93
  function SlugGenerator({
31
94
  title,
32
95
  value,
@@ -35,44 +98,75 @@ function SlugGenerator({
35
98
  placeholder = "your-slug",
36
99
  buttonText = "Generate",
37
100
  disabled = false,
38
- className = "",
39
- icon
101
+ className,
102
+ icon,
103
+ dir = "ltr",
104
+ transliteratePersian = true
40
105
  }) {
41
- const generateSlug = () => {
42
- const slug = title.toLowerCase().trim().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
43
- onChange(slug);
106
+ const handleGenerate = () => {
107
+ if (!title.trim() || disabled) return;
108
+ onChange(createSlug(title, transliteratePersian));
44
109
  };
45
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
46
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
47
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "text-sm font-medium", children: [
48
- icon,
49
- label
50
- ] }),
110
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: (0, import_clsx.default)("w-full space-y-1.5", className), children: [
111
+ label && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "flex items-center gap-2 text-sm font-medium text-(--color-text-primary)", children: [
112
+ icon,
113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label })
114
+ ] }),
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative", children: [
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
117
+ "input",
118
+ {
119
+ dir,
120
+ value,
121
+ onChange: (event) => onChange(event.target.value),
122
+ placeholder,
123
+ disabled,
124
+ onKeyDown: (event) => {
125
+ if (event.key === "Enter") {
126
+ event.preventDefault();
127
+ handleGenerate();
128
+ }
129
+ },
130
+ className: (0, import_clsx.default)(
131
+ "w-full rounded-lg border border-(--color-border)",
132
+ "bg-(--color-bg-main)",
133
+ "py-3 pe-4 ps-32",
134
+ "text-sm text-(--color-text-primary)",
135
+ "outline-none transition-all",
136
+ "placeholder:text-(--color-text-secondary)/70",
137
+ "hover:border-[color-mix(in_oklab,var(--color-primary)_22%,var(--color-border))]",
138
+ "focus:border-[color-mix(in_oklab,var(--color-primary)_45%,var(--color-border))]",
139
+ "focus:ring-4 focus:ring-[color-mix(in_oklab,var(--color-primary)_12%,transparent)]",
140
+ "dark:bg-white/[0.03]",
141
+ "disabled:cursor-not-allowed disabled:opacity-50"
142
+ )
143
+ }
144
+ ),
51
145
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
52
146
  "button",
53
147
  {
54
148
  type: "button",
55
- onClick: generateSlug,
149
+ onClick: handleGenerate,
56
150
  disabled: disabled || !title.trim(),
57
- className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium transition disabled:cursor-not-allowed disabled:opacity-50",
151
+ className: (0, import_clsx.default)(
152
+ "absolute inset-y-1.5 start-1.5",
153
+ "inline-flex items-center justify-center gap-1.5",
154
+ "rounded-md px-3",
155
+ "text-xs font-medium text-white",
156
+ "bg-(--color-primary)",
157
+ "transition-all duration-200",
158
+ "hover:bg-(--color-primary-soft)",
159
+ "focus:outline-none",
160
+ "focus:ring-2 focus:ring-(--color-primary)/30",
161
+ "disabled:cursor-not-allowed disabled:opacity-50"
162
+ ),
58
163
  children: [
59
164
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.WandSparkles, { className: "size-4" }),
60
- buttonText
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: buttonText })
61
166
  ]
62
167
  }
63
168
  )
64
- ] }),
65
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
66
- "input",
67
- {
68
- dir: "ltr",
69
- value,
70
- onChange: (event) => onChange(event.target.value),
71
- placeholder,
72
- disabled,
73
- className: "w-full rounded-lg border px-3 py-3 text-sm outline-none"
74
- }
75
- )
169
+ ] })
76
170
  ] });
77
171
  }
78
172
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/SlugGenerator.tsx"],"sourcesContent":["export { default as SlugGenerator } from \"./SlugGenerator\";\r\nexport type { SlugGeneratorProps } from \"./types\";","\"use client\";\r\n\r\nimport { WandSparkles } from \"lucide-react\";\r\nimport type { SlugGeneratorProps } from \"./types\";\r\n\r\nexport default function SlugGenerator({\r\n title,\r\n value,\r\n onChange,\r\n label = \"Slug\",\r\n placeholder = \"your-slug\",\r\n buttonText = \"Generate\",\r\n disabled = false,\r\n className = \"\",\r\n icon,\r\n}: SlugGeneratorProps) {\r\n const generateSlug = () => {\r\n const slug = title\r\n .toLowerCase()\r\n .trim()\r\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\r\n .replace(/\\s+/g, \"-\")\r\n .replace(/-+/g, \"-\")\r\n .replace(/^-|-$/g, \"\");\r\n\r\n onChange(slug);\r\n };\r\n\r\n return (\r\n <div className={className}>\r\n <div className=\"mb-2 flex items-center justify-between gap-3\">\r\n <label className=\"text-sm font-medium\">\r\n {icon}\r\n {label}\r\n </label>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={generateSlug}\r\n disabled={disabled || !title.trim()}\r\n className=\"inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium transition disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n <WandSparkles className=\"size-4\" />\r\n {buttonText}\r\n </button>\r\n </div>\r\n\r\n <input\r\n dir=\"ltr\"\r\n value={value}\r\n onChange={(event) => onChange(event.target.value)}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n className=\"w-full rounded-lg border px-3 py-3 text-sm outline-none\"\r\n />\r\n </div>\r\n );\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,0BAA6B;AA6BrB;AA1BO,SAAR,cAA+B;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,aAAa;AAAA,EACb,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AACF,GAAuB;AACrB,QAAM,eAAe,MAAM;AACzB,UAAM,OAAO,MACV,YAAY,EACZ,KAAK,EACL,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,QAAQ,GAAG,EACnB,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE;AAEvB,aAAS,IAAI;AAAA,EACf;AAEA,SACE,6CAAC,SAAI,WACH;AAAA,iDAAC,SAAI,WAAU,gDACb;AAAA,mDAAC,WAAM,WAAU,uBACd;AAAA;AAAA,QACA;AAAA,SACH;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU,YAAY,CAAC,MAAM,KAAK;AAAA,UAClC,WAAU;AAAA,UAEV;AAAA,wDAAC,oCAAa,WAAU,UAAS;AAAA,YAChC;AAAA;AAAA;AAAA,MACH;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,KAAI;AAAA,QACJ;AAAA,QACA,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,QAChD;AAAA,QACA;AAAA,QACA,WAAU;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/SlugGenerator.tsx"],"sourcesContent":["export { default as SlugGenerator } from \"./SlugGenerator\";\r\nexport type { SlugGeneratorProps } from \"./types\";","\"use client\";\r\n\r\nimport { WandSparkles } from \"lucide-react\";\r\nimport clsx from \"clsx\";\r\nimport type { SlugGeneratorProps } from \"./types\";\r\n\r\nconst PERSIAN_MAP: Record<string, string> = {\r\n ا: \"a\",\r\n آ: \"a\",\r\n ب: \"b\",\r\n پ: \"p\",\r\n ت: \"t\",\r\n ث: \"s\",\r\n ج: \"j\",\r\n چ: \"ch\",\r\n ح: \"h\",\r\n خ: \"kh\",\r\n د: \"d\",\r\n ذ: \"z\",\r\n ر: \"r\",\r\n ز: \"z\",\r\n ژ: \"zh\",\r\n س: \"s\",\r\n ش: \"sh\",\r\n ص: \"s\",\r\n ض: \"z\",\r\n ط: \"t\",\r\n ظ: \"z\",\r\n ع: \"a\",\r\n غ: \"gh\",\r\n ف: \"f\",\r\n ق: \"gh\",\r\n ک: \"k\",\r\n گ: \"g\",\r\n ل: \"l\",\r\n م: \"m\",\r\n ن: \"n\",\r\n و: \"v\",\r\n ه: \"h\",\r\n ی: \"y\",\r\n ئ: \"y\",\r\n ء: \"a\",\r\n ۀ: \"h\",\r\n ة: \"h\",\r\n ي: \"y\",\r\n ك: \"k\",\r\n};\r\n\r\nconst ARABIC_DIACRITICS = /[\\u064B-\\u065F\\u0670]/g;\r\n\r\nfunction transliterate(text: string): string {\r\n return text\r\n .normalize(\"NFKC\")\r\n .replace(ARABIC_DIACRITICS, \"\")\r\n .split(\"\")\r\n .map((char) => PERSIAN_MAP[char] ?? char)\r\n .join(\"\");\r\n}\r\n\r\nexport function createSlug(\r\n title: string,\r\n transliteratePersian = true,\r\n): string {\r\n let source = title\r\n .normalize(\"NFKC\")\r\n .replace(ARABIC_DIACRITICS, \"\");\r\n\r\n if (transliteratePersian) {\r\n source = transliterate(source);\r\n }\r\n\r\n return source\r\n .toLowerCase()\r\n .trim()\r\n .replace(/['’`\"]/g, \"\")\r\n .replace(/[^a-z0-9\\s-]/g, \"\")\r\n .replace(/\\s+/g, \"-\")\r\n .replace(/-+/g, \"-\")\r\n .replace(/^-+|-+$/g, \"\");\r\n}\r\n\r\nexport default function SlugGenerator({\r\n title,\r\n value,\r\n onChange,\r\n label = \"Slug\",\r\n placeholder = \"your-slug\",\r\n buttonText = \"Generate\",\r\n disabled = false,\r\n className,\r\n icon,\r\n dir = \"ltr\",\r\n transliteratePersian = true,\r\n}: SlugGeneratorProps) {\r\n const handleGenerate = () => {\r\n if (!title.trim() || disabled) return;\r\n\r\n onChange(createSlug(title, transliteratePersian));\r\n };\r\n\r\n return (\r\n <div className={clsx(\"w-full space-y-1.5\", className)}>\r\n {label && (\r\n <label className=\"flex items-center gap-2 text-sm font-medium text-(--color-text-primary)\">\r\n {icon}\r\n <span>{label}</span>\r\n </label>\r\n )}\r\n\r\n <div className=\"relative\">\r\n <input\r\n dir={dir}\r\n value={value}\r\n onChange={(event) => onChange(event.target.value)}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n onKeyDown={(event) => {\r\n if (event.key === \"Enter\") {\r\n event.preventDefault();\r\n handleGenerate();\r\n }\r\n }}\r\n className={clsx(\r\n \"w-full rounded-lg border border-(--color-border)\",\r\n \"bg-(--color-bg-main)\",\r\n \"py-3 pe-4 ps-32\",\r\n \"text-sm text-(--color-text-primary)\",\r\n \"outline-none transition-all\",\r\n \"placeholder:text-(--color-text-secondary)/70\",\r\n \"hover:border-[color-mix(in_oklab,var(--color-primary)_22%,var(--color-border))]\",\r\n \"focus:border-[color-mix(in_oklab,var(--color-primary)_45%,var(--color-border))]\",\r\n \"focus:ring-4 focus:ring-[color-mix(in_oklab,var(--color-primary)_12%,transparent)]\",\r\n \"dark:bg-white/[0.03]\",\r\n \"disabled:cursor-not-allowed disabled:opacity-50\",\r\n )}\r\n />\r\n\r\n <button\r\n type=\"button\"\r\n onClick={handleGenerate}\r\n disabled={disabled || !title.trim()}\r\n className={clsx(\r\n \"absolute inset-y-1.5 start-1.5\",\r\n \"inline-flex items-center justify-center gap-1.5\",\r\n \"rounded-md px-3\",\r\n \"text-xs font-medium text-white\",\r\n \"bg-(--color-primary)\",\r\n \"transition-all duration-200\",\r\n \"hover:bg-(--color-primary-soft)\",\r\n \"focus:outline-none\",\r\n \"focus:ring-2 focus:ring-(--color-primary)/30\",\r\n \"disabled:cursor-not-allowed disabled:opacity-50\",\r\n )}\r\n >\r\n <WandSparkles className=\"size-4\" />\r\n <span>{buttonText}</span>\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n}"],"mappings":"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","names":["clsx"]}
package/dist/index.mjs CHANGED
@@ -1,6 +1,59 @@
1
1
  // src/SlugGenerator.tsx
2
2
  import { WandSparkles } from "lucide-react";
3
+ import clsx from "clsx";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
+ var PERSIAN_MAP = {
6
+ \u0627: "a",
7
+ \u0622: "a",
8
+ \u0628: "b",
9
+ \u067E: "p",
10
+ \u062A: "t",
11
+ \u062B: "s",
12
+ \u062C: "j",
13
+ \u0686: "ch",
14
+ \u062D: "h",
15
+ \u062E: "kh",
16
+ \u062F: "d",
17
+ \u0630: "z",
18
+ \u0631: "r",
19
+ \u0632: "z",
20
+ \u0698: "zh",
21
+ \u0633: "s",
22
+ \u0634: "sh",
23
+ \u0635: "s",
24
+ \u0636: "z",
25
+ \u0637: "t",
26
+ \u0638: "z",
27
+ \u0639: "a",
28
+ \u063A: "gh",
29
+ \u0641: "f",
30
+ \u0642: "gh",
31
+ \u06A9: "k",
32
+ \u06AF: "g",
33
+ \u0644: "l",
34
+ \u0645: "m",
35
+ \u0646: "n",
36
+ \u0648: "v",
37
+ \u0647: "h",
38
+ \u06CC: "y",
39
+ \u0626: "y",
40
+ \u0621: "a",
41
+ \u06C0: "h",
42
+ \u0629: "h",
43
+ \u064A: "y",
44
+ \u0643: "k"
45
+ };
46
+ var ARABIC_DIACRITICS = /[\u064B-\u065F\u0670]/g;
47
+ function transliterate(text) {
48
+ return text.normalize("NFKC").replace(ARABIC_DIACRITICS, "").split("").map((char) => PERSIAN_MAP[char] ?? char).join("");
49
+ }
50
+ function createSlug(title, transliteratePersian = true) {
51
+ let source = title.normalize("NFKC").replace(ARABIC_DIACRITICS, "");
52
+ if (transliteratePersian) {
53
+ source = transliterate(source);
54
+ }
55
+ return source.toLowerCase().trim().replace(/['’`"]/g, "").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
56
+ }
4
57
  function SlugGenerator({
5
58
  title,
6
59
  value,
@@ -9,44 +62,75 @@ function SlugGenerator({
9
62
  placeholder = "your-slug",
10
63
  buttonText = "Generate",
11
64
  disabled = false,
12
- className = "",
13
- icon
65
+ className,
66
+ icon,
67
+ dir = "ltr",
68
+ transliteratePersian = true
14
69
  }) {
15
- const generateSlug = () => {
16
- const slug = title.toLowerCase().trim().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
17
- onChange(slug);
70
+ const handleGenerate = () => {
71
+ if (!title.trim() || disabled) return;
72
+ onChange(createSlug(title, transliteratePersian));
18
73
  };
19
- return /* @__PURE__ */ jsxs("div", { className, children: [
20
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
21
- /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium", children: [
22
- icon,
23
- label
24
- ] }),
74
+ return /* @__PURE__ */ jsxs("div", { className: clsx("w-full space-y-1.5", className), children: [
75
+ label && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm font-medium text-(--color-text-primary)", children: [
76
+ icon,
77
+ /* @__PURE__ */ jsx("span", { children: label })
78
+ ] }),
79
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
80
+ /* @__PURE__ */ jsx(
81
+ "input",
82
+ {
83
+ dir,
84
+ value,
85
+ onChange: (event) => onChange(event.target.value),
86
+ placeholder,
87
+ disabled,
88
+ onKeyDown: (event) => {
89
+ if (event.key === "Enter") {
90
+ event.preventDefault();
91
+ handleGenerate();
92
+ }
93
+ },
94
+ className: clsx(
95
+ "w-full rounded-lg border border-(--color-border)",
96
+ "bg-(--color-bg-main)",
97
+ "py-3 pe-4 ps-32",
98
+ "text-sm text-(--color-text-primary)",
99
+ "outline-none transition-all",
100
+ "placeholder:text-(--color-text-secondary)/70",
101
+ "hover:border-[color-mix(in_oklab,var(--color-primary)_22%,var(--color-border))]",
102
+ "focus:border-[color-mix(in_oklab,var(--color-primary)_45%,var(--color-border))]",
103
+ "focus:ring-4 focus:ring-[color-mix(in_oklab,var(--color-primary)_12%,transparent)]",
104
+ "dark:bg-white/[0.03]",
105
+ "disabled:cursor-not-allowed disabled:opacity-50"
106
+ )
107
+ }
108
+ ),
25
109
  /* @__PURE__ */ jsxs(
26
110
  "button",
27
111
  {
28
112
  type: "button",
29
- onClick: generateSlug,
113
+ onClick: handleGenerate,
30
114
  disabled: disabled || !title.trim(),
31
- className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium transition disabled:cursor-not-allowed disabled:opacity-50",
115
+ className: clsx(
116
+ "absolute inset-y-1.5 start-1.5",
117
+ "inline-flex items-center justify-center gap-1.5",
118
+ "rounded-md px-3",
119
+ "text-xs font-medium text-white",
120
+ "bg-(--color-primary)",
121
+ "transition-all duration-200",
122
+ "hover:bg-(--color-primary-soft)",
123
+ "focus:outline-none",
124
+ "focus:ring-2 focus:ring-(--color-primary)/30",
125
+ "disabled:cursor-not-allowed disabled:opacity-50"
126
+ ),
32
127
  children: [
33
128
  /* @__PURE__ */ jsx(WandSparkles, { className: "size-4" }),
34
- buttonText
129
+ /* @__PURE__ */ jsx("span", { children: buttonText })
35
130
  ]
36
131
  }
37
132
  )
38
- ] }),
39
- /* @__PURE__ */ jsx(
40
- "input",
41
- {
42
- dir: "ltr",
43
- value,
44
- onChange: (event) => onChange(event.target.value),
45
- placeholder,
46
- disabled,
47
- className: "w-full rounded-lg border px-3 py-3 text-sm outline-none"
48
- }
49
- )
133
+ ] })
50
134
  ] });
51
135
  }
52
136
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/SlugGenerator.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { WandSparkles } from \"lucide-react\";\r\nimport type { SlugGeneratorProps } from \"./types\";\r\n\r\nexport default function SlugGenerator({\r\n title,\r\n value,\r\n onChange,\r\n label = \"Slug\",\r\n placeholder = \"your-slug\",\r\n buttonText = \"Generate\",\r\n disabled = false,\r\n className = \"\",\r\n icon,\r\n}: SlugGeneratorProps) {\r\n const generateSlug = () => {\r\n const slug = title\r\n .toLowerCase()\r\n .trim()\r\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\r\n .replace(/\\s+/g, \"-\")\r\n .replace(/-+/g, \"-\")\r\n .replace(/^-|-$/g, \"\");\r\n\r\n onChange(slug);\r\n };\r\n\r\n return (\r\n <div className={className}>\r\n <div className=\"mb-2 flex items-center justify-between gap-3\">\r\n <label className=\"text-sm font-medium\">\r\n {icon}\r\n {label}\r\n </label>\r\n\r\n <button\r\n type=\"button\"\r\n onClick={generateSlug}\r\n disabled={disabled || !title.trim()}\r\n className=\"inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium transition disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n <WandSparkles className=\"size-4\" />\r\n {buttonText}\r\n </button>\r\n </div>\r\n\r\n <input\r\n dir=\"ltr\"\r\n value={value}\r\n onChange={(event) => onChange(event.target.value)}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n className=\"w-full rounded-lg border px-3 py-3 text-sm outline-none\"\r\n />\r\n </div>\r\n );\r\n}"],"mappings":";AAEA,SAAS,oBAAoB;AA6BrB,SAWE,KAXF;AA1BO,SAAR,cAA+B;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,aAAa;AAAA,EACb,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AACF,GAAuB;AACrB,QAAM,eAAe,MAAM;AACzB,UAAM,OAAO,MACV,YAAY,EACZ,KAAK,EACL,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,QAAQ,GAAG,EACnB,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE;AAEvB,aAAS,IAAI;AAAA,EACf;AAEA,SACE,qBAAC,SAAI,WACH;AAAA,yBAAC,SAAI,WAAU,gDACb;AAAA,2BAAC,WAAM,WAAU,uBACd;AAAA;AAAA,QACA;AAAA,SACH;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU,YAAY,CAAC,MAAM,KAAK;AAAA,UAClC,WAAU;AAAA,UAEV;AAAA,gCAAC,gBAAa,WAAU,UAAS;AAAA,YAChC;AAAA;AAAA;AAAA,MACH;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,KAAI;AAAA,QACJ;AAAA,QACA,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,QAChD;AAAA,QACA;AAAA,QACA,WAAU;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/SlugGenerator.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { WandSparkles } from \"lucide-react\";\r\nimport clsx from \"clsx\";\r\nimport type { SlugGeneratorProps } from \"./types\";\r\n\r\nconst PERSIAN_MAP: Record<string, string> = {\r\n ا: \"a\",\r\n آ: \"a\",\r\n ب: \"b\",\r\n پ: \"p\",\r\n ت: \"t\",\r\n ث: \"s\",\r\n ج: \"j\",\r\n چ: \"ch\",\r\n ح: \"h\",\r\n خ: \"kh\",\r\n د: \"d\",\r\n ذ: \"z\",\r\n ر: \"r\",\r\n ز: \"z\",\r\n ژ: \"zh\",\r\n س: \"s\",\r\n ش: \"sh\",\r\n ص: \"s\",\r\n ض: \"z\",\r\n ط: \"t\",\r\n ظ: \"z\",\r\n ع: \"a\",\r\n غ: \"gh\",\r\n ف: \"f\",\r\n ق: \"gh\",\r\n ک: \"k\",\r\n گ: \"g\",\r\n ل: \"l\",\r\n م: \"m\",\r\n ن: \"n\",\r\n و: \"v\",\r\n ه: \"h\",\r\n ی: \"y\",\r\n ئ: \"y\",\r\n ء: \"a\",\r\n ۀ: \"h\",\r\n ة: \"h\",\r\n ي: \"y\",\r\n ك: \"k\",\r\n};\r\n\r\nconst ARABIC_DIACRITICS = /[\\u064B-\\u065F\\u0670]/g;\r\n\r\nfunction transliterate(text: string): string {\r\n return text\r\n .normalize(\"NFKC\")\r\n .replace(ARABIC_DIACRITICS, \"\")\r\n .split(\"\")\r\n .map((char) => PERSIAN_MAP[char] ?? char)\r\n .join(\"\");\r\n}\r\n\r\nexport function createSlug(\r\n title: string,\r\n transliteratePersian = true,\r\n): string {\r\n let source = title\r\n .normalize(\"NFKC\")\r\n .replace(ARABIC_DIACRITICS, \"\");\r\n\r\n if (transliteratePersian) {\r\n source = transliterate(source);\r\n }\r\n\r\n return source\r\n .toLowerCase()\r\n .trim()\r\n .replace(/['’`\"]/g, \"\")\r\n .replace(/[^a-z0-9\\s-]/g, \"\")\r\n .replace(/\\s+/g, \"-\")\r\n .replace(/-+/g, \"-\")\r\n .replace(/^-+|-+$/g, \"\");\r\n}\r\n\r\nexport default function SlugGenerator({\r\n title,\r\n value,\r\n onChange,\r\n label = \"Slug\",\r\n placeholder = \"your-slug\",\r\n buttonText = \"Generate\",\r\n disabled = false,\r\n className,\r\n icon,\r\n dir = \"ltr\",\r\n transliteratePersian = true,\r\n}: SlugGeneratorProps) {\r\n const handleGenerate = () => {\r\n if (!title.trim() || disabled) return;\r\n\r\n onChange(createSlug(title, transliteratePersian));\r\n };\r\n\r\n return (\r\n <div className={clsx(\"w-full space-y-1.5\", className)}>\r\n {label && (\r\n <label className=\"flex items-center gap-2 text-sm font-medium text-(--color-text-primary)\">\r\n {icon}\r\n <span>{label}</span>\r\n </label>\r\n )}\r\n\r\n <div className=\"relative\">\r\n <input\r\n dir={dir}\r\n value={value}\r\n onChange={(event) => onChange(event.target.value)}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n onKeyDown={(event) => {\r\n if (event.key === \"Enter\") {\r\n event.preventDefault();\r\n handleGenerate();\r\n }\r\n }}\r\n className={clsx(\r\n \"w-full rounded-lg border border-(--color-border)\",\r\n \"bg-(--color-bg-main)\",\r\n \"py-3 pe-4 ps-32\",\r\n \"text-sm text-(--color-text-primary)\",\r\n \"outline-none transition-all\",\r\n \"placeholder:text-(--color-text-secondary)/70\",\r\n \"hover:border-[color-mix(in_oklab,var(--color-primary)_22%,var(--color-border))]\",\r\n \"focus:border-[color-mix(in_oklab,var(--color-primary)_45%,var(--color-border))]\",\r\n \"focus:ring-4 focus:ring-[color-mix(in_oklab,var(--color-primary)_12%,transparent)]\",\r\n \"dark:bg-white/[0.03]\",\r\n \"disabled:cursor-not-allowed disabled:opacity-50\",\r\n )}\r\n />\r\n\r\n <button\r\n type=\"button\"\r\n onClick={handleGenerate}\r\n disabled={disabled || !title.trim()}\r\n className={clsx(\r\n \"absolute inset-y-1.5 start-1.5\",\r\n \"inline-flex items-center justify-center gap-1.5\",\r\n \"rounded-md px-3\",\r\n \"text-xs font-medium text-white\",\r\n \"bg-(--color-primary)\",\r\n \"transition-all duration-200\",\r\n \"hover:bg-(--color-primary-soft)\",\r\n \"focus:outline-none\",\r\n \"focus:ring-2 focus:ring-(--color-primary)/30\",\r\n \"disabled:cursor-not-allowed disabled:opacity-50\",\r\n )}\r\n >\r\n <WandSparkles className=\"size-4\" />\r\n <span>{buttonText}</span>\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n}"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codav/slug-generator",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "A lightweight, reusable TypeScript-first React slug generator component",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -13,9 +13,10 @@
13
13
  "dev": "tsup --watch"
14
14
  },
15
15
  "peerDependencies": {
16
+ "clsx": "^2.1.1",
17
+ "lucide-react": "^0.400.0 || ^1.0.0",
16
18
  "react": "^18.0.0 || ^19.0.0",
17
- "react-dom": "^18.0.0 || ^19.0.0",
18
- "lucide-react": "^0.400.0 || ^1.0.0"
19
+ "react-dom": "^18.0.0 || ^19.0.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@types/react": "^19.0.0",
@@ -25,4 +26,4 @@
25
26
  "typescript": "^5.0.0"
26
27
  },
27
28
  "license": "MIT"
28
- }
29
+ }