@tsiky/components-r19 1.0.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 (287) hide show
  1. package/.prettierignore +4 -0
  2. package/.prettierrc +9 -0
  3. package/.storybook/main.ts +17 -0
  4. package/.storybook/preview.ts +21 -0
  5. package/.storybook/vitest.setup.ts +7 -0
  6. package/README.md +69 -0
  7. package/chart.ts +1 -0
  8. package/eslint.config.js +40 -0
  9. package/index.html +13 -0
  10. package/index.ts +33 -0
  11. package/package.json +63 -0
  12. package/public/vite.svg +1 -0
  13. package/src/App.css +42 -0
  14. package/src/App.tsx +12 -0
  15. package/src/assets/accessibility.png +0 -0
  16. package/src/assets/accessibility.svg +1 -0
  17. package/src/assets/addon-library.png +0 -0
  18. package/src/assets/assets.png +0 -0
  19. package/src/assets/avif-test-image.avif +0 -0
  20. package/src/assets/context.png +0 -0
  21. package/src/assets/discord.svg +1 -0
  22. package/src/assets/docs.png +0 -0
  23. package/src/assets/figma-plugin.png +0 -0
  24. package/src/assets/github.svg +1 -0
  25. package/src/assets/react.svg +1 -0
  26. package/src/assets/share.png +0 -0
  27. package/src/assets/styling.png +0 -0
  28. package/src/assets/testing.png +0 -0
  29. package/src/assets/theming.png +0 -0
  30. package/src/assets/tutorials.svg +1 -0
  31. package/src/assets/youtube.svg +1 -0
  32. package/src/components/AddItemModal/AddItemModal.module.css +72 -0
  33. package/src/components/AddItemModal/AddItemModal.tsx +82 -0
  34. package/src/components/AddItemModal/index.ts +1 -0
  35. package/src/components/Alert/Alert.css +54 -0
  36. package/src/components/Alert/Alert.stories.tsx +82 -0
  37. package/src/components/Alert/Alert.tsx +85 -0
  38. package/src/components/Alert/AlertContext.tsx +200 -0
  39. package/src/components/Alert/AlertModels.ts +34 -0
  40. package/src/components/Alert/index.ts +3 -0
  41. package/src/components/AnnouncementPanel/FlexRowContainer.css +17 -0
  42. package/src/components/AnnouncementPanel/FlexRowContainer.stories.tsx +329 -0
  43. package/src/components/AnnouncementPanel/FlexRowContainer.tsx +24 -0
  44. package/src/components/AnnouncementPanel/ListBox/CounterListBox.css +56 -0
  45. package/src/components/AnnouncementPanel/ListBox/CounterListBox.stories.tsx +292 -0
  46. package/src/components/AnnouncementPanel/ListBox/CounterListBox.tsx +106 -0
  47. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.css +57 -0
  48. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.stories.tsx +189 -0
  49. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.tsx +138 -0
  50. package/src/components/AnnouncementPanel/ListBox/TrendListBox.css +61 -0
  51. package/src/components/AnnouncementPanel/ListBox/TrendListBox.stories.tsx +257 -0
  52. package/src/components/AnnouncementPanel/ListBox/TrendListBox.tsx +90 -0
  53. package/src/components/AnnouncementPanel/ListBox/index.ts +3 -0
  54. package/src/components/AnnouncementPanel/ListContentContainer.css +23 -0
  55. package/src/components/AnnouncementPanel/ListContentContainer.stories.tsx +212 -0
  56. package/src/components/AnnouncementPanel/ListContentContainer.tsx +33 -0
  57. package/src/components/AnnouncementPanel/index.ts +3 -0
  58. package/src/components/BandChart/index.tsx +282 -0
  59. package/src/components/Button/Button.module.css +165 -0
  60. package/src/components/Button/Button.stories.ts +132 -0
  61. package/src/components/Button/Button.tsx +55 -0
  62. package/src/components/Button/button.css +29 -0
  63. package/src/components/Button/index.ts +2 -0
  64. package/src/components/ChartContainer/ChartContainer.css +116 -0
  65. package/src/components/ChartContainer/ChartContainer.stories.tsx +159 -0
  66. package/src/components/ChartContainer/ChartContainer.tsx +155 -0
  67. package/src/components/ChartContainer/index.ts +1 -0
  68. package/src/components/Charts/area-chart-admission/AreaChartAdmission.stories.tsx +65 -0
  69. package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +89 -0
  70. package/src/components/Charts/area-chart-admission/content.json +48 -0
  71. package/src/components/Charts/area-chart-hospitalisation/AreaChartHospitalisation.stories.tsx +141 -0
  72. package/src/components/Charts/area-chart-hospitalisation/AreaChartHospitalisation.tsx +262 -0
  73. package/src/components/Charts/area-chart-hospitalisation/content.json +55 -0
  74. package/src/components/Charts/bar-chart/BarChart.stories.tsx +50 -0
  75. package/src/components/Charts/bar-chart/BarChart.tsx +132 -0
  76. package/src/components/Charts/bar-chart/content.json +15 -0
  77. package/src/components/Charts/boxplot-chart/BoxPlotChart.stories.tsx +63 -0
  78. package/src/components/Charts/boxplot-chart/BoxPlotChart.tsx +114 -0
  79. package/src/components/Charts/boxplot-chart/boxUtils.ts +22 -0
  80. package/src/components/Charts/boxplot-chart/content.json +11 -0
  81. package/src/components/Charts/mixed-chart/MixedChart.stories.tsx +83 -0
  82. package/src/components/Charts/mixed-chart/MixedChart.tsx +625 -0
  83. package/src/components/Charts/mixed-chart/content.json +34 -0
  84. package/src/components/Charts/sankey-adaptation/sankey.tsx +70 -0
  85. package/src/components/Charts/sankey-chart/SankeyChart.stories.tsx +69 -0
  86. package/src/components/Charts/sankey-chart/SankeyChart.tsx +155 -0
  87. package/src/components/Charts/sankey-chart/content.json +15 -0
  88. package/src/components/Charts/stacked-column/StackedColumn.stories.tsx +72 -0
  89. package/src/components/Charts/stacked-column/StackedColumn.tsx +406 -0
  90. package/src/components/Charts/stacked-column/content.json +37 -0
  91. package/src/components/Charts/stacked-column-one-hundred-percent/StackedColumnOneHundredPercent.stories.tsx +43 -0
  92. package/src/components/Charts/stacked-column-one-hundred-percent/StackedColumnOneHundredPercent.tsx +75 -0
  93. package/src/components/Charts/stacked-column-one-hundred-percent/content.json +6 -0
  94. package/src/components/CircularProgress/CircularProgress.css +79 -0
  95. package/src/components/CircularProgress/CircularProgress.stories.tsx +251 -0
  96. package/src/components/CircularProgress/CircularProgress.tsx +101 -0
  97. package/src/components/CircularProgress/index.ts +2 -0
  98. package/src/components/Configure.mdx +369 -0
  99. package/src/components/DayStatCard/DayStatCard.css +50 -0
  100. package/src/components/DayStatCard/DayStatCard.stories.tsx +273 -0
  101. package/src/components/DayStatCard/DayStatCard.tsx +69 -0
  102. package/src/components/DayStatCard/index.ts +2 -0
  103. package/src/components/DraggableSwitcher/DraggableSwitcherButton.tsx +58 -0
  104. package/src/components/DraggableSwitcher/context/useDraggableSwitcher.tsx +45 -0
  105. package/src/components/DraggableSwitcher/index.ts +2 -0
  106. package/src/components/DropdownMenu/DropdownMenu.css +100 -0
  107. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +174 -0
  108. package/src/components/DropdownMenu/DropdownMenu.tsx +106 -0
  109. package/src/components/DropdownMenu/index.ts +1 -0
  110. package/src/components/DynamicForm/DynamicFom.stories.ts +773 -0
  111. package/src/components/DynamicForm/DynamicForm.module.css +468 -0
  112. package/src/components/DynamicForm/DynamicForm.tsx +224 -0
  113. package/src/components/DynamicForm/index.ts +3 -0
  114. package/src/components/DynamicForm/tools/form-metadata.ts +82 -0
  115. package/src/components/DynamicForm/tools/validation.ts +168 -0
  116. package/src/components/DynamicInput/DynamicInput.module.css +126 -0
  117. package/src/components/DynamicInput/DynamicInput.stories.ts +350 -0
  118. package/src/components/DynamicInput/DynamicInput.tsx +144 -0
  119. package/src/components/DynamicInput/index.ts +2 -0
  120. package/src/components/DynamicInput/input/CheckboxInput.tsx +41 -0
  121. package/src/components/DynamicInput/input/ColorInput.tsx +46 -0
  122. package/src/components/DynamicInput/input/DateInput.tsx +57 -0
  123. package/src/components/DynamicInput/input/FileInput.tsx +174 -0
  124. package/src/components/DynamicInput/input/InputWrapper.tsx +26 -0
  125. package/src/components/DynamicInput/input/RadioInput.tsx +46 -0
  126. package/src/components/DynamicInput/input/RangeInput.tsx +46 -0
  127. package/src/components/DynamicInput/input/SelectInput.tsx +75 -0
  128. package/src/components/DynamicInput/input/TextInput.tsx +101 -0
  129. package/src/components/DynamicInput/input/TextareaInput.tsx +47 -0
  130. package/src/components/DynamicInput/input/assets/ColorInput.module.css +48 -0
  131. package/src/components/DynamicInput/input/assets/DateInput.module.css +83 -0
  132. package/src/components/DynamicInput/input/assets/FileInput.module.css +133 -0
  133. package/src/components/DynamicInput/input/assets/RangeInput.module.css +169 -0
  134. package/src/components/DynamicInput/input/assets/SelectInput.module.css +95 -0
  135. package/src/components/DynamicInput/input/index.ts +10 -0
  136. package/src/components/DynamicTable/AdvancedFilters.tsx +196 -0
  137. package/src/components/DynamicTable/ColumnSorter.tsx +185 -0
  138. package/src/components/DynamicTable/Pagination.tsx +115 -0
  139. package/src/components/DynamicTable/TableBody.tsx +42 -0
  140. package/src/components/DynamicTable/TableCell.tsx +30 -0
  141. package/src/components/DynamicTable/TableHeader.tsx +34 -0
  142. package/src/components/DynamicTable/TableRow.tsx +56 -0
  143. package/src/components/DynamicTable/TableauDynamic.stories.ts +1014 -0
  144. package/src/components/DynamicTable/TableauDynamique.module.css +1287 -0
  145. package/src/components/DynamicTable/TableauDynamique.tsx +154 -0
  146. package/src/components/DynamicTable/filters/BooleanFilter.tsx +30 -0
  147. package/src/components/DynamicTable/filters/DateFilter.tsx +28 -0
  148. package/src/components/DynamicTable/filters/DateRangeFilter.tsx +51 -0
  149. package/src/components/DynamicTable/filters/FilterRenderer.tsx +117 -0
  150. package/src/components/DynamicTable/filters/MultiSelectFilter.tsx +59 -0
  151. package/src/components/DynamicTable/filters/NumberFilter.tsx +37 -0
  152. package/src/components/DynamicTable/filters/OperatorFilter.tsx +64 -0
  153. package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -0
  154. package/src/components/DynamicTable/filters/TextFilter.tsx +39 -0
  155. package/src/components/DynamicTable/filters/index.ts +9 -0
  156. package/src/components/DynamicTable/hooks/index.ts +2 -0
  157. package/src/components/DynamicTable/hooks/useAsyncActions.ts +36 -0
  158. package/src/components/DynamicTable/hooks/useFilters.ts +142 -0
  159. package/src/components/DynamicTable/hooks/useTableData.ts +216 -0
  160. package/src/components/DynamicTable/index.ts +11 -0
  161. package/src/components/DynamicTable/tools/filterTypes.ts +118 -0
  162. package/src/components/DynamicTable/tools/index.ts +3 -0
  163. package/src/components/DynamicTable/tools/tableConfig.ts +96 -0
  164. package/src/components/DynamicTable/tools/tableTypes.ts +63 -0
  165. package/src/components/EntryControl/EntryControl.module.css +218 -0
  166. package/src/components/EntryControl/EntryControl.stories.tsx +71 -0
  167. package/src/components/EntryControl/EntryControl.tsx +117 -0
  168. package/src/components/EntryControl/index.ts +2 -0
  169. package/src/components/Grid/Grid.stories.tsx +94 -0
  170. package/src/components/Grid/Grid.tsx +214 -0
  171. package/src/components/Grid/grid.css +285 -0
  172. package/src/components/Grid/index.ts +2 -0
  173. package/src/components/Header/Header.stories.tsx +164 -0
  174. package/src/components/Header/Header.tsx +59 -0
  175. package/src/components/Header/header.css +31 -0
  176. package/src/components/Header/index.ts +1 -0
  177. package/src/components/IconText/IconText..stories.tsx +135 -0
  178. package/src/components/IconText/IconText.css +43 -0
  179. package/src/components/IconText/IconText.tsx +43 -0
  180. package/src/components/IconText/index.ts +1 -0
  181. package/src/components/LanguageSelector/LanguageSelector.css +31 -0
  182. package/src/components/LanguageSelector/LanguageSelector.stories.tsx +38 -0
  183. package/src/components/LanguageSelector/LanguageSelector.tsx +37 -0
  184. package/src/components/LanguageSelector/index.ts +1 -0
  185. package/src/components/Logo/Logo.css +89 -0
  186. package/src/components/Logo/Logo.stories.tsx +79 -0
  187. package/src/components/Logo/Logo.tsx +22 -0
  188. package/src/components/Logo/index.ts +2 -0
  189. package/src/components/MetricsPanel/MetricsItem.tsx +128 -0
  190. package/src/components/MetricsPanel/MetricsPanel.module.css +636 -0
  191. package/src/components/MetricsPanel/MetricsPanel.stories.tsx +692 -0
  192. package/src/components/MetricsPanel/MetricsPanel.tsx +282 -0
  193. package/src/components/MetricsPanel/PanelHeader.tsx +19 -0
  194. package/src/components/MetricsPanel/SummaryCard.tsx +61 -0
  195. package/src/components/MetricsPanel/index.ts +4 -0
  196. package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +125 -0
  197. package/src/components/MetricsPanel/renderers/ImageCard.tsx +62 -0
  198. package/src/components/MetricsPanel/renderers/PertinenceCard.tsx +55 -0
  199. package/src/components/MetricsPanel/tools/MetricsPanelTypes.ts +62 -0
  200. package/src/components/MetricsPanel/tools/chooseDefaultRender.ts +50 -0
  201. package/src/components/MetricsPanel/tools/colorUtils.ts +39 -0
  202. package/src/components/MetricsPanel/tools/index.ts +2 -0
  203. package/src/components/ModuleHeader/ModuleHeader.css +37 -0
  204. package/src/components/ModuleHeader/ModuleHeader.stories.tsx +37 -0
  205. package/src/components/ModuleHeader/ModuleHeader.tsx +42 -0
  206. package/src/components/ModuleHeader/index.ts +1 -0
  207. package/src/components/ModuleSideBar/ModuleSideBar.css +227 -0
  208. package/src/components/ModuleSideBar/ModuleSideBar.stories.tsx +40 -0
  209. package/src/components/ModuleSideBar/ModuleSideBar.tsx +155 -0
  210. package/src/components/ModuleSideBar/index.ts +1 -0
  211. package/src/components/NavBar/NavBar.css +58 -0
  212. package/src/components/NavBar/NavBar.stories.tsx +169 -0
  213. package/src/components/NavBar/NavBar.tsx +100 -0
  214. package/src/components/NavBar/NavContext.tsx +30 -0
  215. package/src/components/NavBar/index.ts +2 -0
  216. package/src/components/NavItem/NavItem.css +29 -0
  217. package/src/components/NavItem/NavItem.tsx +58 -0
  218. package/src/components/NavItem/index.ts +1 -0
  219. package/src/components/Page/Dashboard.tsx +93 -0
  220. package/src/components/Page/Page.stories.ts +33 -0
  221. package/src/components/Page/Page.tsx +73 -0
  222. package/src/components/Page/page.css +81 -0
  223. package/src/components/PerformanceCard/PerformanceCard.module.css +232 -0
  224. package/src/components/PerformanceCard/PerformanceCard.stories.tsx +441 -0
  225. package/src/components/PerformanceCard/PerformanceCard.tsx +198 -0
  226. package/src/components/PerformanceCard/defaultHistogramRenderer.tsx +32 -0
  227. package/src/components/PerformanceCard/tools/types.ts +50 -0
  228. package/src/components/PerformanceCard/tools/usePerformanceCard.ts +93 -0
  229. package/src/components/PeriodRange/PeriodRange.module.css +158 -0
  230. package/src/components/PeriodRange/PeriodRange.stories.tsx +66 -0
  231. package/src/components/PeriodRange/PeriodRange.tsx +130 -0
  232. package/src/components/PeriodSelect/PeriodSelect.module.css +65 -0
  233. package/src/components/PeriodSelect/PeriodSelect.stories.tsx +40 -0
  234. package/src/components/PeriodSelect/PeriodSelect.tsx +42 -0
  235. package/src/components/PeriodSelect/index.ts +1 -0
  236. package/src/components/ScrollableHorizontale/ScrollableHorizontale.css +40 -0
  237. package/src/components/ScrollableHorizontale/ScrollableHorizontale.stories.tsx +133 -0
  238. package/src/components/ScrollableHorizontale/ScrollableHorizontale.tsx +29 -0
  239. package/src/components/ScrollableHorizontale/index.ts +1 -0
  240. package/src/components/SearchBar/SearchBar.css +40 -0
  241. package/src/components/SearchBar/SearchBar.stories.tsx +36 -0
  242. package/src/components/SearchBar/SearchBar.tsx +30 -0
  243. package/src/components/SearchBar/index.ts +1 -0
  244. package/src/components/SectionTitle/SectionTitle.css +21 -0
  245. package/src/components/SectionTitle/SectionTitle.stories.tsx +39 -0
  246. package/src/components/SectionTitle/SectionTitle.tsx +18 -0
  247. package/src/components/SideComponent/PatientEditor.tsx +64 -0
  248. package/src/components/SideComponent/SideComponent.css +179 -0
  249. package/src/components/SideComponent/SideComponent.stories.tsx +547 -0
  250. package/src/components/SideComponent/SideComponent.tsx +243 -0
  251. package/src/components/SideComponent/hooks/useBodyScrollLock.ts +15 -0
  252. package/src/components/SideComponent/index.ts +2 -0
  253. package/src/components/SideComponent/portal.ts +11 -0
  254. package/src/components/SubNavBar/SubNavBar.tsx +41 -0
  255. package/src/components/SubNavBar/index.ts +1 -0
  256. package/src/components/Switcher/Switcher.css +65 -0
  257. package/src/components/Switcher/Switcher.stories.tsx +153 -0
  258. package/src/components/Switcher/Switcher.tsx +83 -0
  259. package/src/components/Switcher/index.ts +1 -0
  260. package/src/components/Title/Title.stories.tsx +18 -0
  261. package/src/components/Title/Title.tsx +26 -0
  262. package/src/components/Title/index.ts +1 -0
  263. package/src/components/TranslationKey/TranslationKey.css +272 -0
  264. package/src/components/TranslationKey/TranslationKey.stories.tsx +50 -0
  265. package/src/components/TranslationKey/TranslationKey.tsx +245 -0
  266. package/src/components/TranslationKey/index.ts +1 -0
  267. package/src/components/TrendItem/TrendItem.css +36 -0
  268. package/src/components/TrendItem/TrendItem.stories.tsx +276 -0
  269. package/src/components/TrendItem/TrendItem.tsx +46 -0
  270. package/src/components/TrendItem/index.ts +1 -0
  271. package/src/components/TrendList/TrendList.css +16 -0
  272. package/src/components/TrendList/TrendList.stories.tsx +337 -0
  273. package/src/components/TrendList/TrendList.tsx +45 -0
  274. package/src/components/TrendList/index.ts +1 -0
  275. package/src/components/theme/ThemeSwitcherButton.tsx +64 -0
  276. package/src/components/theme/context/ThemeContext.tsx +61 -0
  277. package/src/components/theme/context/index.ts +2 -0
  278. package/src/components/theme/context/useThemeSwitcher.ts +18 -0
  279. package/src/components/theme/index.ts +1 -0
  280. package/src/index.css +68 -0
  281. package/src/main.tsx +10 -0
  282. package/src/vite-env.d.ts +1 -0
  283. package/tsconfig.app.json +27 -0
  284. package/tsconfig.json +4 -0
  285. package/tsconfig.node.json +25 -0
  286. package/vite.config.ts +43 -0
  287. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,50 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* chooseDefaultRender.ts
3
+ Retourne une fonction de rendu (RenderFn) compatible .ts (pas de JSX).
4
+ */
5
+ import React from 'react';
6
+ import ImageCard from '../renderers/ImageCard';
7
+ import PertinenceCard from '../renderers/PertinenceCard';
8
+ import CompactRenderer from '../renderers/CompactRenderer';
9
+ import type { Preset } from './MetricsPanelTypes';
10
+ import type { MetricsItem as ItemType } from './MetricsPanelTypes';
11
+
12
+ export type RenderFn = (item: ItemType, index?: number) => React.ReactNode;
13
+
14
+ const callCompactRenderer = (renderer: any, item: ItemType): React.ReactNode => {
15
+ try {
16
+ if (typeof renderer === 'function') {
17
+ const res = (renderer as any)(item);
18
+ if (res !== undefined) return res;
19
+ }
20
+ } catch {
21
+ /* empty */
22
+ }
23
+ try {
24
+ return React.createElement(renderer as any, { item });
25
+ } catch (e) {
26
+ console.warn('chooseDefaultRender: impossible de rendre CompactRenderer', e);
27
+ return null;
28
+ }
29
+ };
30
+
31
+ const chooseDefaultRender = (preset?: Preset): RenderFn | undefined => {
32
+ switch (preset) {
33
+ case 'image':
34
+ // React.createElement pour éviter le JSX dans un fichier .ts
35
+ return (item: ItemType) => React.createElement(ImageCard as any, { item });
36
+ case 'pertinence':
37
+ return (item: ItemType) => React.createElement(PertinenceCard as any, { item });
38
+ case 'alternateHorizontal':
39
+ return (item: ItemType) => callCompactRenderer(CompactRenderer as any, item);
40
+ case 'compact':
41
+ return (item: ItemType) => callCompactRenderer(CompactRenderer as any, item);
42
+ case 'summary':
43
+ return undefined;
44
+ case 'default':
45
+ default:
46
+ return undefined;
47
+ }
48
+ };
49
+
50
+ export default chooseDefaultRender;
@@ -0,0 +1,39 @@
1
+ export const hexToRgba = (color: string | undefined | null, alpha = 1): string => {
2
+ if (!color || typeof color !== 'string') {
3
+ return `rgba(124, 58, 237, ${alpha})`;
4
+ }
5
+
6
+ const val = color.trim();
7
+
8
+ if (val.toLowerCase() === 'transparent') return 'rgba(0,0,0,0)';
9
+
10
+ if (/^rgba?\(/i.test(val)) {
11
+ if (/^rgba\(/i.test(val)) {
12
+ return val.replace(/rgba\(([^)]+),\s*([0-9.]+)\s*\)$/, (_, rgb) => {
13
+ const parts = rgb
14
+ .split(',')
15
+ .map((p: string) => p.trim())
16
+ .slice(0, 3)
17
+ .join(', ');
18
+ return `rgba(${parts}, ${alpha})`;
19
+ });
20
+ }
21
+ return val;
22
+ }
23
+
24
+ let cleaned = val.replace('#', '').toLowerCase();
25
+ if (cleaned.length === 3)
26
+ cleaned = cleaned
27
+ .split('')
28
+ .map((c) => c + c)
29
+ .join('');
30
+ if (!/^[0-9a-f]{6}$/i.test(cleaned)) {
31
+ return `rgba(124, 58, 237, ${alpha})`;
32
+ }
33
+
34
+ const r = parseInt(cleaned.slice(0, 2), 16);
35
+ const g = parseInt(cleaned.slice(2, 4), 16);
36
+ const b = parseInt(cleaned.slice(4, 6), 16);
37
+
38
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
39
+ };
@@ -0,0 +1,2 @@
1
+ export * from './colorUtils';
2
+ export * from './MetricsPanelTypes';
@@ -0,0 +1,37 @@
1
+ .header-container {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ }
6
+
7
+ .header-info h1 {
8
+ font-size: var(--h3);
9
+ font-weight: var(--font-medium);
10
+ margin: 0 0 0.25rem 0;
11
+ }
12
+
13
+ .header-info p {
14
+ font-size: var(--text-small);
15
+ color: #6b7280;
16
+ margin: 0;
17
+ }
18
+
19
+ .add-key-button {
20
+ display: inline-flex;
21
+ align-items: center;
22
+ gap: 0.5rem;
23
+ padding: 0.5rem 1rem;
24
+ background-color: var(--color-primary);
25
+ border: none;
26
+ border-radius: 9999px;
27
+ font-weight: var(--font-medium);
28
+ font-size: var(--text-small);
29
+ cursor: pointer;
30
+ transition: background-color 0.2s;
31
+ color: var(--color-text-on-primary);
32
+ }
33
+
34
+ .add-key-icon {
35
+ width: 1rem;
36
+ height: 1rem;
37
+ }
@@ -0,0 +1,37 @@
1
+ // ModuleHeader.stories.tsx
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ModuleHeader } from './ModuleHeader';
4
+
5
+ const meta: Meta<typeof ModuleHeader> = {
6
+ title: 'Components/ModuleHeader',
7
+ component: ModuleHeader,
8
+ tags: ['autodocs'],
9
+ args: {
10
+ title: 'Interface utilisateur',
11
+ keyCount: 5,
12
+ },
13
+ };
14
+ export default meta;
15
+
16
+ type Story = StoryObj<typeof ModuleHeader>;
17
+
18
+ export const Default: Story = {};
19
+
20
+ export const WithManyKeys: Story = {
21
+ args: {
22
+ keyCount: 42,
23
+ },
24
+ };
25
+
26
+ export const CustomTitle: Story = {
27
+ args: {
28
+ title: 'Paramètres',
29
+ keyCount: 8,
30
+ },
31
+ };
32
+
33
+ export const WithAddKeyHandler: Story = {
34
+ args: {
35
+ onAddKey: () => alert('Nouvelle clé ajoutée 🚀'),
36
+ },
37
+ };
@@ -0,0 +1,42 @@
1
+ import React, { useState } from 'react';
2
+ import { Plus } from 'lucide-react';
3
+ import './ModuleHeader.css';
4
+ import { AddItemModal } from '../AddItemModal/AddItemModal';
5
+
6
+ interface ModuleHeaderProps {
7
+ title: string;
8
+ keyCount: number;
9
+ onAddKey?: (keyName: string) => void;
10
+ }
11
+
12
+ export const ModuleHeader: React.FC<ModuleHeaderProps> = ({ title, keyCount, onAddKey }) => {
13
+ const [isModalOpen, setIsModalOpen] = useState(false);
14
+
15
+ const handleSave = (values: Record<string, string | number>) => {
16
+ const keyName = values.key as string;
17
+ if (onAddKey && keyName) {
18
+ onAddKey(keyName);
19
+ }
20
+ };
21
+
22
+ return (
23
+ <div className='header-container'>
24
+ <div className='header-info'>
25
+ <h1>{title}</h1>
26
+ <p>{keyCount} clés</p>
27
+ </div>
28
+ <button onClick={() => setIsModalOpen(true)} className='add-key-button'>
29
+ <Plus className='add-key-icon' />
30
+ Clé
31
+ </button>
32
+
33
+ <AddItemModal
34
+ isOpen={isModalOpen}
35
+ title='Ajouter une clé'
36
+ onClose={() => setIsModalOpen(false)}
37
+ onSave={handleSave}
38
+ fields={[{ name: 'key', label: 'Nouvelle clé', placeholder: 'Entrer une nouvelle clé' }]}
39
+ />
40
+ </div>
41
+ );
42
+ };
@@ -0,0 +1 @@
1
+ export * from './ModuleHeader';
@@ -0,0 +1,227 @@
1
+ /* ModuleSideBar.css */
2
+
3
+ /* Conteneur principal de la sidebar */
4
+ .module-sidebar {
5
+ width: 100%;
6
+ min-height: 100vh;
7
+ background: var(--color-card-background);
8
+ border-radius: 6px;
9
+ padding: 8px;
10
+ box-sizing: border-box;
11
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
12
+ display: flex;
13
+ flex-direction: column;
14
+ border: 1px solid var(--color-border);
15
+ transition:
16
+ width 0.25s ease,
17
+ min-height 0.25s ease;
18
+ }
19
+
20
+ /* Header de la sidebar */
21
+ .sidebar-header {
22
+ display: flex;
23
+ justify-content: space-between;
24
+ align-items: center;
25
+ font-size: var(--text-small);
26
+ font-weight: var(--font-semibold, var(--font-medium));
27
+ padding: 4px 2px;
28
+ }
29
+
30
+ /* Actions dans l'entête (FolderPlus + Menu) */
31
+ .header-actions {
32
+ display: flex;
33
+ gap: 8px;
34
+ align-items: center;
35
+ }
36
+
37
+ /* Séparateur */
38
+ .divider {
39
+ border: none;
40
+ border-top: 1px solid #ddd;
41
+ margin: 6px 0 10px;
42
+ }
43
+
44
+ /* Liste des modules */
45
+ .module-list {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 8px;
49
+ padding-bottom: 8px;
50
+ }
51
+
52
+ /* Item individuel de la liste
53
+ largeur FIXE pour empêcher le débordement, box-sizing inclus */
54
+ .module-item {
55
+ width: 100%; /* largeur fixe souhaitée */
56
+ box-sizing: border-box; /* inclut padding/border dans la largeur */
57
+ min-height: 48px;
58
+ display: flex;
59
+ justify-content: space-between;
60
+ align-items: center;
61
+ border: 1px solid #ddd;
62
+ border-radius: 6px;
63
+ padding: 10px;
64
+ cursor: pointer;
65
+ transition:
66
+ background 0.2s,
67
+ border-color 0.2s;
68
+ overflow: hidden; /* évite tout débordement visuel */
69
+ background: transparent;
70
+ }
71
+
72
+ /* Hover / sélection */
73
+ .module-item:hover {
74
+ border-color: var(--color-primary);
75
+ }
76
+
77
+ .module-item.selected {
78
+ border-color: var(--color-primary);
79
+ background-color: rgba(0, 143, 187, 0.08);
80
+ }
81
+
82
+ /* Partie gauche de l’item (icône + info)
83
+ min-width:0 et flex:1 permettent au contenu texte de shrink correctement */
84
+ .module-left {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 8px;
88
+ flex: 1 1 auto;
89
+ min-width: 0; /* crucial pour permettre au texte de Wrap/Shrink */
90
+ }
91
+
92
+ /* Icône dossier : taille fixe */
93
+ .item-folder {
94
+ flex: 0 0 auto;
95
+ margin-right: 8px;
96
+ }
97
+
98
+ /* Zone texte : autorise le retour à la ligne même sur mots longs */
99
+ .module-info {
100
+ display: flex;
101
+ flex-direction: column;
102
+ overflow-wrap: anywhere; /* autorise le cassage sur mots sans espace */
103
+ word-break: break-word; /* fallback pour anciens navigateurs */
104
+ min-width: 0; /* permet au texte de shrink à l'intérieur */
105
+ }
106
+
107
+ /* Titre : autorise le wrap et garde la mise en forme */
108
+ .module-title {
109
+ font-size: var(--text-small);
110
+ font-weight: var(--font-semibold, var(--font-medium));
111
+ white-space: normal; /* autorise retours à la ligne */
112
+ margin: 0;
113
+ line-height: 1.2;
114
+ }
115
+
116
+ /* Compteur à côté du titre */
117
+ .module-title .count {
118
+ font-size: 0.75rem;
119
+ font-weight: var(--font-normal);
120
+ margin-left: 6px;
121
+ }
122
+
123
+ /* Sous-titre */
124
+ .module-subtitle {
125
+ font-size: 0.75rem;
126
+ color: var(--color-muted, #6b7280);
127
+ margin-top: 4px;
128
+ line-height: 1.1;
129
+ }
130
+
131
+ /* Actions (icônes) : ne prennent pas trop d'espace */
132
+ .module-actions {
133
+ display: flex;
134
+ gap: 6px;
135
+ flex: 0 0 auto;
136
+ margin-left: 8px;
137
+ }
138
+
139
+ /* Boutons d'action génériques */
140
+ .action-btn {
141
+ background: none;
142
+ border: none;
143
+ cursor: pointer;
144
+ padding: 2px;
145
+ display: flex;
146
+ align-items: center;
147
+ transition: color 0.2s;
148
+ }
149
+
150
+ .action-btn:hover {
151
+ color: #2979ff;
152
+ }
153
+
154
+ /* petite marge entre icônes et texte pour lucide-react si besoin */
155
+ .header-folder,
156
+ .header-menu,
157
+ .item-folder,
158
+ .module-actions svg {
159
+ vertical-align: middle;
160
+ }
161
+
162
+ /* vue compacte (collapsed) : sidebar très étroite */
163
+ .module-sidebar.collapsed {
164
+ width: 2.5rem; /* taille du panneau lorsqu'il devient juste un bouton */
165
+ min-height: auto;
166
+ padding: 8px;
167
+ display: flex;
168
+ justify-content: center;
169
+ align-items: flex-start;
170
+ }
171
+
172
+ /* Contenu affiché en vue compacte (icone menu centrée) */
173
+ .module-sidebar.collapsed .module-list,
174
+ .module-sidebar.collapsed .sidebar-header + .divider,
175
+ .module-sidebar.collapsed .sidebar-header .header-text {
176
+ display: none;
177
+ }
178
+
179
+ /* Icône de toggle dans la vue compacte */
180
+ .menu-toggle {
181
+ cursor: pointer;
182
+ margin-top: 4px;
183
+ display: inline-flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ }
187
+
188
+ /* Règles responsive */
189
+
190
+ /* Desktop (par défaut) : garde la min-height complète */
191
+ @media (min-width: 769px) {
192
+ .module-sidebar {
193
+ min-height: 100vh;
194
+ }
195
+ }
196
+
197
+ /* Mobile : réduit la hauteur et supprime min-height casse */
198
+ @media (max-width: 768px) {
199
+ .module-sidebar {
200
+ min-height: auto;
201
+ width: 100%;
202
+ }
203
+
204
+ /* Les module-items peuvent être moins larges sur mobile */
205
+ .module-item {
206
+ width: 100%; /* occupe la largeur disponible sur mobile */
207
+ }
208
+
209
+ .module-actions {
210
+ margin-left: 6px;
211
+ }
212
+
213
+ .module-title .count {
214
+ margin-left: 4px;
215
+ }
216
+ }
217
+
218
+ /* petites optimisations pour très petits écrans */
219
+ @media (max-width: 420px) {
220
+ .module-subtitle {
221
+ font-size: 0.7rem;
222
+ }
223
+
224
+ .module-item {
225
+ padding: 8px;
226
+ }
227
+ }
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ModuleSideBar } from './ModuleSideBar';
3
+ import type { ModuleItem } from './ModuleSideBar';
4
+
5
+ const meta: Meta<typeof ModuleSideBar> = {
6
+ title: 'Components/ModuleSideBar',
7
+ component: ModuleSideBar,
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof ModuleSideBar>;
12
+
13
+ const modules: ModuleItem[] = [
14
+ {
15
+ id: '1',
16
+ title: 'Interface Utilisateur',
17
+ subtitle: 'Traductions pour l’interface principale',
18
+ count: 2,
19
+ },
20
+ {
21
+ id: '2',
22
+ title: 'Messages d’erreur',
23
+ subtitle: 'Messages d’erreur du système',
24
+ count: 1,
25
+ },
26
+ ];
27
+
28
+ export const Default: Story = {
29
+ args: {
30
+ modules,
31
+ },
32
+ };
33
+
34
+ export const WithActions: Story = {
35
+ args: {
36
+ modules,
37
+ onEdit: (id) => alert('Edit ' + id),
38
+ onDelete: (id) => alert('Delete ' + id),
39
+ },
40
+ };
@@ -0,0 +1,155 @@
1
+ import React, { useState } from 'react';
2
+ import { Folder, FolderPlus, Pencil, Trash2, Menu } from 'lucide-react';
3
+ import { AddItemModal } from '../AddItemModal/AddItemModal';
4
+ import './ModuleSideBar.css';
5
+ import type { Translation } from '../TranslationKey';
6
+
7
+ export interface TranslationData {
8
+ [key: string]: Translation[];
9
+ }
10
+
11
+ export interface ModuleItem {
12
+ id: string;
13
+ title: string;
14
+ subtitle: string;
15
+ translations: TranslationData;
16
+ }
17
+
18
+ interface ModuleSideBarProps {
19
+ modules: ModuleItem[];
20
+ onEdit?: (module: ModuleItem) => void;
21
+ onDelete?: (id: string) => void;
22
+ onAddModule?: (module: ModuleItem) => void;
23
+ onSelectModule?: (module: ModuleItem) => void;
24
+ }
25
+
26
+ export const ModuleSideBar: React.FC<ModuleSideBarProps> = ({
27
+ modules,
28
+ onEdit,
29
+ onDelete,
30
+ onAddModule,
31
+ onSelectModule,
32
+ }) => {
33
+ const [isAddModalOpen, setIsAddModalOpen] = useState(false);
34
+ const [editingModule, setEditingModule] = useState<ModuleItem | null>(null);
35
+ const [selectedModuleId, setSelectedModuleId] = useState<string | null>(null);
36
+
37
+ // nouvel état pour "vue compacte"
38
+ const [isCollapsed, setIsCollapsed] = useState(false);
39
+
40
+ // --- ADD ---
41
+ const handleAddClick = () => setIsAddModalOpen(true);
42
+ const handleCloseAddModal = () => setIsAddModalOpen(false);
43
+ const handleSaveAdd = (values: Record<string, string | number>) => {
44
+ if (onAddModule) {
45
+ const maxId = modules.reduce((max, m) => Math.max(max, parseInt(m.id, 10)), 0);
46
+ const newModule: ModuleItem = {
47
+ id: (maxId + 1).toString(),
48
+ title: values.title as string,
49
+ subtitle: values.subtitle as string,
50
+ translations: {},
51
+ };
52
+ onAddModule(newModule);
53
+ }
54
+ handleCloseAddModal();
55
+ };
56
+
57
+ // --- EDIT ---
58
+ const handleEditClick = (mod: ModuleItem) => setEditingModule(mod);
59
+ const handleCloseEditModal = () => setEditingModule(null);
60
+ const handleSaveEdit = (values: Record<string, string | number>) => {
61
+ if (editingModule && onEdit) {
62
+ const updatedModule: ModuleItem = {
63
+ ...editingModule,
64
+ title: values.title as string,
65
+ subtitle: values.subtitle as string,
66
+ };
67
+ onEdit(updatedModule);
68
+ }
69
+ handleCloseEditModal();
70
+ };
71
+
72
+ // --- SELECT ---
73
+ const handleSelect = (mod: ModuleItem) => {
74
+ setSelectedModuleId(mod.id);
75
+ if (onSelectModule) onSelectModule(mod);
76
+ };
77
+
78
+ if (isCollapsed) {
79
+ return (
80
+ <div className='module-sidebar collapsed'>
81
+ <Menu size={20} className='menu-toggle' onClick={() => setIsCollapsed(false)} />
82
+ </div>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <div className='module-sidebar'>
88
+ {/* Header */}
89
+ <div className='sidebar-header'>
90
+ <span className='header-text'>Modules ({modules.length})</span>
91
+ <div className='header-actions'>
92
+ <FolderPlus size={18} className='header-folder' onClick={handleAddClick} />
93
+ <Menu size={18} className='header-menu' onClick={() => setIsCollapsed(true)} />
94
+ </div>
95
+ </div>
96
+ <hr className='divider' />
97
+
98
+ {/* Liste des modules */}
99
+ <div className='module-list'>
100
+ {modules.map((mod) => (
101
+ <div
102
+ key={mod.id}
103
+ className={`module-item ${mod.id === selectedModuleId ? 'selected' : ''}`}
104
+ onClick={() => handleSelect(mod)}
105
+ >
106
+ <div className='module-left'>
107
+ <Folder size={16} className='item-folder' />
108
+ <div className='module-info'>
109
+ <div className='module-title'>
110
+ {mod.title} <span className='count'>{Object.keys(mod.translations).length}</span>
111
+ </div>
112
+ <div className='module-subtitle'>{mod.subtitle}</div>
113
+ </div>
114
+ </div>
115
+
116
+ <div className='module-actions'>
117
+ {onEdit && <Pencil onClick={() => handleEditClick(mod)} color='#777' size={16} />}
118
+ {onDelete && <Trash2 onClick={() => onDelete(mod.id)} color='#777' size={16} />}
119
+ </div>
120
+ </div>
121
+ ))}
122
+ </div>
123
+
124
+ {/* Modal d'ajout */}
125
+ <AddItemModal
126
+ isOpen={isAddModalOpen}
127
+ onClose={handleCloseAddModal}
128
+ onSave={handleSaveAdd}
129
+ title='Ajouter un module'
130
+ fields={[
131
+ { name: 'title', label: 'Titre', placeholder: 'Titre du module' },
132
+ { name: 'subtitle', label: 'Sous-titre', placeholder: 'Sous-titre' },
133
+ ]}
134
+ />
135
+
136
+ {/* Modal d'édition */}
137
+ {editingModule && (
138
+ <AddItemModal
139
+ isOpen={!!editingModule}
140
+ onClose={handleCloseEditModal}
141
+ onSave={handleSaveEdit}
142
+ title='Éditer le module'
143
+ initialValues={{
144
+ title: editingModule.title,
145
+ subtitle: editingModule.subtitle,
146
+ }}
147
+ fields={[
148
+ { name: 'title', label: 'Titre', placeholder: 'Titre du module' },
149
+ { name: 'subtitle', label: 'Sous-titre', placeholder: 'Sous-titre' },
150
+ ]}
151
+ />
152
+ )}
153
+ </div>
154
+ );
155
+ };
@@ -0,0 +1 @@
1
+ export * from './ModuleSideBar';
@@ -0,0 +1,58 @@
1
+ .navbar {
2
+ display: flex;
3
+ justify-content: space-between;
4
+ gap: 1rem;
5
+ }
6
+
7
+ .navbar-mobile {
8
+ position: relative;
9
+ }
10
+
11
+ .navbar-dropdown-toggle {
12
+ display: flex;
13
+ justify-content: space-between;
14
+ align-items: center;
15
+ padding: 0.75rem 1rem;
16
+ border-radius: 8px;
17
+ cursor: pointer;
18
+ transition: all 0.3s ease;
19
+ border: none;
20
+ }
21
+
22
+ .dropdown-arrow {
23
+ width: 0;
24
+ height: 0;
25
+ border-left: 5px solid transparent;
26
+ border-right: 5px solid transparent;
27
+ border-top: 5px solid currentColor;
28
+ transition: transform 0.3s ease;
29
+ }
30
+
31
+ .dropdown-arrow.open {
32
+ transform: rotate(180deg);
33
+ }
34
+
35
+ .navbar-dropdown {
36
+ position: absolute;
37
+ top: 100%;
38
+ left: 0;
39
+ background-color: white;
40
+ border: 1px solid #ddd;
41
+ border-top: none;
42
+ border-radius: 8px;
43
+ z-index: 1000;
44
+ overflow: hidden;
45
+ }
46
+
47
+ .navbar-dropdown .nav-item {
48
+ display: block;
49
+ width: 100%;
50
+ padding: 0.75rem 1rem;
51
+ border-bottom: 1px solid #eee;
52
+ box-sizing: border-box;
53
+ border-radius: 0;
54
+ }
55
+
56
+ .navbar-dropdown .nav-item:last-child {
57
+ border-bottom: none;
58
+ }