@creezio/observability 0.22.0

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 (209) hide show
  1. package/README.md +328 -0
  2. package/dist/.creezio-src-hash.json +4 -0
  3. package/dist/api-endpoints/http-routes.d.ts +11 -0
  4. package/dist/api-endpoints/http-routes.d.ts.map +1 -0
  5. package/dist/api-endpoints/http-routes.js +11 -0
  6. package/dist/api-endpoints/http-routes.js.map +1 -0
  7. package/dist/api-endpoints/index.d.ts +4 -0
  8. package/dist/api-endpoints/index.d.ts.map +1 -0
  9. package/dist/api-endpoints/index.js +3 -0
  10. package/dist/api-endpoints/index.js.map +1 -0
  11. package/dist/api-endpoints/registry.d.ts +66 -0
  12. package/dist/api-endpoints/registry.d.ts.map +1 -0
  13. package/dist/api-endpoints/registry.js +102 -0
  14. package/dist/api-endpoints/registry.js.map +1 -0
  15. package/dist/api-mount.d.ts +7 -0
  16. package/dist/api-mount.d.ts.map +1 -0
  17. package/dist/api-mount.js +114 -0
  18. package/dist/api-mount.js.map +1 -0
  19. package/dist/helpers.d.ts +23 -0
  20. package/dist/helpers.d.ts.map +1 -0
  21. package/dist/helpers.js +36 -0
  22. package/dist/helpers.js.map +1 -0
  23. package/dist/index.d.ts +35 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +24 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/memory-store.d.ts +3 -0
  28. package/dist/memory-store.d.ts.map +1 -0
  29. package/dist/memory-store.js +113 -0
  30. package/dist/memory-store.js.map +1 -0
  31. package/dist/ops/emit.d.ts +8 -0
  32. package/dist/ops/emit.d.ts.map +1 -0
  33. package/dist/ops/emit.js +15 -0
  34. package/dist/ops/emit.js.map +1 -0
  35. package/dist/ops/fleet-activity.d.ts +50 -0
  36. package/dist/ops/fleet-activity.d.ts.map +1 -0
  37. package/dist/ops/fleet-activity.js +123 -0
  38. package/dist/ops/fleet-activity.js.map +1 -0
  39. package/dist/ops/fleet-agent.d.ts +102 -0
  40. package/dist/ops/fleet-agent.d.ts.map +1 -0
  41. package/dist/ops/fleet-agent.js +352 -0
  42. package/dist/ops/fleet-agent.js.map +1 -0
  43. package/dist/ops/fleet-samples.d.ts +33 -0
  44. package/dist/ops/fleet-samples.d.ts.map +1 -0
  45. package/dist/ops/fleet-samples.js +223 -0
  46. package/dist/ops/fleet-samples.js.map +1 -0
  47. package/dist/ops/journal.d.ts +56 -0
  48. package/dist/ops/journal.d.ts.map +1 -0
  49. package/dist/ops/journal.js +307 -0
  50. package/dist/ops/journal.js.map +1 -0
  51. package/dist/ops/rules.d.ts +16 -0
  52. package/dist/ops/rules.d.ts.map +1 -0
  53. package/dist/ops/rules.js +98 -0
  54. package/dist/ops/rules.js.map +1 -0
  55. package/dist/ops/types.d.ts +69 -0
  56. package/dist/ops/types.d.ts.map +1 -0
  57. package/dist/ops/types.js +119 -0
  58. package/dist/ops/types.js.map +1 -0
  59. package/dist/request-logs/config.d.ts +19 -0
  60. package/dist/request-logs/config.d.ts.map +1 -0
  61. package/dist/request-logs/config.js +36 -0
  62. package/dist/request-logs/config.js.map +1 -0
  63. package/dist/request-logs/http-routes.d.ts +8 -0
  64. package/dist/request-logs/http-routes.d.ts.map +1 -0
  65. package/dist/request-logs/http-routes.js +29 -0
  66. package/dist/request-logs/http-routes.js.map +1 -0
  67. package/dist/request-logs/index.d.ts +6 -0
  68. package/dist/request-logs/index.d.ts.map +1 -0
  69. package/dist/request-logs/index.js +5 -0
  70. package/dist/request-logs/index.js.map +1 -0
  71. package/dist/request-logs/middleware.d.ts +11 -0
  72. package/dist/request-logs/middleware.d.ts.map +1 -0
  73. package/dist/request-logs/middleware.js +198 -0
  74. package/dist/request-logs/middleware.js.map +1 -0
  75. package/dist/request-logs/request-logs.d.ts +60 -0
  76. package/dist/request-logs/request-logs.d.ts.map +1 -0
  77. package/dist/request-logs/request-logs.js +260 -0
  78. package/dist/request-logs/request-logs.js.map +1 -0
  79. package/dist/schema.d.ts +3 -0
  80. package/dist/schema.d.ts.map +1 -0
  81. package/dist/schema.js +21 -0
  82. package/dist/schema.js.map +1 -0
  83. package/dist/sqlite-driver.d.ts +16 -0
  84. package/dist/sqlite-driver.d.ts.map +1 -0
  85. package/dist/sqlite-driver.js +15 -0
  86. package/dist/sqlite-driver.js.map +1 -0
  87. package/dist/sqlite-store.d.ts +12 -0
  88. package/dist/sqlite-store.d.ts.map +1 -0
  89. package/dist/sqlite-store.js +176 -0
  90. package/dist/sqlite-store.js.map +1 -0
  91. package/dist/types.d.ts +67 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +9 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/usage/adapters.d.ts +31 -0
  96. package/dist/usage/adapters.d.ts.map +1 -0
  97. package/dist/usage/adapters.js +37 -0
  98. package/dist/usage/adapters.js.map +1 -0
  99. package/dist/usage/http-routes.d.ts +23 -0
  100. package/dist/usage/http-routes.d.ts.map +1 -0
  101. package/dist/usage/http-routes.js +150 -0
  102. package/dist/usage/http-routes.js.map +1 -0
  103. package/dist/usage/index.d.ts +11 -0
  104. package/dist/usage/index.d.ts.map +1 -0
  105. package/dist/usage/index.js +7 -0
  106. package/dist/usage/index.js.map +1 -0
  107. package/dist/usage/ui-brand.d.ts +23 -0
  108. package/dist/usage/ui-brand.d.ts.map +1 -0
  109. package/dist/usage/ui-brand.js +18 -0
  110. package/dist/usage/ui-brand.js.map +1 -0
  111. package/dist/usage/usage-analytics-productivity.d.ts +73 -0
  112. package/dist/usage/usage-analytics-productivity.d.ts.map +1 -0
  113. package/dist/usage/usage-analytics-productivity.js +317 -0
  114. package/dist/usage/usage-analytics-productivity.js.map +1 -0
  115. package/dist/usage/usage-analytics-shared.d.ts +5 -0
  116. package/dist/usage/usage-analytics-shared.d.ts.map +1 -0
  117. package/dist/usage/usage-analytics-shared.js +20 -0
  118. package/dist/usage/usage-analytics-shared.js.map +1 -0
  119. package/dist/usage/usage-analytics.d.ts +127 -0
  120. package/dist/usage/usage-analytics.d.ts.map +1 -0
  121. package/dist/usage/usage-analytics.js +338 -0
  122. package/dist/usage/usage-analytics.js.map +1 -0
  123. package/dist-cjs/api-endpoints/http-routes.js +14 -0
  124. package/dist-cjs/api-endpoints/http-routes.js.map +1 -0
  125. package/dist-cjs/api-endpoints/index.js +10 -0
  126. package/dist-cjs/api-endpoints/index.js.map +1 -0
  127. package/dist-cjs/api-endpoints/registry.js +107 -0
  128. package/dist-cjs/api-endpoints/registry.js.map +1 -0
  129. package/dist-cjs/api-mount.js +117 -0
  130. package/dist-cjs/api-mount.js.map +1 -0
  131. package/dist-cjs/helpers.js +41 -0
  132. package/dist-cjs/helpers.js.map +1 -0
  133. package/dist-cjs/index.js +118 -0
  134. package/dist-cjs/index.js.map +1 -0
  135. package/dist-cjs/memory-store.js +119 -0
  136. package/dist-cjs/memory-store.js.map +1 -0
  137. package/dist-cjs/ops/emit.js +18 -0
  138. package/dist-cjs/ops/emit.js.map +1 -0
  139. package/dist-cjs/ops/fleet-activity.js +131 -0
  140. package/dist-cjs/ops/fleet-activity.js.map +1 -0
  141. package/dist-cjs/ops/fleet-agent.js +358 -0
  142. package/dist-cjs/ops/fleet-agent.js.map +1 -0
  143. package/dist-cjs/ops/fleet-samples.js +229 -0
  144. package/dist-cjs/ops/fleet-samples.js.map +1 -0
  145. package/dist-cjs/ops/journal.js +326 -0
  146. package/dist-cjs/ops/journal.js.map +1 -0
  147. package/dist-cjs/ops/rules.js +102 -0
  148. package/dist-cjs/ops/rules.js.map +1 -0
  149. package/dist-cjs/ops/types.js +126 -0
  150. package/dist-cjs/ops/types.js.map +1 -0
  151. package/dist-cjs/package.json +3 -0
  152. package/dist-cjs/request-logs/config.js +42 -0
  153. package/dist-cjs/request-logs/config.js.map +1 -0
  154. package/dist-cjs/request-logs/http-routes.js +32 -0
  155. package/dist-cjs/request-logs/http-routes.js.map +1 -0
  156. package/dist-cjs/request-logs/index.js +27 -0
  157. package/dist-cjs/request-logs/index.js.map +1 -0
  158. package/dist-cjs/request-logs/middleware.js +203 -0
  159. package/dist-cjs/request-logs/middleware.js.map +1 -0
  160. package/dist-cjs/request-logs/request-logs.js +277 -0
  161. package/dist-cjs/request-logs/request-logs.js.map +1 -0
  162. package/dist-cjs/schema.js +24 -0
  163. package/dist-cjs/schema.js.map +1 -0
  164. package/dist-cjs/sqlite-driver.js +18 -0
  165. package/dist-cjs/sqlite-driver.js.map +1 -0
  166. package/dist-cjs/sqlite-store.js +182 -0
  167. package/dist-cjs/sqlite-store.js.map +1 -0
  168. package/dist-cjs/types.js +12 -0
  169. package/dist-cjs/types.js.map +1 -0
  170. package/dist-cjs/usage/adapters.js +47 -0
  171. package/dist-cjs/usage/adapters.js.map +1 -0
  172. package/dist-cjs/usage/http-routes.js +154 -0
  173. package/dist-cjs/usage/http-routes.js.map +1 -0
  174. package/dist-cjs/usage/index.js +34 -0
  175. package/dist-cjs/usage/index.js.map +1 -0
  176. package/dist-cjs/usage/ui-brand.js +23 -0
  177. package/dist-cjs/usage/ui-brand.js.map +1 -0
  178. package/dist-cjs/usage/usage-analytics-productivity.js +321 -0
  179. package/dist-cjs/usage/usage-analytics-productivity.js.map +1 -0
  180. package/dist-cjs/usage/usage-analytics-shared.js +23 -0
  181. package/dist-cjs/usage/usage-analytics-shared.js.map +1 -0
  182. package/dist-cjs/usage/usage-analytics.js +353 -0
  183. package/dist-cjs/usage/usage-analytics.js.map +1 -0
  184. package/fleet-collector/AGENTS.md +7 -0
  185. package/fleet-collector/README.md +83 -0
  186. package/fleet-collector/configure-fleet-npm.sh +108 -0
  187. package/fleet-collector/env.mjs +174 -0
  188. package/fleet-collector/ops-api.mjs +455 -0
  189. package/fleet-collector/public/admin.html +696 -0
  190. package/fleet-collector/public/index.html +941 -0
  191. package/fleet-collector/server.mjs +564 -0
  192. package/fleet-collector/test-fleet-collector.mjs +413 -0
  193. package/fleet-collector/test-server-admin.mjs +475 -0
  194. package/package.json +109 -0
  195. package/scripts/test-api-endpoints-registry.mjs +60 -0
  196. package/ui/analytics-client.tsx +834 -0
  197. package/ui/analytics-productivity-panel.tsx +513 -0
  198. package/ui/api-endpoints-client.tsx +228 -0
  199. package/ui/index.ts +39 -0
  200. package/ui/primitives/badge.tsx +37 -0
  201. package/ui/primitives/button.tsx +44 -0
  202. package/ui/primitives/card.tsx +44 -0
  203. package/ui/primitives/cn.ts +6 -0
  204. package/ui/primitives/input.tsx +19 -0
  205. package/ui/primitives/tabs.tsx +51 -0
  206. package/ui/request-logs-client.tsx +327 -0
  207. package/ui/session-usage-analytics-provider.tsx +28 -0
  208. package/ui/usage-analytics-client.ts +479 -0
  209. package/ui/usage-analytics-provider.tsx +84 -0
@@ -0,0 +1,228 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import { Braces, RefreshCw, Search } from "lucide-react";
5
+ import { Badge } from "./primitives/badge";
6
+ import { Button } from "./primitives/button";
7
+ import { Card, CardContent, CardHeader, CardTitle } from "./primitives/card";
8
+ import { Input } from "./primitives/input";
9
+
10
+ type Endpoint = {
11
+ method: string;
12
+ path: string;
13
+ documented: boolean;
14
+ summary: string | null;
15
+ description: string | null;
16
+ tags: string[];
17
+ };
18
+
19
+ type RegistryResponse = {
20
+ generatedAt: string;
21
+ source: string;
22
+ openapiUrl: string;
23
+ endpoints: Endpoint[];
24
+ error?: string;
25
+ };
26
+
27
+ const METHOD_STYLE: Record<string, string> = {
28
+ GET: "bg-emerald-100 text-emerald-800",
29
+ POST: "bg-sky-100 text-sky-800",
30
+ PUT: "bg-amber-100 text-amber-800",
31
+ PATCH: "bg-violet-100 text-violet-800",
32
+ DELETE: "bg-red-100 text-red-800",
33
+ OPTIONS: "bg-slate-100 text-slate-700",
34
+ HEAD: "bg-slate-100 text-slate-700",
35
+ };
36
+
37
+ export function ApiEndpointsClient() {
38
+ const [registry, setRegistry] = useState<RegistryResponse | null>(null);
39
+ const [query, setQuery] = useState("");
40
+ const [method, setMethod] = useState("ALL");
41
+ const [loading, setLoading] = useState(true);
42
+ const [error, setError] = useState("");
43
+
44
+ const refresh = useCallback(async () => {
45
+ setLoading(true);
46
+ setError("");
47
+ try {
48
+ const response = await fetch("/api/v1/admin/endpoints");
49
+ const body = (await response.json()) as RegistryResponse;
50
+ if (!response.ok) throw new Error(body.error || "Chargement impossible");
51
+ setRegistry(body);
52
+ } catch (cause) {
53
+ setError(cause instanceof Error ? cause.message : "Chargement impossible");
54
+ } finally {
55
+ setLoading(false);
56
+ }
57
+ }, []);
58
+
59
+ useEffect(() => {
60
+ void refresh();
61
+ }, [refresh]);
62
+
63
+ const methods = useMemo(
64
+ () =>
65
+ Array.from(
66
+ new Set(registry?.endpoints.map((endpoint) => endpoint.method) || []),
67
+ ).sort(),
68
+ [registry],
69
+ );
70
+
71
+ const endpoints = useMemo(() => {
72
+ const needle = query.trim().toLowerCase();
73
+ return (registry?.endpoints || []).filter((endpoint) => {
74
+ if (method !== "ALL" && endpoint.method !== method) return false;
75
+ if (!needle) return true;
76
+ return (
77
+ endpoint.path.toLowerCase().includes(needle) ||
78
+ endpoint.method.toLowerCase().includes(needle) ||
79
+ endpoint.summary?.toLowerCase().includes(needle) ||
80
+ endpoint.tags.some((tag) => tag.toLowerCase().includes(needle))
81
+ );
82
+ });
83
+ }, [method, query, registry]);
84
+
85
+ const documentedCount =
86
+ registry?.endpoints.filter((endpoint) => endpoint.documented).length || 0;
87
+
88
+ return (
89
+ <div className="space-y-4">
90
+ <div className="grid gap-3 sm:grid-cols-3">
91
+ <Stat label="Routes runtime" value={registry?.endpoints.length || 0} />
92
+ <Stat label="Documentées OpenAPI" value={documentedCount} />
93
+ <Stat
94
+ label="Routes Hono seules"
95
+ value={(registry?.endpoints.length || 0) - documentedCount}
96
+ />
97
+ </div>
98
+
99
+ <Card>
100
+ <CardHeader className="gap-3 sm:flex-row sm:items-center sm:justify-between">
101
+ <div>
102
+ <CardTitle className="flex items-center gap-2 text-base">
103
+ <Braces className="h-4 w-4" />
104
+ Endpoints montés
105
+ </CardTitle>
106
+ <p className="mt-1 text-xs text-slate-500">
107
+ Registre Hono enrichi par{" "}
108
+ <a
109
+ href={registry?.openapiUrl || "/api/v1/openapi.json"}
110
+ target="_blank"
111
+ rel="noreferrer"
112
+ className="font-mono text-sky-700 hover:underline"
113
+ >
114
+ /api/v1/openapi.json
115
+ </a>
116
+ </p>
117
+ </div>
118
+ <Button size="sm" variant="outline" onClick={() => void refresh()} disabled={loading}>
119
+ <RefreshCw className={`mr-1 h-3.5 w-3.5 ${loading ? "animate-spin" : ""}`} />
120
+ Actualiser
121
+ </Button>
122
+ </CardHeader>
123
+ <CardContent className="space-y-4">
124
+ <div className="flex flex-col gap-2 sm:flex-row">
125
+ <div className="relative flex-1">
126
+ <Search className="absolute left-3 top-2.5 h-4 w-4 text-slate-400" />
127
+ <Input
128
+ value={query}
129
+ onChange={(event) => setQuery(event.target.value)}
130
+ placeholder="Chemin, résumé ou tag…"
131
+ className="pl-9"
132
+ />
133
+ </div>
134
+ <select
135
+ value={method}
136
+ onChange={(event) => setMethod(event.target.value)}
137
+ className="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm"
138
+ >
139
+ <option value="ALL">Toutes les méthodes</option>
140
+ {methods.map((item) => (
141
+ <option key={item} value={item}>
142
+ {item}
143
+ </option>
144
+ ))}
145
+ </select>
146
+ </div>
147
+
148
+ {error ? (
149
+ <div className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700">
150
+ {error}
151
+ </div>
152
+ ) : null}
153
+
154
+ <div className="overflow-x-auto rounded-lg border">
155
+ <table className="min-w-full text-left text-sm">
156
+ <thead className="border-b bg-slate-50 text-xs uppercase text-slate-500">
157
+ <tr>
158
+ <th className="px-3 py-2">Méthode</th>
159
+ <th className="px-3 py-2">Chemin</th>
160
+ <th className="px-3 py-2">Description</th>
161
+ <th className="px-3 py-2">Source</th>
162
+ </tr>
163
+ </thead>
164
+ <tbody>
165
+ {endpoints.map((endpoint) => (
166
+ <tr
167
+ key={`${endpoint.method}:${endpoint.path}`}
168
+ className="border-b last:border-0 hover:bg-slate-50"
169
+ >
170
+ <td className="px-3 py-2">
171
+ <span
172
+ className={`inline-flex rounded px-2 py-0.5 font-mono text-[11px] font-bold ${
173
+ METHOD_STYLE[endpoint.method] || METHOD_STYLE.OPTIONS
174
+ }`}
175
+ >
176
+ {endpoint.method}
177
+ </span>
178
+ </td>
179
+ <td className="whitespace-nowrap px-3 py-2 font-mono text-xs text-slate-800">
180
+ {endpoint.path}
181
+ </td>
182
+ <td className="min-w-[16rem] px-3 py-2 text-xs text-slate-600">
183
+ {endpoint.summary || endpoint.description || "—"}
184
+ {endpoint.tags.length ? (
185
+ <div className="mt-1 flex flex-wrap gap-1">
186
+ {endpoint.tags.map((tag) => (
187
+ <Badge key={tag} variant="secondary" className="text-[10px]">
188
+ {tag}
189
+ </Badge>
190
+ ))}
191
+ </div>
192
+ ) : null}
193
+ </td>
194
+ <td className="px-3 py-2 text-xs">
195
+ {endpoint.documented ? (
196
+ <span className="text-emerald-700">OpenAPI</span>
197
+ ) : (
198
+ <span className="text-slate-500">Runtime Hono</span>
199
+ )}
200
+ </td>
201
+ </tr>
202
+ ))}
203
+ {!endpoints.length && !loading ? (
204
+ <tr>
205
+ <td colSpan={4} className="px-4 py-8 text-center text-slate-500">
206
+ Aucun endpoint pour ce filtre.
207
+ </td>
208
+ </tr>
209
+ ) : null}
210
+ </tbody>
211
+ </table>
212
+ </div>
213
+ </CardContent>
214
+ </Card>
215
+ </div>
216
+ );
217
+ }
218
+
219
+ function Stat({ label, value }: { label: string; value: number }) {
220
+ return (
221
+ <Card>
222
+ <CardContent className="p-4">
223
+ <div className="text-2xl font-semibold tabular-nums text-slate-900">{value}</div>
224
+ <div className="text-xs text-slate-500">{label}</div>
225
+ </CardContent>
226
+ </Card>
227
+ );
228
+ }
package/ui/index.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Observability Admin UI (usage analytics N6 + request-logs / api-endpoints O5).
3
+ * Consommer via `@creezio/observability/ui`.
4
+ */
5
+ export { AnalyticsClient } from "./analytics-client";
6
+ export {
7
+ AnalyticsProductivityPanel,
8
+ type ProductivityPayload,
9
+ } from "./analytics-productivity-panel";
10
+ export {
11
+ UsageAnalyticsProvider,
12
+ type UsageAnalyticsProviderSession,
13
+ } from "./usage-analytics-provider";
14
+ export { SessionUsageAnalyticsProvider } from "./session-usage-analytics-provider";
15
+
16
+ export {
17
+ flushUsageAnalytics,
18
+ setUsageAnalyticsSession,
19
+ trackUsagePageView,
20
+ trackUsageEvent,
21
+ ensureUsageAnalyticsDom,
22
+ } from "./usage-analytics-client";
23
+
24
+ /** Tokens UI marque (aidAttr / fleet) — safe client via dist. */
25
+ export {
26
+ configureUsageAnalyticsUiBrand,
27
+ getUsageAnalyticsUiBrand,
28
+ type UsageAnalyticsUiBrand,
29
+ type FleetActionPayload,
30
+ } from "../dist/usage/ui-brand.js";
31
+
32
+ /* ── O5 : request-logs / api-endpoints ── */
33
+ export { RequestLogsClient } from "./request-logs-client";
34
+ export { ApiEndpointsClient } from "./api-endpoints-client";
35
+ export type {
36
+ RequestLogEntry,
37
+ RequestLogSource,
38
+ RequestLogDetail,
39
+ } from "../dist/request-logs/request-logs.js";
@@ -0,0 +1,37 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "./cn";
4
+
5
+ const badgeVariants = cva(
6
+ "inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium transition-colors",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "border-transparent bg-slate-900 text-white",
11
+ secondary: "border-transparent bg-slate-100 text-slate-700",
12
+ outline: "border-slate-200 text-slate-700",
13
+ success: "border-transparent bg-emerald-100 text-emerald-800",
14
+ warning: "border-transparent bg-amber-100 text-amber-800",
15
+ danger: "border-transparent bg-red-100 text-red-800",
16
+ info: "border-transparent bg-sky-100 text-sky-800",
17
+ muted: "border-transparent bg-slate-50 text-slate-500",
18
+ },
19
+ },
20
+ defaultVariants: { variant: "default" },
21
+ },
22
+ );
23
+
24
+ export interface BadgeProps
25
+ extends React.HTMLAttributes<HTMLSpanElement>,
26
+ VariantProps<typeof badgeVariants> {}
27
+
28
+ /**
29
+ * <span> et non <div> : un badge est utilisé DANS des <p>, <a> et <span>
30
+ * (fiche produit, dashboard, préférences…). Un <div> y est du HTML invalide :
31
+ * le parseur navigateur restructure le DOM (ferme le <p> avant le div) et
32
+ * l'hydratation React échoue (« Hydration failed » #418/#422, cascade
33
+ * possible en #310) — écran « Application error » dans l'app desktop.
34
+ */
35
+ export function Badge({ className, variant, ...props }: BadgeProps) {
36
+ return <span className={cn(badgeVariants({ variant }), className)} {...props} />;
37
+ }
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "./cn";
5
+
6
+ const buttonVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 disabled:pointer-events-none disabled:opacity-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-slate-900 text-white hover:bg-slate-800",
12
+ secondary: "bg-slate-100 text-slate-900 hover:bg-slate-200",
13
+ outline: "border border-slate-200 bg-white hover:bg-slate-50",
14
+ ghost: "hover:bg-slate-100",
15
+ destructive: "bg-red-600 text-white hover:bg-red-700",
16
+ },
17
+ size: {
18
+ default: "h-9 px-4 py-2",
19
+ sm: "h-8 rounded-md px-3 text-xs",
20
+ lg: "h-10 rounded-md px-8",
21
+ icon: "h-9 w-9",
22
+ },
23
+ },
24
+ defaultVariants: { variant: "default", size: "default" },
25
+ },
26
+ );
27
+
28
+ export interface ButtonProps
29
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
30
+ VariantProps<typeof buttonVariants> {
31
+ asChild?: boolean;
32
+ }
33
+
34
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
35
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
36
+ const Comp = asChild ? Slot : "button";
37
+ return (
38
+ <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
39
+ );
40
+ },
41
+ );
42
+ Button.displayName = "Button";
43
+
44
+ export { Button, buttonVariants };
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ import { cn } from "./cn";
3
+
4
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div
7
+ ref={ref}
8
+ className={cn("rounded-xl border border-slate-200 bg-white shadow-sm", className)}
9
+ {...props}
10
+ />
11
+ ),
12
+ );
13
+ Card.displayName = "Card";
14
+
15
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
16
+ ({ className, ...props }, ref) => (
17
+ <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
18
+ ),
19
+ );
20
+ CardHeader.displayName = "CardHeader";
21
+
22
+ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
23
+ ({ className, ...props }, ref) => (
24
+ <h3 ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />
25
+ ),
26
+ );
27
+ CardTitle.displayName = "CardTitle";
28
+
29
+ const CardDescription = React.forwardRef<
30
+ HTMLParagraphElement,
31
+ React.HTMLAttributes<HTMLParagraphElement>
32
+ >(({ className, ...props }, ref) => (
33
+ <p ref={ref} className={cn("text-sm text-slate-500", className)} {...props} />
34
+ ));
35
+ CardDescription.displayName = "CardDescription";
36
+
37
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
38
+ ({ className, ...props }, ref) => (
39
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
40
+ ),
41
+ );
42
+ CardContent.displayName = "CardContent";
43
+
44
+ export { Card, CardHeader, CardTitle, CardDescription, CardContent };
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { cn } from "./cn";
3
+
4
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
5
+ ({ className, type, ...props }, ref) => (
6
+ <input
7
+ type={type}
8
+ className={cn(
9
+ "flex h-9 w-full rounded-md border border-slate-200 bg-white px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 disabled:cursor-not-allowed disabled:opacity-50",
10
+ className,
11
+ )}
12
+ ref={ref}
13
+ {...props}
14
+ />
15
+ ),
16
+ );
17
+ Input.displayName = "Input";
18
+
19
+ export { Input };
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
5
+ import { cn } from "./cn";
6
+
7
+ const Tabs = TabsPrimitive.Root;
8
+
9
+ const TabsList = React.forwardRef<
10
+ React.ElementRef<typeof TabsPrimitive.List>,
11
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
12
+ >(({ className, ...props }, ref) => (
13
+ <TabsPrimitive.List
14
+ ref={ref}
15
+ className={cn(
16
+ "inline-flex h-10 items-center justify-start gap-1 rounded-lg bg-slate-100 p-1 text-slate-600",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ ));
22
+ TabsList.displayName = TabsPrimitive.List.displayName;
23
+
24
+ const TabsTrigger = React.forwardRef<
25
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
26
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
27
+ >(({ className, ...props }, ref) => (
28
+ <TabsPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm",
32
+ className,
33
+ )}
34
+ {...props}
35
+ />
36
+ ));
37
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
38
+
39
+ const TabsContent = React.forwardRef<
40
+ React.ElementRef<typeof TabsPrimitive.Content>,
41
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
42
+ >(({ className, ...props }, ref) => (
43
+ <TabsPrimitive.Content
44
+ ref={ref}
45
+ className={cn("mt-4 focus-visible:outline-none", className)}
46
+ {...props}
47
+ />
48
+ ));
49
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
50
+
51
+ export { Tabs, TabsList, TabsTrigger, TabsContent };