@ramses1er/cbk-toolkit 1.0.0 → 1.1.2

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.
Files changed (3) hide show
  1. package/README.md +52 -280
  2. package/dist/index.js.map +1 -1
  3. package/package.json +34 -34
package/README.md CHANGED
@@ -1,280 +1,52 @@
1
- # CBK Toolkit
2
-
3
- Package de composants React réutilisables, framework-agnostic.
4
- **Livré avec son propre CSS Tailwind pré-compilé + la police Merriweather** — le consommateur n'a pas besoin d'installer Tailwind.
5
-
6
- ```bash
7
- npm install @ramses1er/cbk-toolkit
8
- ```
9
-
10
- ---
11
-
12
- ## CSS et police
13
-
14
- Le package fournit **tout le CSS nécessaire** (Tailwind + Merriweather).
15
-
16
- ```tsx
17
- import "@ramses1er/cbk-toolkit/styles.css";
18
- ```
19
-
20
- Ne **surtout pas** avoir d'import Tailwind parallèle dans votre CSS, sinon les valeurs du package (police Merriweather, thème) seront écrasées.
21
-
22
- ### Dans un projet Next.js
23
-
24
- Commenter ou supprimer ces lignes dans `src/app/globals.css` :
25
-
26
- ```css
27
- /* @import "tailwindcss"; */
28
-
29
- /* @theme inline {
30
- --color-background: var(--background);
31
- --color-foreground: var(--foreground);
32
- --font-sans: var(--font-geist-sans);
33
- --font-mono: var(--font-geist-mono);
34
- } */
35
-
36
- /* body {
37
- background: var(--background);
38
- color: var(--foreground);
39
- font-family: Arial, Helvetica, sans-serif;
40
- } */
41
- ```
42
-
43
- Supprimer aussi `transpilePackages: ["@ramses1er/cbk-toolkit"]` de `next.config.ts` (inutile, le package est pré-compilé).
44
-
45
- ---
46
-
47
- ## SideNav
48
-
49
- Sidebar de navigation latérale avec header, sous-menus imbriqués, footer et branding configurable. Compatible Next.js, Vite, React Router.
50
-
51
- ### Installation
52
-
53
- ```bash
54
- npm install @ramses1er/cbk-toolkit
55
- ```
56
-
57
- ```tsx
58
- import { SideNav, createSideNav } from "@ramses1er/cbk-toolkit";
59
- import "@ramses1er/cbk-toolkit/styles.css";
60
- import type { NavItem } from "@ramses1er/cbk-toolkit";
61
- ```
62
-
63
- ### API factory (recommandée)
64
-
65
- Créer un fichier de configuration de la sidebar :
66
-
67
- ```ts
68
- // config/side-nav.ts
69
- import { createSideNav } from "@ramses1er/cbk-toolkit";
70
-
71
- export const MySideNav = createSideNav({
72
- brand: {
73
- name: "Genesis",
74
- tagline: "Gestion scolaire",
75
- logo: "/logo.png",
76
- },
77
- mainItems: [
78
- { id: "dashboard", label: "Accueil", icon: "🏠", href: "/dashboard" },
79
- {
80
- id: "students",
81
- label: "Élèves",
82
- icon: "🎓",
83
- href: "/students",
84
- subItems: [
85
- { id: "list", label: "Liste", href: "/students" },
86
- { id: "classes", label: "Classes", href: "/students/classes" },
87
- ],
88
- },
89
- { id: "payments", label: "Paiements", icon: "💳", href: "/payments" },
90
- ],
91
- footer: [
92
- { icon: "⚙️", label: "Paramètres", href: "/settings" },
93
- ],
94
- });
95
- ```
96
-
97
- Le `title` du header est automatiquement déduit du menu actif (son `label` ou son champ `title` optionnel). Voir la section [Titre dynamique](#titre-dynamique).
98
-
99
- ### Intégration dans le layout
100
-
101
- #### Next.js (App Router)
102
-
103
- ```tsx
104
- // app/layout.tsx
105
- "use client";
106
-
107
- import { usePathname } from "next/navigation";
108
- import { MySideNav } from "@/config/side-nav";
109
- import "@ramses1er/cbk-toolkit/styles.css";
110
-
111
- export default function Layout({ children }: { children: React.ReactNode }) {
112
- const pathname = usePathname();
113
-
114
- return (
115
- <html lang="fr">
116
- <body>
117
- <MySideNav currentPath={pathname}>{children}</MySideNav>
118
- </body>
119
- </html>
120
- );
121
- }
122
- ```
123
-
124
- **Notes pour Next.js :**
125
- - Le layout doit être `"use client"` à cause de `usePathname`
126
- - Voir la section [CSS et police](#css-et-politique) pour les lignes à commenter dans `globals.css`
127
- - Pas besoin de `transpilePackages` dans `next.config.ts`
128
- - Pas besoin d'importer des polices via `next/font/google` — le package fournit Merriweather
129
-
130
- #### Vite + React Router
131
-
132
- ```tsx
133
- // src/App.tsx ou src/layouts/MainLayout.tsx
134
- import "@ramses1er/cbk-toolkit/styles.css";
135
- import { MySideNav } from "./config/side-nav";
136
- import { useLocation } from "react-router-dom";
137
-
138
- function MainLayout({ children }: { children: React.ReactNode }) {
139
- const { pathname } = useLocation();
140
-
141
- return <MySideNav currentPath={pathname}>{children}</MySideNav>;
142
- }
143
-
144
- export default MainLayout;
145
- ```
146
-
147
- **Notes pour Vite :**
148
- - Voir la section [CSS et police](#css-et-politique) pour les lignes à commenter dans `globals.css`
149
- - Si vous utilisez PostCSS + Tailwind (`@tailwindcss/postcss`), supprimer la config ou ne pas importer Tailwind dans le CSS
150
- - Pas besoin d'importer des polices via Google Fonts manuellement — le package fournit Merriweather
151
-
152
- #### Vite vanilla (sans React Router)
153
-
154
- ```tsx
155
- import "@ramses1er/cbk-toolkit/styles.css";
156
- import { MySideNav } from "./config/side-nav";
157
- import { useState, useEffect } from "react";
158
-
159
- function App() {
160
- const [pathname, setPathname] = useState(window.location.pathname);
161
-
162
- useEffect(() => {
163
- const onPopState = () => setPathname(window.location.pathname);
164
- window.addEventListener("popstate", onPopState);
165
- return () => window.removeEventListener("popstate", onPopState);
166
- }, []);
167
-
168
- return <MySideNav currentPath={pathname}>{children}</MySideNav>;
169
- }
170
- ```
171
-
172
- ### API directe
173
-
174
- ```tsx
175
- <SideNav
176
- currentPath={pathname}
177
- brand={{ name: "Mon App", logo: "/logo.svg", tagline: "Slogan" }}
178
- mainItems={[
179
- { id: "dashboard", label: "Dashboard", icon: "🏠", href: "/dashboard" },
180
- { id: "students", label: "Élèves", icon: "🎓", href: "/students" },
181
- ]}
182
- title="Dashboard"
183
- >
184
- <YourContent />
185
- </SideNav>
186
- ```
187
-
188
- ---
189
-
190
- ## Titre dynamique
191
-
192
- Le header affiché en haut de la page (à droite de la sidebar) est déterminé automatiquement selon cet ordre de priorité :
193
-
194
- 1. **`title` explicite** (prop passée à `SideNav` ou `createSideNav`)
195
- 2. **`title` optionnel** dans `NavItem` de l'entrée active
196
- 3. **`label`** de l'entrée active (par défaut)
197
-
198
- ```ts
199
- mainItems: [
200
- {
201
- id: "dashboard",
202
- label: "Accueil", // header = "Accueil"
203
- title: "Tableau de bord", // header = "Tableau de bord" si défini
204
- href: "/dashboard",
205
- },
206
- ];
207
- ```
208
-
209
- ---
210
-
211
- ## Props
212
-
213
- ### SideNavStaticConfig (configuration statique)
214
-
215
- | Prop | Type | Défaut | Description |
216
- |------|------|--------|-------------|
217
- | `mainItems` | `NavItem[]` | requis | Entrées de navigation |
218
- | `brand` | `{ logo?, name?, tagline? }` | — | Branding en haut de la sidebar |
219
- | `footer` | `{ icon?, label, href }[]` | — | Liens en pied de sidebar |
220
- | `title` | `string` | — | Titre du header (override le titre auto) |
221
- | `toggleIcon` | `ReactNode` | `"▼"/"▶"` | Icône de toggle des sous-menus |
222
-
223
- ### Props dynamiques (passées au render)
224
-
225
- | Prop | Type | Défaut | Description |
226
- |------|------|--------|-------------|
227
- | `children` | `ReactNode` | requis | Contenu principal |
228
- | `currentPath` | `string` | requis | Chemin actif pour le surlignage |
229
-
230
- ### NavItem
231
-
232
- ```ts
233
- type NavItem = {
234
- id: string;
235
- label: string;
236
- title?: string; // Optionnel : surcharge le titre dans le header
237
- icon?: ReactNode;
238
- href: string;
239
- subItems?: NavItem[];
240
- };
241
- ```
242
-
243
- ---
244
-
245
- ## Police Merriweather
246
-
247
- Le package charge et applique automatiquement Merriweather (poids 300, 400, 700, 900) via Google Fonts avec `display=swap`. Aucune configuration requise.
248
-
249
- **Pour que la police fonctionne :**
250
- - Le CSS du package (`@ramses1er/cbk-toolkit/styles.css`) doit être importé **avant** le CSS local
251
- - Ne pas avoir d'import Tailwind parallèle qui écrase `--font-sans`
252
- - Ne pas avoir `body { font-family: ... }` dans son propre CSS qui écrase la valeur
253
-
254
- ---
255
-
256
- ## Composants commentés
257
-
258
- Les composants suivants sont temporairement mis en commentaire en attendant leur refactoring :
259
-
260
- Confirmation, Date, ErrorDisplay, FieldContainer, Form, HeaderContent, Input, InputSearch, InstallPrompt, Label, LoaderSubmit, Loading, Modal, Option, Select, SelectSearch, SwRegistration, Table, TextArea, TopicIllustration
261
-
262
- ---
263
-
264
- ## Développement
265
-
266
- ```bash
267
- pnpm install
268
- pnpm dev # Build watch avec tsup (JS uniquement)
269
- pnpm build # Build JS + CSS de production (dist/)
270
- ```
271
-
272
- Le CSS est généré via Tailwind v4 en scannant les sources `packages/*/src/`.
273
- Les fichiers de sortie :
274
-
275
- ```
276
- dist/
277
- index.js # Bundle JS (composants)
278
- index.d.ts # Types TypeScript
279
- styles.css # CSS Tailwind compilé + Merriweather (minifié)
280
- ```
1
+ <div align="center">
2
+ <br/>
3
+ <h1>CBK Toolkit</h1>
4
+ <p>
5
+ <strong>Chaque nouveau projet devient une amélioration du précédent.</strong>
6
+ <br/>
7
+ Et non plus un redémarrage à zéro.
8
+ </p>
9
+ <br/>
10
+ </div>
11
+
12
+ ---
13
+
14
+ Chaque fois que je démarre un nouveau projet, je me retrouve face au même problème : je réécris les mêmes bases.
15
+
16
+ Structure UI, composants, logique répétitive, patterns d'architecture…
17
+
18
+ J'ai donc décidé de changer ça.
19
+
20
+ Je travaille sur mon propre **toolkit**, déjà publié sur npm, qui me permet de centraliser et réutiliser mes composants et patterns de développement.
21
+
22
+ L'objectif n'est pas seulement de gagner du temps sur mes projets.
23
+
24
+ C'est de construire une **base stable** que je peux améliorer à chaque nouveau projet, et éventuellement partager avec d'autres développeurs.
25
+
26
+ <br/>
27
+
28
+ <div align="center">
29
+ <em>Chaque nouveau projet devient une amélioration du précédent.</em>
30
+ <br/>
31
+ <strong>Et non plus un redémarrage à zéro.</strong>
32
+ </div>
33
+
34
+ <br/>
35
+
36
+ ---
37
+
38
+ <div align="center">
39
+ <p>
40
+ <a href="https://cbk-toolkit.cbkapps.com/docs" target="_blank">
41
+ <strong>📖 Voir la documentation &rarr;</strong>
42
+ </a>
43
+ <br/><br/>
44
+ <code>npm install @ramses1er/cbk-toolkit</code>
45
+ </p>
46
+ <br/>
47
+ <p>
48
+ <small>
49
+ #npm #typescript #webdevelopment #opensource #frontend #softwareengineering
50
+ </small>
51
+ </p>
52
+ </div>
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../packages/ui/src/layouts/side-nav.tsx","../packages/ui/src/config/create-side-nav.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n ReactNode,\n useState,\n createElement,\n isValidElement,\n type ComponentType,\n} from \"react\";\n\ntype IconType = ReactNode | ComponentType<{ size?: number }>;\n\nexport interface NavItem {\n id: string;\n label: string;\n title?: string;\n icon?: IconType;\n href: string;\n subItems?: NavItem[];\n}\n\nexport interface SideNavStaticConfig {\n brand?: {\n logo?: ReactNode;\n name?: string;\n tagline?: string;\n };\n mainItems: NavItem[];\n title?: string;\n footer?: { icon?: IconType; label: string; href: string }[];\n toggleIcon?: ReactNode;\n}\n\nexport interface SideNavProps extends SideNavStaticConfig {\n children: ReactNode;\n currentPath: string;\n}\n\nfunction isActive(href: string, currentPath: string) {\n return currentPath === href || currentPath.startsWith(href + \"/\");\n}\n\nfunction isExactActive(href: string, currentPath: string) {\n return currentPath === href;\n}\n\nexport function SideNav({\n children,\n title,\n mainItems,\n currentPath,\n brand,\n footer,\n toggleIcon,\n}: SideNavProps) {\n const activeMain = mainItems.find((item) => isActive(item.href, currentPath));\n const headerTitle = title ?? activeMain?.title ?? activeMain?.label;\n const [expandedSection, setExpandedSection] = useState<string | null>(\n activeMain?.id ?? null,\n );\n\n const toggleSection = (id: string) => {\n setExpandedSection((prev) => (prev === id ? null : id));\n };\n\n return (\n <div className=\"flex min-h-screen\">\n <aside className=\"w-64 bg-white border-r border-gray-200 flex flex-col fixed h-full\">\n {brand && (\n <div className=\"p-[6.5px] border-b border-gray-100\">\n <div className=\"flex flex-row gap-2 items-center\">\n {brand.logo &&\n (typeof brand.logo === \"string\" ? (\n <img\n src={brand.logo}\n alt={brand.name ?? \"\"}\n className=\"w-12.5 h-12.5\"\n />\n ) : (\n brand.logo\n ))}\n {brand.name && (\n <p className=\"text-gray-500 text-base font-semibold\">\n {brand.name}\n </p>\n )}\n </div>\n </div>\n )}\n\n <nav className=\"flex-1 p-4 overflow-y-auto\">\n <ul className=\"space-y-1\">\n {mainItems.map((item) => {\n const active = isActive(item.href, currentPath);\n const isExpanded = expandedSection === item.id;\n const hasSubItems = item.subItems && item.subItems.length > 0;\n\n return (\n <li key={item.id}>\n <div className=\"flex items-center gap-1\">\n <a\n href={item.href}\n className={`flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors flex-1 ${\n active\n ? \"bg-gray-200 text-black\"\n : \"text-gray-500 hover:bg-gray-100\"\n }`}\n >\n {item.icon &&\n (typeof item.icon === \"string\" ? (\n <span className=\"text-lg\">{item.icon}</span>\n ) : isValidElement(item.icon) ? (\n item.icon\n ) : (\n createElement(\n item.icon as ComponentType<{ size?: number }>,\n { size: 20 },\n )\n ))}\n {item.label}\n </a>\n {active && hasSubItems && (\n <button\n onClick={() => toggleSection(item.id)}\n className={`p-1.5 rounded-lg transition-colors ${\n active\n ? \"text-black hover:bg-gray-100\"\n : \"text-gray-500 hover:bg-gray-100\"\n }`}\n title={isExpanded ? \"Réduire\" : \"Développer\"}\n >\n {toggleIcon ? (\n <span\n className={`inline-block transition-transform duration-200 ${\n isExpanded ? \"rotate-0\" : \"-rotate-90\"\n }`}\n >\n {toggleIcon}\n </span>\n ) : (\n <span className=\"text-xs\">\n {isExpanded ? \"▼\" : \"▶\"}\n </span>\n )}\n </button>\n )}\n </div>\n\n {active && hasSubItems && (\n <div\n className={`grid transition-all duration-300 ease-in-out ${\n isExpanded ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\"\n }`}\n >\n <div className=\"overflow-hidden\">\n <ul className=\"ml-4 mt-1 space-y-1 pl-2\">\n {item.subItems!.map((sub) => (\n <li key={sub.id}>\n <a\n href={sub.href}\n className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${\n isExactActive(sub.href, currentPath)\n ? \"bg-gray-200 text-black\"\n : \"text-gray-500 hover:bg-gray-100\"\n }`}\n >\n {sub.label}\n </a>\n </li>\n ))}\n </ul>\n </div>\n </div>\n )}\n </li>\n );\n })}\n </ul>\n </nav>\n\n {footer && footer.length > 0 && (\n <div className=\"p-4 border-t border-gray-100 space-y-1\">\n {footer.map((item) => (\n <a\n key={item.label}\n href={item.href}\n className=\"flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors\"\n >\n {typeof item.icon === \"string\" ? (\n <span className=\"text-lg\">{item.icon}</span>\n ) : isValidElement(item.icon) ? (\n item.icon\n ) : (\n createElement(item.icon as ComponentType<{ size?: number }>, {\n size: 20,\n })\n )}\n {item.label}\n </a>\n ))}\n </div>\n )}\n </aside>\n\n <div className=\"flex-1 ml-64\">\n {headerTitle && (\n <header className=\"h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 fixed top-0 right-0 left-64 z-10\">\n <h1 className=\"text-base font-semibold text-gray-500\">\n {headerTitle}\n </h1>\n {brand?.tagline && (\n <span className=\"text-sm text-gray-500\">{brand.tagline}</span>\n )}\n </header>\n )}\n <main className={headerTitle ? \"p-6 pt-20\" : \"p-6\"}>{children}</main>\n </div>\n </div>\n );\n}\n","import { SideNav, type SideNavStaticConfig } from \"../layouts/side-nav\";\n\nexport function createSideNav(config: SideNavStaticConfig) {\n return function ConfiguredSideNav({\n children,\n currentPath,\n }: {\n children: React.ReactNode;\n currentPath: string;\n }) {\n return (\n <SideNav {...config} currentPath={currentPath}>\n {children}\n </SideNav>\n );\n };\n}\n"],"mappings":"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","names":["jsx"]}
1
+ {"version":3,"sources":["../packages/ui/src/layouts/side-nav.tsx","../packages/ui/src/config/create-side-nav.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport {\r\n ReactNode,\r\n useState,\r\n createElement,\r\n isValidElement,\r\n type ComponentType,\r\n} from \"react\";\r\n\r\ntype IconType = ReactNode | ComponentType<{ size?: number }>;\r\n\r\nexport interface NavItem {\r\n id: string;\r\n label: string;\r\n title?: string;\r\n icon?: IconType;\r\n href: string;\r\n subItems?: NavItem[];\r\n}\r\n\r\nexport interface SideNavStaticConfig {\r\n brand?: {\r\n logo?: ReactNode;\r\n name?: string;\r\n tagline?: string;\r\n };\r\n mainItems: NavItem[];\r\n title?: string;\r\n footer?: { icon?: IconType; label: string; href: string }[];\r\n toggleIcon?: ReactNode;\r\n}\r\n\r\nexport interface SideNavProps extends SideNavStaticConfig {\r\n children: ReactNode;\r\n currentPath: string;\r\n}\r\n\r\nfunction isActive(href: string, currentPath: string) {\r\n return currentPath === href || currentPath.startsWith(href + \"/\");\r\n}\r\n\r\nfunction isExactActive(href: string, currentPath: string) {\r\n return currentPath === href;\r\n}\r\n\r\nexport function SideNav({\r\n children,\r\n title,\r\n mainItems,\r\n currentPath,\r\n brand,\r\n footer,\r\n toggleIcon,\r\n}: SideNavProps) {\r\n const activeMain = mainItems.find((item) => isActive(item.href, currentPath));\r\n const headerTitle = title ?? activeMain?.title ?? activeMain?.label;\r\n const [expandedSection, setExpandedSection] = useState<string | null>(\r\n activeMain?.id ?? null,\r\n );\r\n\r\n const toggleSection = (id: string) => {\r\n setExpandedSection((prev) => (prev === id ? null : id));\r\n };\r\n\r\n return (\r\n <div className=\"flex min-h-screen\">\r\n <aside className=\"w-64 bg-white border-r border-gray-200 flex flex-col fixed h-full\">\r\n {brand && (\r\n <div className=\"p-[6.5px] border-b border-gray-100\">\r\n <div className=\"flex flex-row gap-2 items-center\">\r\n {brand.logo &&\r\n (typeof brand.logo === \"string\" ? (\r\n <img\r\n src={brand.logo}\r\n alt={brand.name ?? \"\"}\r\n className=\"w-12.5 h-12.5\"\r\n />\r\n ) : (\r\n brand.logo\r\n ))}\r\n {brand.name && (\r\n <p className=\"text-gray-500 text-base font-semibold\">\r\n {brand.name}\r\n </p>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n <nav className=\"flex-1 p-4 overflow-y-auto\">\r\n <ul className=\"space-y-1\">\r\n {mainItems.map((item) => {\r\n const active = isActive(item.href, currentPath);\r\n const isExpanded = expandedSection === item.id;\r\n const hasSubItems = item.subItems && item.subItems.length > 0;\r\n\r\n return (\r\n <li key={item.id}>\r\n <div className=\"flex items-center gap-1\">\r\n <a\r\n href={item.href}\r\n className={`flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors flex-1 ${\r\n active\r\n ? \"bg-gray-200 text-black\"\r\n : \"text-gray-500 hover:bg-gray-100\"\r\n }`}\r\n >\r\n {item.icon &&\r\n (typeof item.icon === \"string\" ? (\r\n <span className=\"text-lg\">{item.icon}</span>\r\n ) : isValidElement(item.icon) ? (\r\n item.icon\r\n ) : (\r\n createElement(\r\n item.icon as ComponentType<{ size?: number }>,\r\n { size: 20 },\r\n )\r\n ))}\r\n {item.label}\r\n </a>\r\n {active && hasSubItems && (\r\n <button\r\n onClick={() => toggleSection(item.id)}\r\n className={`p-1.5 rounded-lg transition-colors ${\r\n active\r\n ? \"text-black hover:bg-gray-100\"\r\n : \"text-gray-500 hover:bg-gray-100\"\r\n }`}\r\n title={isExpanded ? \"Réduire\" : \"Développer\"}\r\n >\r\n {toggleIcon ? (\r\n <span\r\n className={`inline-block transition-transform duration-200 ${\r\n isExpanded ? \"rotate-0\" : \"-rotate-90\"\r\n }`}\r\n >\r\n {toggleIcon}\r\n </span>\r\n ) : (\r\n <span className=\"text-xs\">\r\n {isExpanded ? \"▼\" : \"▶\"}\r\n </span>\r\n )}\r\n </button>\r\n )}\r\n </div>\r\n\r\n {active && hasSubItems && (\r\n <div\r\n className={`grid transition-all duration-300 ease-in-out ${\r\n isExpanded ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\"\r\n }`}\r\n >\r\n <div className=\"overflow-hidden\">\r\n <ul className=\"ml-4 mt-1 space-y-1 pl-2\">\r\n {item.subItems!.map((sub) => (\r\n <li key={sub.id}>\r\n <a\r\n href={sub.href}\r\n className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${\r\n isExactActive(sub.href, currentPath)\r\n ? \"bg-gray-200 text-black\"\r\n : \"text-gray-500 hover:bg-gray-100\"\r\n }`}\r\n >\r\n {sub.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n </div>\r\n )}\r\n </li>\r\n );\r\n })}\r\n </ul>\r\n </nav>\r\n\r\n {footer && footer.length > 0 && (\r\n <div className=\"p-4 border-t border-gray-100 space-y-1\">\r\n {footer.map((item) => (\r\n <a\r\n key={item.label}\r\n href={item.href}\r\n className=\"flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors\"\r\n >\r\n {typeof item.icon === \"string\" ? (\r\n <span className=\"text-lg\">{item.icon}</span>\r\n ) : isValidElement(item.icon) ? (\r\n item.icon\r\n ) : (\r\n createElement(item.icon as ComponentType<{ size?: number }>, {\r\n size: 20,\r\n })\r\n )}\r\n {item.label}\r\n </a>\r\n ))}\r\n </div>\r\n )}\r\n </aside>\r\n\r\n <div className=\"flex-1 ml-64\">\r\n {headerTitle && (\r\n <header className=\"h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 fixed top-0 right-0 left-64 z-10\">\r\n <h1 className=\"text-base font-semibold text-gray-500\">\r\n {headerTitle}\r\n </h1>\r\n {brand?.tagline && (\r\n <span className=\"text-sm text-gray-500\">{brand.tagline}</span>\r\n )}\r\n </header>\r\n )}\r\n <main className={headerTitle ? \"p-6 pt-20\" : \"p-6\"}>{children}</main>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { SideNav, type SideNavStaticConfig } from \"../layouts/side-nav\";\n\nexport function createSideNav(config: SideNavStaticConfig) {\n return function ConfiguredSideNav({\n children,\n currentPath,\n }: {\n children: React.ReactNode;\n currentPath: string;\n }) {\n return (\n <SideNav {...config} currentPath={currentPath}>\n {children}\n </SideNav>\n );\n };\n}\n"],"mappings":"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","names":["jsx"]}
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
- {
2
- "name": "@ramses1er/cbk-toolkit",
3
- "version": "1.0.0",
4
- "type": "module",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.js",
7
- "exports": {
8
- ".": {
9
- "import": "./dist/index.js",
10
- "types": "./dist/index.d.ts"
11
- },
12
- "./styles.css": "./dist/styles.css"
13
- },
14
- "files": [
15
- "dist"
16
- ],
17
- "peerDependencies": {
18
- "react": ">=18",
19
- "react-dom": ">=18"
20
- },
21
- "devDependencies": {
22
- "@tailwindcss/cli": "^4.3.1",
23
- "@types/react": "^19",
24
- "@types/react-dom": "^19",
25
- "tailwindcss": "^4.3.1",
26
- "tsup": "^8.5.0"
27
- },
28
- "scripts": {
29
- "build": "tsup && pnpm build:css",
30
- "build:css": "pnpm exec tailwindcss -i src/styles.css -o dist/styles.css --minify",
31
- "dev": "tsup --watch",
32
- "clean": "rm -rf dist"
33
- }
34
- }
1
+ {
2
+ "name": "@ramses1er/cbk-toolkit",
3
+ "version": "1.1.2",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./styles.css": "./dist/styles.css"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup && pnpm build:css",
19
+ "build:css": "pnpm exec tailwindcss -i src/styles.css -o dist/styles.css --minify",
20
+ "dev": "tsup --watch",
21
+ "clean": "rm -rf dist"
22
+ },
23
+ "peerDependencies": {
24
+ "react": ">=18",
25
+ "react-dom": ">=18"
26
+ },
27
+ "devDependencies": {
28
+ "@tailwindcss/cli": "^4.3.1",
29
+ "@types/react": "^19",
30
+ "@types/react-dom": "^19",
31
+ "tailwindcss": "^4.3.1",
32
+ "tsup": "^8.5.0"
33
+ }
34
+ }