@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.
- package/README.md +52 -280
- package/dist/index.js.map +1 -1
- package/package.json +34 -34
package/README.md
CHANGED
|
@@ -1,280 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 →</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.
|
|
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
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
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
|
+
}
|