@creezio/shell-ui 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 (232) hide show
  1. package/README.md +336 -0
  2. package/dist/adapters/nav-shell.d.ts +54 -0
  3. package/dist/adapters/nav-shell.d.ts.map +1 -0
  4. package/dist/adapters/nav-shell.js +112 -0
  5. package/dist/adapters/nav-shell.js.map +1 -0
  6. package/dist/brand.d.ts +64 -0
  7. package/dist/brand.d.ts.map +1 -0
  8. package/dist/brand.js +59 -0
  9. package/dist/brand.js.map +1 -0
  10. package/dist/core-nav.d.ts +17 -0
  11. package/dist/core-nav.d.ts.map +1 -0
  12. package/dist/core-nav.js +40 -0
  13. package/dist/core-nav.js.map +1 -0
  14. package/dist/index.d.ts +29 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +24 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lib/api-scopes.d.ts +24 -0
  19. package/dist/lib/api-scopes.d.ts.map +1 -0
  20. package/dist/lib/api-scopes.js +78 -0
  21. package/dist/lib/api-scopes.js.map +1 -0
  22. package/dist/lib/catalog-suspense-key.d.ts +3 -0
  23. package/dist/lib/catalog-suspense-key.d.ts.map +1 -0
  24. package/dist/lib/catalog-suspense-key.js +12 -0
  25. package/dist/lib/catalog-suspense-key.js.map +1 -0
  26. package/dist/lib/data-changed.d.ts +51 -0
  27. package/dist/lib/data-changed.d.ts.map +1 -0
  28. package/dist/lib/data-changed.js +227 -0
  29. package/dist/lib/data-changed.js.map +1 -0
  30. package/dist/lib/desktop-home-path.d.ts +14 -0
  31. package/dist/lib/desktop-home-path.d.ts.map +1 -0
  32. package/dist/lib/desktop-home-path.js +29 -0
  33. package/dist/lib/desktop-home-path.js.map +1 -0
  34. package/dist/lib/geo-distance.d.ts +6 -0
  35. package/dist/lib/geo-distance.d.ts.map +1 -0
  36. package/dist/lib/geo-distance.js +27 -0
  37. package/dist/lib/geo-distance.js.map +1 -0
  38. package/dist/lib/img.d.ts +2 -0
  39. package/dist/lib/img.d.ts.map +1 -0
  40. package/dist/lib/img.js +40 -0
  41. package/dist/lib/img.js.map +1 -0
  42. package/dist/lib/keepalive-eviction.d.ts +14 -0
  43. package/dist/lib/keepalive-eviction.d.ts.map +1 -0
  44. package/dist/lib/keepalive-eviction.js +44 -0
  45. package/dist/lib/keepalive-eviction.js.map +1 -0
  46. package/dist/lib/ops-track.d.ts +29 -0
  47. package/dist/lib/ops-track.d.ts.map +1 -0
  48. package/dist/lib/ops-track.js +37 -0
  49. package/dist/lib/ops-track.js.map +1 -0
  50. package/dist/lib/optimize-cover-url.d.ts +10 -0
  51. package/dist/lib/optimize-cover-url.d.ts.map +1 -0
  52. package/dist/lib/optimize-cover-url.js +80 -0
  53. package/dist/lib/optimize-cover-url.js.map +1 -0
  54. package/dist/lib/page-trails.d.ts +9 -0
  55. package/dist/lib/page-trails.d.ts.map +1 -0
  56. package/dist/lib/page-trails.js +20 -0
  57. package/dist/lib/page-trails.js.map +1 -0
  58. package/dist/lib/public-origin.d.ts +32 -0
  59. package/dist/lib/public-origin.d.ts.map +1 -0
  60. package/dist/lib/public-origin.js +125 -0
  61. package/dist/lib/public-origin.js.map +1 -0
  62. package/dist/lib/server-incident.d.ts +12 -0
  63. package/dist/lib/server-incident.d.ts.map +1 -0
  64. package/dist/lib/server-incident.js +72 -0
  65. package/dist/lib/server-incident.js.map +1 -0
  66. package/dist/lib/tab-document-url.d.ts +13 -0
  67. package/dist/lib/tab-document-url.d.ts.map +1 -0
  68. package/dist/lib/tab-document-url.js +60 -0
  69. package/dist/lib/tab-document-url.js.map +1 -0
  70. package/dist/lib/utils.d.ts +20 -0
  71. package/dist/lib/utils.d.ts.map +1 -0
  72. package/dist/lib/utils.js +87 -0
  73. package/dist/lib/utils.js.map +1 -0
  74. package/dist/nav-catalog.d.ts +97 -0
  75. package/dist/nav-catalog.d.ts.map +1 -0
  76. package/dist/nav-catalog.js +282 -0
  77. package/dist/nav-catalog.js.map +1 -0
  78. package/dist/registry.d.ts +11 -0
  79. package/dist/registry.d.ts.map +1 -0
  80. package/dist/registry.js +60 -0
  81. package/dist/registry.js.map +1 -0
  82. package/dist/types.d.ts +22 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +2 -0
  85. package/dist/types.js.map +1 -0
  86. package/dist-cjs/adapters/nav-shell.js +115 -0
  87. package/dist-cjs/adapters/nav-shell.js.map +1 -0
  88. package/dist-cjs/brand.js +66 -0
  89. package/dist-cjs/brand.js.map +1 -0
  90. package/dist-cjs/core-nav.js +43 -0
  91. package/dist-cjs/core-nav.js.map +1 -0
  92. package/dist-cjs/index.js +95 -0
  93. package/dist-cjs/index.js.map +1 -0
  94. package/dist-cjs/lib/api-scopes.js +86 -0
  95. package/dist-cjs/lib/api-scopes.js.map +1 -0
  96. package/dist-cjs/lib/catalog-suspense-key.js +15 -0
  97. package/dist-cjs/lib/catalog-suspense-key.js.map +1 -0
  98. package/dist-cjs/lib/data-changed.js +236 -0
  99. package/dist-cjs/lib/data-changed.js.map +1 -0
  100. package/dist-cjs/lib/desktop-home-path.js +34 -0
  101. package/dist-cjs/lib/desktop-home-path.js.map +1 -0
  102. package/dist-cjs/lib/geo-distance.js +30 -0
  103. package/dist-cjs/lib/geo-distance.js.map +1 -0
  104. package/dist-cjs/lib/img.js +43 -0
  105. package/dist-cjs/lib/img.js.map +1 -0
  106. package/dist-cjs/lib/keepalive-eviction.js +49 -0
  107. package/dist-cjs/lib/keepalive-eviction.js.map +1 -0
  108. package/dist-cjs/lib/ops-track.js +41 -0
  109. package/dist-cjs/lib/ops-track.js.map +1 -0
  110. package/dist-cjs/lib/optimize-cover-url.js +83 -0
  111. package/dist-cjs/lib/optimize-cover-url.js.map +1 -0
  112. package/dist-cjs/lib/page-trails.js +25 -0
  113. package/dist-cjs/lib/page-trails.js.map +1 -0
  114. package/dist-cjs/lib/public-origin.js +130 -0
  115. package/dist-cjs/lib/public-origin.js.map +1 -0
  116. package/dist-cjs/lib/server-incident.js +75 -0
  117. package/dist-cjs/lib/server-incident.js.map +1 -0
  118. package/dist-cjs/lib/tab-document-url.js +65 -0
  119. package/dist-cjs/lib/tab-document-url.js.map +1 -0
  120. package/dist-cjs/lib/utils.js +97 -0
  121. package/dist-cjs/lib/utils.js.map +1 -0
  122. package/dist-cjs/nav-catalog.js +293 -0
  123. package/dist-cjs/nav-catalog.js.map +1 -0
  124. package/dist-cjs/package.json +3 -0
  125. package/dist-cjs/registry.js +64 -0
  126. package/dist-cjs/registry.js.map +1 -0
  127. package/dist-cjs/types.js +3 -0
  128. package/dist-cjs/types.js.map +1 -0
  129. package/package.json +163 -0
  130. package/ui/app-error-boundary.tsx +59 -0
  131. package/ui/data-table.tsx +121 -0
  132. package/ui/desktop/auth-window-chrome.tsx +51 -0
  133. package/ui/desktop/desktop-bridge.tsx +87 -0
  134. package/ui/desktop/external-site-slot.tsx +313 -0
  135. package/ui/desktop/external-site-surface.ts +58 -0
  136. package/ui/desktop/site-link.tsx +105 -0
  137. package/ui/desktop/window-chrome-controls.tsx +5 -0
  138. package/ui/desktop-types.ts +30 -0
  139. package/ui/faceted-filters.tsx +119 -0
  140. package/ui/global-search-host.ts +20 -0
  141. package/ui/global-search.tsx +66 -0
  142. package/ui/index.ts +178 -0
  143. package/ui/kit.ts +30 -0
  144. package/ui/layout/app-shell.tsx +58 -0
  145. package/ui/layout/desktop-update-banner.tsx +100 -0
  146. package/ui/layout/entity-header.tsx +44 -0
  147. package/ui/layout/native-os-nav.ts +104 -0
  148. package/ui/layout/nav-catalog-loader.tsx +98 -0
  149. package/ui/layout/nav-icons.ts +67 -0
  150. package/ui/layout/page-chrome.tsx +90 -0
  151. package/ui/layout/page-toolbar-context.tsx +82 -0
  152. package/ui/layout/sandbox-banner.tsx +38 -0
  153. package/ui/layout/section-view-shell.tsx +73 -0
  154. package/ui/layout/sidebar-host.ts +144 -0
  155. package/ui/layout/sidebar.tsx +1150 -0
  156. package/ui/lib/ai-screencast-hub.ts +127 -0
  157. package/ui/lib/ai-workspace-client.ts +46 -0
  158. package/ui/lib/aid.ts +22 -0
  159. package/ui/lib/desktop-host.ts +114 -0
  160. package/ui/lib/fleet-tracker-client.ts +237 -0
  161. package/ui/lib/hermes-ui.ts +114 -0
  162. package/ui/lib/n8n-ui.ts +115 -0
  163. package/ui/lib/use-creezio-resource.tsx +121 -0
  164. package/ui/lib/use-shell-ui-brand.ts +22 -0
  165. package/ui/list-toolbar.tsx +309 -0
  166. package/ui/os-pages/desktop-settings-page.tsx +158 -0
  167. package/ui/os-pages/index.ts +8 -0
  168. package/ui/page-loading/entity-page-loading.tsx +59 -0
  169. package/ui/page-loading/list-page-loading.tsx +61 -0
  170. package/ui/pagination.tsx +56 -0
  171. package/ui/primitives/avatar.tsx +33 -0
  172. package/ui/primitives/badge.tsx +36 -0
  173. package/ui/primitives/breadcrumb.tsx +93 -0
  174. package/ui/primitives/button.tsx +44 -0
  175. package/ui/primitives/card.tsx +44 -0
  176. package/ui/primitives/chart.tsx +317 -0
  177. package/ui/primitives/command.tsx +111 -0
  178. package/ui/primitives/dialog.tsx +100 -0
  179. package/ui/primitives/dropdown-menu.tsx +187 -0
  180. package/ui/primitives/input.tsx +19 -0
  181. package/ui/primitives/label.tsx +17 -0
  182. package/ui/primitives/resizable.tsx +45 -0
  183. package/ui/primitives/scroll-area.tsx +41 -0
  184. package/ui/primitives/select.tsx +73 -0
  185. package/ui/primitives/separator.tsx +26 -0
  186. package/ui/primitives/sheet.tsx +90 -0
  187. package/ui/primitives/skeleton.tsx +7 -0
  188. package/ui/primitives/sonner.tsx +22 -0
  189. package/ui/primitives/tabs.tsx +51 -0
  190. package/ui/primitives/textarea.tsx +22 -0
  191. package/ui/primitives/tooltip.tsx +31 -0
  192. package/ui/pwa/client-error-reporter.tsx +62 -0
  193. package/ui/pwa/register-sw.tsx +50 -0
  194. package/ui/range-filters.tsx +174 -0
  195. package/ui/search/global-search-config.ts +32 -0
  196. package/ui/search/global-search-provider.tsx +528 -0
  197. package/ui/search/search-history.ts +74 -0
  198. package/ui/search-input.tsx +146 -0
  199. package/ui/settings/account-settings.tsx +140 -0
  200. package/ui/settings/agent-profile-settings.tsx +184 -0
  201. package/ui/settings/api-keys-settings.tsx +349 -0
  202. package/ui/settings/desktop-background-settings.tsx +128 -0
  203. package/ui/settings/desktop-connection-settings.tsx +251 -0
  204. package/ui/settings/desktop-embed-env-panel.tsx +257 -0
  205. package/ui/settings/desktop-fleet-telemetry-settings.tsx +269 -0
  206. package/ui/settings/desktop-hermes-settings.tsx +341 -0
  207. package/ui/settings/desktop-llm-keys.tsx +192 -0
  208. package/ui/settings/desktop-n8n-settings.tsx +306 -0
  209. package/ui/settings/desktop-tunnel.tsx +264 -0
  210. package/ui/settings/desktop-update-settings.tsx +175 -0
  211. package/ui/settings/factory-reset-settings.tsx +89 -0
  212. package/ui/settings/host-only-settings.tsx +62 -0
  213. package/ui/settings/locked-config-field.tsx +30 -0
  214. package/ui/settings/ops-diagnostic-settings.tsx +242 -0
  215. package/ui/settings/search-reindex-settings.tsx +158 -0
  216. package/ui/settings/server-mode-cards.tsx +801 -0
  217. package/ui/theme/tailwind-preset.cjs +82 -0
  218. package/ui/theme/theme.css +328 -0
  219. package/ui/workspace/ai-activity-panel-host.tsx +19 -0
  220. package/ui/workspace/ai-workspace-agent-host.tsx +54 -0
  221. package/ui/workspace/ai-workspace-banner.tsx +168 -0
  222. package/ui/workspace/impersonation-banner.tsx +39 -0
  223. package/ui/workspace/keep-alive.tsx +220 -0
  224. package/ui/workspace/tab-workspace-context.tsx +1172 -0
  225. package/ui/workspace/tab-workspace-host.ts +89 -0
  226. package/ui/workspace/types.ts +500 -0
  227. package/ui/workspace/use-location-search.tsx +50 -0
  228. package/ui/workspace/window-chrome-controls.tsx +116 -0
  229. package/ui/workspace/workspace-config.ts +47 -0
  230. package/ui/workspace/workspace-root.tsx +147 -0
  231. package/ui/workspace/workspace-shell.tsx +292 -0
  232. package/ui/workspace/workspace-tab-bar.tsx +572 -0
@@ -0,0 +1,158 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Page OS réglages desktop — composition kit (pas de métier marque).
5
+ * Les marques ne font que `export { DesktopSettingsPage as default }`.
6
+ *
7
+ * Parité Configuration TF (onglets Connexion / Hosts / Compte / Support / Système).
8
+ */
9
+ import { AccountSettings } from "../settings/account-settings";
10
+ import { AgentProfileSettings } from "../settings/agent-profile-settings";
11
+ import { ApiKeysSettings } from "../settings/api-keys-settings";
12
+ import { DesktopConnectionSettings } from "../settings/desktop-connection-settings";
13
+ import { DesktopHermesSettings } from "../settings/desktop-hermes-settings";
14
+ import { DesktopN8nSettings } from "../settings/desktop-n8n-settings";
15
+ import { DesktopTunnel } from "../settings/desktop-tunnel";
16
+ import { DesktopLlmKeys } from "../settings/desktop-llm-keys";
17
+ import { DesktopUpdateSettings } from "../settings/desktop-update-settings";
18
+ import { DesktopBackgroundSettings } from "../settings/desktop-background-settings";
19
+ import { DesktopFleetTelemetrySettings } from "../settings/desktop-fleet-telemetry-settings";
20
+ import { FactoryResetSettings } from "../settings/factory-reset-settings";
21
+ import { OpsDiagnosticSettings } from "../settings/ops-diagnostic-settings";
22
+ import { SearchReindexSettings } from "../settings/search-reindex-settings";
23
+ import {
24
+ HostManagedNotice,
25
+ HostOnlySettings,
26
+ } from "../settings/host-only-settings";
27
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/tabs";
28
+ import type { ReactNode } from "react";
29
+
30
+ export type DesktopSettingsExtraTab = {
31
+ value: string;
32
+ label: string;
33
+ content: ReactNode;
34
+ };
35
+
36
+ export type DesktopSettingsPageProps = {
37
+ /**
38
+ * Onglets additionnels injectés par la marque / le wrapper os-ui —
39
+ * ex. Email (`MailSettings` de @creezio/mails/ui, dépendance inverse
40
+ * interdite depuis shell-ui). Insérés avant Support.
41
+ */
42
+ extraTabs?: DesktopSettingsExtraTab[];
43
+ /** Onglet ouvert par défaut (ex. `?tab=email`). Défaut : connexion. */
44
+ defaultTab?: string;
45
+ };
46
+
47
+ export function DesktopSettingsPage({
48
+ extraTabs = [],
49
+ defaultTab,
50
+ }: DesktopSettingsPageProps = {}) {
51
+ const known = new Set([
52
+ "connexion",
53
+ "hermes",
54
+ "n8n",
55
+ "tunnel",
56
+ "compte",
57
+ "support",
58
+ "systeme",
59
+ ...extraTabs.map((t) => t.value),
60
+ ]);
61
+ const initial = defaultTab && known.has(defaultTab) ? defaultTab : "connexion";
62
+ return (
63
+ <section className="mx-auto max-w-4xl px-0 py-4">
64
+ <h1 className="mb-4 text-2xl font-semibold tracking-tight text-slate-900">
65
+ Configuration
66
+ </h1>
67
+ <p className="mb-4 text-sm text-slate-600">
68
+ Chaque service a son onglet — paramètres verrouillés visibles, actions et
69
+ statut
70
+ </p>
71
+ <Tabs defaultValue={initial} className="w-full">
72
+ <TabsList className="mb-4 flex h-auto w-full flex-wrap justify-start gap-1">
73
+ <TabsTrigger value="connexion">Connexion</TabsTrigger>
74
+ <TabsTrigger value="hermes">Hermes</TabsTrigger>
75
+ <TabsTrigger value="n8n">n8n</TabsTrigger>
76
+ <TabsTrigger value="tunnel">Accès distant</TabsTrigger>
77
+ <TabsTrigger value="compte">Compte &amp; clés</TabsTrigger>
78
+ {extraTabs.map((t) => (
79
+ <TabsTrigger key={t.value} value={t.value}>
80
+ {t.label}
81
+ </TabsTrigger>
82
+ ))}
83
+ <TabsTrigger value="support">Support</TabsTrigger>
84
+ <TabsTrigger value="systeme">Système</TabsTrigger>
85
+ </TabsList>
86
+
87
+ <TabsContent value="connexion" className="mt-0 space-y-4">
88
+ <DesktopConnectionSettings />
89
+ </TabsContent>
90
+
91
+ <TabsContent value="hermes" className="mt-0 space-y-4">
92
+ <HostOnlySettings
93
+ fallback={<HostManagedNotice label="l'agent Hermes" />}
94
+ >
95
+ <DesktopHermesSettings />
96
+ </HostOnlySettings>
97
+ </TabsContent>
98
+
99
+ <TabsContent value="n8n" className="mt-0 space-y-4">
100
+ <HostOnlySettings fallback={<HostManagedNotice label="n8n" />}>
101
+ <DesktopN8nSettings />
102
+ </HostOnlySettings>
103
+ </TabsContent>
104
+
105
+ <TabsContent value="tunnel" className="mt-0 space-y-4">
106
+ <HostOnlySettings
107
+ fallback={<HostManagedNotice label="l'accès distant (tunnel)" />}
108
+ >
109
+ <DesktopTunnel />
110
+ </HostOnlySettings>
111
+ </TabsContent>
112
+
113
+ <TabsContent value="compte" className="mt-0 space-y-4">
114
+ <HostOnlySettings
115
+ fallback={
116
+ <HostManagedNotice label="le compte propriétaire et les clés IA" />
117
+ }
118
+ >
119
+ <AccountSettings />
120
+ <DesktopLlmKeys />
121
+ </HostOnlySettings>
122
+ <AgentProfileSettings />
123
+ <ApiKeysSettings />
124
+ </TabsContent>
125
+
126
+ {extraTabs.map((t) => (
127
+ <TabsContent key={t.value} value={t.value} className="mt-0 space-y-4">
128
+ {t.content}
129
+ </TabsContent>
130
+ ))}
131
+
132
+ <TabsContent value="support" className="mt-0 space-y-4">
133
+ <HostOnlySettings
134
+ fallback={<HostManagedNotice label="la télémétrie support" />}
135
+ >
136
+ <DesktopFleetTelemetrySettings />
137
+ </HostOnlySettings>
138
+ </TabsContent>
139
+
140
+ <TabsContent value="systeme" className="mt-0 space-y-4">
141
+ <DesktopUpdateSettings />
142
+ <DesktopBackgroundSettings />
143
+ <HostOnlySettings
144
+ fallback={
145
+ <HostManagedNotice label="l'index de recherche et la remise à zéro" />
146
+ }
147
+ >
148
+ <SearchReindexSettings />
149
+ <OpsDiagnosticSettings />
150
+ <FactoryResetSettings />
151
+ </HostOnlySettings>
152
+ </TabsContent>
153
+ </Tabs>
154
+ </section>
155
+ );
156
+ }
157
+
158
+ export default DesktopSettingsPage;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Surfaces UI OS — imports ciblés (évite le barrel /ui qui tire workspace+assistant Node).
3
+ */
4
+ export { DesktopSettingsPage } from "./desktop-settings-page";
5
+ export type {
6
+ DesktopSettingsPageProps,
7
+ DesktopSettingsExtraTab,
8
+ } from "./desktop-settings-page";
@@ -0,0 +1,59 @@
1
+ import { AppShell } from "../layout/app-shell";
2
+ import { Skeleton } from "../primitives/skeleton";
3
+ import type { TrailCrumb } from "../workspace/types";
4
+
5
+ /** Squelette fiche entité (galerie + panneau) — pas un tableau liste. */
6
+ export function EntityPageLoading({
7
+ title = "Produit",
8
+ subtitle = "Chargement de la fiche…",
9
+ trail,
10
+ }: {
11
+ title?: string;
12
+ subtitle?: string;
13
+ trail?: TrailCrumb[];
14
+ }) {
15
+ return (
16
+ <AppShell title={title} subtitle={subtitle} kind="entity" trail={trail}>
17
+ <div
18
+ className="space-y-6"
19
+ aria-busy="true"
20
+ aria-label="Chargement de la fiche"
21
+ >
22
+ <div className="grid gap-6 lg:grid-cols-12 lg:items-start">
23
+ <div className="lg:col-span-7">
24
+ <Skeleton className="aspect-square w-full rounded-xl" />
25
+ </div>
26
+ <aside className="lg:col-span-5">
27
+ <div className="rounded-xl border border-slate-200 bg-white p-5 sm:p-6">
28
+ <Skeleton className="h-7 w-3/4" />
29
+ <div className="mt-3 flex flex-wrap gap-2">
30
+ <Skeleton className="h-5 w-20 rounded-full" />
31
+ <Skeleton className="h-5 w-16 rounded-full" />
32
+ </div>
33
+ <div className="mt-5 space-y-2">
34
+ <Skeleton className="h-3 w-24" />
35
+ <Skeleton className="h-9 w-36" />
36
+ <Skeleton className="h-4 w-28" />
37
+ </div>
38
+ <div className="mt-5 grid gap-3 border-t border-slate-100 pt-5 sm:grid-cols-2">
39
+ <Skeleton className="h-10 w-full" />
40
+ <Skeleton className="h-10 w-full" />
41
+ <Skeleton className="h-10 w-full" />
42
+ <Skeleton className="h-10 w-full" />
43
+ </div>
44
+ <Skeleton className="mt-6 h-10 w-full rounded-md" />
45
+ </div>
46
+ </aside>
47
+ </div>
48
+ <div className="rounded-xl border border-slate-200 bg-white p-4">
49
+ <Skeleton className="mb-3 h-4 w-40" />
50
+ <div className="space-y-2">
51
+ <Skeleton className="h-8 w-full" />
52
+ <Skeleton className="h-8 w-full" />
53
+ <Skeleton className="h-8 w-2/3" />
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </AppShell>
58
+ );
59
+ }
@@ -0,0 +1,61 @@
1
+ import { AppShell } from "../layout/app-shell";
2
+ import { Skeleton } from "../primitives/skeleton";
3
+ import type { PageKind, TrailCrumb } from "../workspace/types";
4
+
5
+ /** Shell immédiat pour pages liste lourdes — même pattern JustRent. */
6
+ export function ListPageLoading({
7
+ title,
8
+ subtitle = "Chargement des données…",
9
+ kpiCount = 0,
10
+ filterRows = 1,
11
+ tableRows = 8,
12
+ kind = "section",
13
+ trail,
14
+ }: {
15
+ title: string;
16
+ subtitle?: string;
17
+ kpiCount?: number;
18
+ filterRows?: number;
19
+ tableRows?: number;
20
+ kind?: PageKind;
21
+ trail?: TrailCrumb[];
22
+ }) {
23
+ return (
24
+ <AppShell title={title} subtitle={subtitle} kind={kind} trail={trail}>
25
+ <div className="space-y-4" aria-busy="true" aria-label="Chargement de la page">
26
+ {kpiCount > 0 ? (
27
+ <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
28
+ {Array.from({ length: kpiCount }).map((_, i) => (
29
+ <Skeleton key={i} className="h-28 rounded-xl" />
30
+ ))}
31
+ </div>
32
+ ) : null}
33
+ <div className="space-y-2 rounded-xl border border-slate-200 bg-white p-3 shadow-sm">
34
+ <Skeleton className="h-9 w-full max-w-md" />
35
+ {Array.from({ length: filterRows }).map((_, i) => (
36
+ <div key={i} className="flex flex-wrap gap-2">
37
+ <Skeleton className="h-8 w-28 rounded-md" />
38
+ <Skeleton className="h-8 w-32 rounded-md" />
39
+ <Skeleton className="h-8 w-24 rounded-md" />
40
+ </div>
41
+ ))}
42
+ </div>
43
+ <div className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
44
+ <div className="border-b border-slate-100 px-4 py-3">
45
+ <Skeleton className="h-4 w-40" />
46
+ </div>
47
+ <div className="divide-y divide-slate-100">
48
+ {Array.from({ length: tableRows }).map((_, i) => (
49
+ <div key={i} className="flex items-center gap-3 px-4 py-3">
50
+ <Skeleton className="h-4 w-24" />
51
+ <Skeleton className="h-4 flex-1" />
52
+ <Skeleton className="h-4 w-16" />
53
+ <Skeleton className="h-4 w-20" />
54
+ </div>
55
+ ))}
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </AppShell>
60
+ );
61
+ }
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname, useSearchParams } from "next/navigation";
5
+
6
+ export function Pagination({
7
+ page,
8
+ pageSize,
9
+ total,
10
+ }: {
11
+ page: number;
12
+ pageSize: number;
13
+ total: number;
14
+ }) {
15
+ const pathname = usePathname();
16
+ const params = useSearchParams();
17
+ const pages = Math.max(1, Math.ceil(total / pageSize));
18
+
19
+ function href(p: number) {
20
+ const next = new URLSearchParams(params.toString());
21
+ next.set("page", String(p));
22
+ return `${pathname}?${next.toString()}`;
23
+ }
24
+
25
+ const from = total === 0 ? 0 : (page - 1) * pageSize + 1;
26
+ const to = Math.min(page * pageSize, total);
27
+ const btn =
28
+ "inline-flex h-8 items-center rounded-md border border-slate-200 bg-white px-3 text-xs font-medium hover:bg-slate-50 disabled:opacity-40";
29
+
30
+ return (
31
+ <div className="flex flex-wrap items-center justify-between gap-3 pt-2">
32
+ <p className="text-sm text-slate-500">
33
+ {from}–{to} sur {total.toLocaleString("fr-FR")}
34
+ </p>
35
+ <div className="flex items-center gap-2">
36
+ {page > 1 ? (
37
+ <Link className={btn} href={href(page - 1)}>
38
+ Précédent
39
+ </Link>
40
+ ) : (
41
+ <span className={`${btn} opacity-40`}>Précédent</span>
42
+ )}
43
+ <span className="text-sm tabular-nums text-slate-600">
44
+ Page {page} / {pages}
45
+ </span>
46
+ {page < pages ? (
47
+ <Link className={btn} href={href(page + 1)}>
48
+ Suivant
49
+ </Link>
50
+ ) : (
51
+ <span className={`${btn} opacity-40`}>Suivant</span>
52
+ )}
53
+ </div>
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
4
+ import { cn } from "@creezio/shell-ui";
5
+
6
+ function Avatar({
7
+ className,
8
+ ...props
9
+ }: React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>) {
10
+ return (
11
+ <AvatarPrimitive.Root
12
+ className={cn("relative flex h-9 w-9 shrink-0 overflow-hidden rounded-full", className)}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function AvatarFallback({
19
+ className,
20
+ ...props
21
+ }: React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>) {
22
+ return (
23
+ <AvatarPrimitive.Fallback
24
+ className={cn(
25
+ "flex h-full w-full items-center justify-center rounded-full bg-slate-200 text-xs font-medium text-slate-700",
26
+ className,
27
+ )}
28
+ {...props}
29
+ />
30
+ );
31
+ }
32
+
33
+ export { Avatar, AvatarFallback };
@@ -0,0 +1,36 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { cn } from "@creezio/shell-ui";
3
+
4
+ const badgeVariants = cva(
5
+ "inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium transition-colors",
6
+ {
7
+ variants: {
8
+ variant: {
9
+ default: "border-transparent bg-slate-900 text-white",
10
+ secondary: "border-transparent bg-slate-100 text-slate-700",
11
+ outline: "border-slate-200 text-slate-700",
12
+ success: "border-transparent bg-emerald-100 text-emerald-800",
13
+ warning: "border-transparent bg-amber-100 text-amber-800",
14
+ danger: "border-transparent bg-red-100 text-red-800",
15
+ info: "border-transparent bg-sky-100 text-sky-800",
16
+ muted: "border-transparent bg-slate-50 text-slate-500",
17
+ },
18
+ },
19
+ defaultVariants: { variant: "default" },
20
+ },
21
+ );
22
+
23
+ export interface BadgeProps
24
+ extends React.HTMLAttributes<HTMLSpanElement>,
25
+ VariantProps<typeof badgeVariants> {}
26
+
27
+ /**
28
+ * <span> et non <div> : un badge est utilisé DANS des <p>, <a> et <span>
29
+ * (fiche produit, dashboard, préférences…). Un <div> y est du HTML invalide :
30
+ * le parseur navigateur restructure le DOM (ferme le <p> avant le div) et
31
+ * l'hydratation React échoue (« Hydration failed » #418/#422, cascade
32
+ * possible en #310) — écran « Application error » dans l'app desktop.
33
+ */
34
+ export function Badge({ className, variant, ...props }: BadgeProps) {
35
+ return <span className={cn(badgeVariants({ variant }), className)} {...props} />;
36
+ }
@@ -0,0 +1,93 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight } from "lucide-react";
4
+ import { cn } from "@creezio/shell-ui";
5
+
6
+ const Breadcrumb = React.forwardRef<
7
+ HTMLElement,
8
+ React.ComponentPropsWithoutRef<"nav"> & { separator?: React.ReactNode }
9
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="Fil d'Ariane" {...props} />);
10
+ Breadcrumb.displayName = "Breadcrumb";
11
+
12
+ const BreadcrumbList = React.forwardRef<
13
+ HTMLOListElement,
14
+ React.ComponentPropsWithoutRef<"ol">
15
+ >(({ className, ...props }, ref) => (
16
+ <ol
17
+ ref={ref}
18
+ className={cn(
19
+ "flex min-w-0 flex-wrap items-center gap-1.5 overflow-hidden text-xs text-slate-500",
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ ));
25
+ BreadcrumbList.displayName = "BreadcrumbList";
26
+
27
+ const BreadcrumbItem = React.forwardRef<
28
+ HTMLLIElement,
29
+ React.ComponentPropsWithoutRef<"li">
30
+ >(({ className, ...props }, ref) => (
31
+ <li
32
+ ref={ref}
33
+ className={cn("inline-flex min-w-0 items-center gap-1.5", className)}
34
+ {...props}
35
+ />
36
+ ));
37
+ BreadcrumbItem.displayName = "BreadcrumbItem";
38
+
39
+ const BreadcrumbLink = React.forwardRef<
40
+ HTMLAnchorElement,
41
+ React.ComponentPropsWithoutRef<"a"> & { asChild?: boolean }
42
+ >(({ asChild, className, ...props }, ref) => {
43
+ const Comp = asChild ? Slot : "a";
44
+ return (
45
+ <Comp
46
+ ref={ref}
47
+ className={cn("truncate text-sky-700 transition-colors hover:underline", className)}
48
+ {...props}
49
+ />
50
+ );
51
+ });
52
+ BreadcrumbLink.displayName = "BreadcrumbLink";
53
+
54
+ const BreadcrumbPage = React.forwardRef<
55
+ HTMLSpanElement,
56
+ React.ComponentPropsWithoutRef<"span">
57
+ >(({ className, ...props }, ref) => (
58
+ <span
59
+ ref={ref}
60
+ role="link"
61
+ aria-disabled="true"
62
+ aria-current="page"
63
+ className={cn("truncate font-medium text-slate-800", className)}
64
+ {...props}
65
+ />
66
+ ));
67
+ BreadcrumbPage.displayName = "BreadcrumbPage";
68
+
69
+ function BreadcrumbSeparator({
70
+ children,
71
+ className,
72
+ ...props
73
+ }: React.ComponentProps<"li">) {
74
+ return (
75
+ <li
76
+ role="presentation"
77
+ aria-hidden="true"
78
+ className={cn("shrink-0 text-slate-300 [&>svg]:h-3 [&>svg]:w-3", className)}
79
+ {...props}
80
+ >
81
+ {children ?? <ChevronRight />}
82
+ </li>
83
+ );
84
+ }
85
+
86
+ export {
87
+ Breadcrumb,
88
+ BreadcrumbList,
89
+ BreadcrumbItem,
90
+ BreadcrumbLink,
91
+ BreadcrumbPage,
92
+ BreadcrumbSeparator,
93
+ };
@@ -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 "@creezio/shell-ui";
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 "@creezio/shell-ui";
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 };