@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,243 @@
1
+ import React, {
2
+ forwardRef,
3
+ useImperativeHandle,
4
+ useRef,
5
+ useState,
6
+ useEffect,
7
+ type CSSProperties,
8
+ } from 'react';
9
+ import { X } from 'lucide-react';
10
+ import './SideComponent.css';
11
+
12
+ export type SideComponentRef = {
13
+ open: () => void;
14
+ close: () => void;
15
+ toggle: () => void;
16
+ };
17
+
18
+ export interface SideComponentProps {
19
+ title: string;
20
+ subtitle?: string;
21
+ size?: string | number;
22
+ beforeOpen?: () => void;
23
+ afterOpen?: () => void;
24
+ beforeClose?: () => void;
25
+ afterClose?: () => void;
26
+ style?: CSSProperties;
27
+ className?: string;
28
+ children?: React.ReactNode;
29
+ closeOnOverlayClick?: boolean;
30
+ showCloseButton?: boolean;
31
+ useFocusTrap?: boolean;
32
+ }
33
+
34
+ const TRANSITION_MS = 250; // must match CSS transition duration
35
+
36
+ export const SideComponent = forwardRef<SideComponentRef, SideComponentProps>(
37
+ (
38
+ {
39
+ title,
40
+ subtitle,
41
+ size = '420px',
42
+ beforeOpen,
43
+ afterOpen,
44
+ beforeClose,
45
+ afterClose,
46
+ style,
47
+ className = '',
48
+ children,
49
+ closeOnOverlayClick = true,
50
+ showCloseButton = true,
51
+ },
52
+ ref
53
+ ) => {
54
+ const [open, setOpen] = useState<boolean>(false);
55
+ const [interactive, setInteractive] = useState<boolean>(false);
56
+ const rootRef = useRef<HTMLDivElement | null>(null);
57
+ const panelRef = useRef<HTMLDivElement | null>(null);
58
+ const openerRef = useRef<HTMLElement | null>(null);
59
+ const fallbackTimer = useRef<number | null>(null);
60
+
61
+ useEffect(() => {
62
+ const root = rootRef.current;
63
+ if (!root) return;
64
+ const parent = root.parentElement;
65
+ if (!parent) return;
66
+
67
+ const computed = window.getComputedStyle(parent);
68
+ if (computed.position === 'static') {
69
+ parent.setAttribute('data-scp-prev-position', parent.style.position ?? '');
70
+ parent.style.position = 'relative';
71
+ parent.setAttribute('data-scp-position-changed', 'true');
72
+ }
73
+
74
+ return () => {
75
+ if (parent.getAttribute('data-scp-position-changed') === 'true') {
76
+ const prev = parent.getAttribute('data-scp-prev-position') ?? '';
77
+ parent.style.position = prev;
78
+ parent.removeAttribute('data-scp-position-changed');
79
+ parent.removeAttribute('data-scp-prev-position');
80
+ }
81
+ };
82
+ }, []);
83
+
84
+ useImperativeHandle(
85
+ ref,
86
+ () => ({
87
+ open: () => {
88
+ if (!open) {
89
+ beforeOpen?.();
90
+ setInteractive(false);
91
+ setOpen(true);
92
+ }
93
+ },
94
+ close: () => {
95
+ if (open) {
96
+ beforeClose?.();
97
+ setInteractive(false);
98
+ setOpen(false);
99
+ }
100
+ },
101
+ toggle: () => {
102
+ if (open) {
103
+ beforeClose?.();
104
+ setInteractive(false);
105
+ setOpen(false);
106
+ } else {
107
+ beforeOpen?.();
108
+ setInteractive(false);
109
+ setOpen(true);
110
+ }
111
+ },
112
+ }),
113
+ [open, beforeOpen, beforeClose]
114
+ );
115
+
116
+ useEffect(() => {
117
+ const node = panelRef.current;
118
+ if (fallbackTimer.current) {
119
+ window.clearTimeout(fallbackTimer.current);
120
+ fallbackTimer.current = null;
121
+ }
122
+
123
+ const handleTransitionEnd = (e: TransitionEvent) => {
124
+ if (e.target !== node) return;
125
+ if (e.propertyName !== 'transform' && e.propertyName !== 'opacity') return;
126
+
127
+ if (open) {
128
+ setInteractive(true);
129
+ afterOpen?.();
130
+ try {
131
+ node?.focus();
132
+ } catch {
133
+ // TODO: handle focus error
134
+ }
135
+ } else {
136
+ afterClose?.();
137
+ try {
138
+ openerRef.current?.focus();
139
+ } catch {
140
+ // TODO: handle focus error
141
+ }
142
+ }
143
+ };
144
+
145
+ if (node) {
146
+ node.addEventListener('transitionend', handleTransitionEnd);
147
+ fallbackTimer.current = window.setTimeout(() => {
148
+ if (open) {
149
+ setInteractive(true);
150
+ afterOpen?.();
151
+ try {
152
+ node?.focus();
153
+ } catch {
154
+ // TODO: handle focus error
155
+ }
156
+ } else {
157
+ afterClose?.();
158
+ try {
159
+ openerRef.current?.focus();
160
+ } catch {
161
+ // TODO: handle focus error
162
+ }
163
+ }
164
+ }, TRANSITION_MS + 60);
165
+ }
166
+
167
+ if (open) openerRef.current = document.activeElement as HTMLElement | null;
168
+
169
+ return () => {
170
+ if (node) node.removeEventListener('transitionend', handleTransitionEnd);
171
+ if (fallbackTimer.current) {
172
+ window.clearTimeout(fallbackTimer.current);
173
+ fallbackTimer.current = null;
174
+ }
175
+ };
176
+ }, [open, afterOpen, afterClose]);
177
+
178
+ useEffect(() => {
179
+ const onKey = (e: KeyboardEvent) => {
180
+ if (e.key === 'Escape' && open) {
181
+ beforeClose?.();
182
+ setInteractive(false);
183
+ setOpen(false);
184
+ }
185
+ };
186
+ window.addEventListener('keydown', onKey);
187
+ return () => window.removeEventListener('keydown', onKey);
188
+ }, [open, beforeClose]);
189
+
190
+ const onOverlayClick = () => {
191
+ if (!closeOnOverlayClick) return;
192
+ beforeClose?.();
193
+ setInteractive(false);
194
+ setOpen(false);
195
+ };
196
+
197
+ const cssSize = typeof size === 'number' ? `${size}px` : size;
198
+
199
+ return (
200
+ <div ref={rootRef} className={`scp-inline-root ${open ? 'open' : ''}`} aria-hidden={!open}>
201
+ <div
202
+ className={`scp-overlay ${open ? 'visible' : ''}`}
203
+ onMouseDown={onOverlayClick}
204
+ role='presentation'
205
+ />
206
+
207
+ <aside
208
+ className={`scp-panel ${open ? 'in' : 'out'} ${interactive ? 'interactive' : ''} ${className}`}
209
+ style={{ width: cssSize, ...style }}
210
+ ref={panelRef}
211
+ tabIndex={-1}
212
+ role='dialog'
213
+ aria-label={title}
214
+ >
215
+ <div className='scp-header'>
216
+ <div>
217
+ <h3 className='scp-title'>{title}</h3>
218
+ {subtitle && <div className='scp-subtitle'>{subtitle}</div>}
219
+ </div>
220
+
221
+ {showCloseButton && (
222
+ <button
223
+ className='scp-close'
224
+ aria-label='Close'
225
+ onClick={() => {
226
+ beforeClose?.();
227
+ setInteractive(false);
228
+ setOpen(false);
229
+ }}
230
+ >
231
+ <X size={18} />
232
+ </button>
233
+ )}
234
+ </div>
235
+
236
+ <div className='scp-body'>{children}</div>
237
+ </aside>
238
+ </div>
239
+ );
240
+ }
241
+ );
242
+
243
+ export default SideComponent;
@@ -0,0 +1,15 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ export function useBodyScrollLock(active: boolean) {
4
+ const prev = useRef<string>('');
5
+
6
+ useEffect(() => {
7
+ if (active) {
8
+ prev.current = document.body.style.overflow;
9
+ document.body.style.overflow = 'hidden';
10
+ return;
11
+ }
12
+ // restore
13
+ document.body.style.overflow = prev.current || '';
14
+ }, [active]);
15
+ }
@@ -0,0 +1,2 @@
1
+ export * from './SideComponent';
2
+ export * from './hooks/useBodyScrollLock';
@@ -0,0 +1,11 @@
1
+ export const PORTAL_ID = 'sidecomponent-portal-root';
2
+
3
+ export function ensurePortalRoot(): HTMLElement {
4
+ let root = document.getElementById(PORTAL_ID);
5
+ if (!root) {
6
+ root = document.createElement('div');
7
+ root.id = PORTAL_ID;
8
+ document.body.appendChild(root);
9
+ }
10
+ return root;
11
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { ScrollableHorizontale } from '../ScrollableHorizontale/ScrollableHorizontale';
3
+ import { IconText } from '../IconText/IconText';
4
+ import { useNavigation } from '../NavBar/NavContext';
5
+ import type { MenuCategory } from '../DropdownMenu';
6
+
7
+ export type SubNavItemType = {
8
+ id: string;
9
+ icon?: string;
10
+ label: string;
11
+ href?: string;
12
+ };
13
+ export type SubNavBarProps = {
14
+ subNavItems?: SubNavItemType[];
15
+ highlightColor?: string;
16
+ style?: React.CSSProperties;
17
+ item?: MenuCategory[];
18
+ };
19
+
20
+ export const SubNavBar: React.FC<SubNavBarProps> = ({
21
+ subNavItems,
22
+ highlightColor = '#1387C8',
23
+ style,
24
+ item,
25
+ }) => {
26
+ const { activeSubNav } = useNavigation();
27
+ return (
28
+ <ScrollableHorizontale style={style} item={item}>
29
+ {subNavItems?.map((item) => (
30
+ <IconText
31
+ key={item.id}
32
+ icon={item.icon}
33
+ text={item.label}
34
+ href={item.href}
35
+ color={item.id === activeSubNav ? highlightColor : ''}
36
+ active={item.id === activeSubNav}
37
+ />
38
+ ))}
39
+ </ScrollableHorizontale>
40
+ );
41
+ };
@@ -0,0 +1 @@
1
+ export * from './SubNavBar';
@@ -0,0 +1,65 @@
1
+ .switcher {
2
+ position: relative;
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: space-between;
6
+ width: calc(var(--size) * 2);
7
+ height: var(--size);
8
+ background-color: var(--bg);
9
+ border-radius: var(--size);
10
+ border: calc(var(--size) * 0.08) solid var(--border);
11
+ cursor: pointer;
12
+ transition: background-color 0.3s ease;
13
+ outline: none;
14
+ overflow: hidden;
15
+ }
16
+
17
+ .switcher:focus {
18
+ box-shadow: 0 0 0 calc(var(--size) * 0.15) rgba(59, 130, 246, 0.4);
19
+ }
20
+
21
+ .switcher-checked {
22
+ background-color: var(--active-bg);
23
+ }
24
+
25
+ .switcher-circle {
26
+ position: absolute;
27
+ top: calc(var(--size) * 0.085);
28
+ left: calc(var(--size) * 0.1);
29
+ width: calc(var(--size) * 0.7);
30
+ height: calc(var(--size) * 0.7);
31
+ background-color: var(--circle);
32
+ border-radius: 50%;
33
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
34
+ box-shadow: 0 calc(var(--size) * 0.08) calc(var(--size) * 0.25) rgba(0, 0, 0, 0.25);
35
+ }
36
+
37
+ .switcher-checked .switcher-circle {
38
+ transform: translateX(calc(var(--size)));
39
+ }
40
+
41
+ .switcher-icons {
42
+ position: absolute;
43
+ inset: 0;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ padding: 0 calc(var(--size) * 0.25);
48
+ pointer-events: none;
49
+ }
50
+
51
+ .switcher-icons svg:first-child {
52
+ color: var(--circle);
53
+ }
54
+
55
+ .switcher-icons svg:last-child {
56
+ color: var(--icon);
57
+ }
58
+
59
+ .switcher-checked .switcher-icons svg:first-child {
60
+ color: var(--circle);
61
+ }
62
+
63
+ .switcher-checked .switcher-icons svg:last-child {
64
+ color: var(--icon2);
65
+ }
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Switcher } from './Switcher';
4
+
5
+ // Définir le type des propriétés du Switcher
6
+ type SwitcherProps = React.ComponentProps<typeof Switcher>;
7
+
8
+ const meta: Meta<SwitcherProps> = {
9
+ title: 'Components/Switcher',
10
+ component: Switcher,
11
+ parameters: {
12
+ layout: 'centered',
13
+ },
14
+ tags: ['autodocs'],
15
+ argTypes: {
16
+ leftIcon: {
17
+ control: { type: 'select' },
18
+ options: ['Moon', 'ChevronLeft', 'ArrowLeft', 'Settings', 'Menu', 'Sun'],
19
+ description: "Nom de l'icône Lucide pour le côté gauche",
20
+ },
21
+ rightIcon: {
22
+ control: { type: 'select' },
23
+ options: ['Sun', 'ChevronRight', 'ArrowRight', 'Plus', 'Menu', 'Moon'],
24
+ description: "Nom de l'icône Lucide pour le côté droit",
25
+ },
26
+ size: {
27
+ control: { type: 'range', min: 16, max: 64, step: 4 },
28
+ description: 'Taille du cercle (px)',
29
+ },
30
+ colors: {
31
+ control: 'object',
32
+ description:
33
+ 'Objet de configuration des couleurs (bg, activeBg, border, circle, icon, icon2)',
34
+ },
35
+ checked: {
36
+ control: 'boolean',
37
+ description: 'État du switch (ON/OFF)',
38
+ },
39
+ onToggle: { action: 'toggled' },
40
+ className: {
41
+ control: 'text',
42
+ description: 'Classe CSS supplémentaire',
43
+ },
44
+ },
45
+ args: {
46
+ leftIcon: 'Moon',
47
+ rightIcon: 'Sun',
48
+ size: 32,
49
+ checked: false,
50
+ },
51
+ };
52
+
53
+ export default meta;
54
+ type Story = StoryObj<SwitcherProps>;
55
+
56
+ // Stories de base
57
+ export const Default: Story = {};
58
+
59
+ export const WithDifferentIcons: Story = {
60
+ args: {
61
+ leftIcon: 'Settings',
62
+ rightIcon: 'Menu',
63
+ },
64
+ };
65
+
66
+ export const LargeSize: Story = {
67
+ args: { size: 48 },
68
+ };
69
+
70
+ export const SmallSize: Story = {
71
+ args: { size: 20 },
72
+ };
73
+
74
+ // Stories pour différents styles de couleurs
75
+ export const PrimaryColors: Story = {
76
+ args: {
77
+ colors: {
78
+ bg: '#1f2937',
79
+ activeBg: '#3b82f6',
80
+ border: '#2563eb',
81
+ circle: '#fff',
82
+ icon: '#93c5fd',
83
+ icon2: '#dbeafe', // Nouvelle propriété icon2
84
+ },
85
+ },
86
+ };
87
+
88
+ export const SuccessColors: Story = {
89
+ args: {
90
+ colors: {
91
+ bg: '#064e3b',
92
+ activeBg: '#10b981',
93
+ border: '#047857',
94
+ circle: '#fff',
95
+ icon: '#a7f3d0',
96
+ icon2: '#d1fae5', // Nouvelle propriété icon2
97
+ },
98
+ },
99
+ };
100
+
101
+ export const WarningColors: Story = {
102
+ args: {
103
+ colors: {
104
+ bg: '#78350f',
105
+ activeBg: '#f59e0b',
106
+ border: '#d97706',
107
+ circle: '#fff',
108
+ icon: '#fde68a',
109
+ icon2: '#fef3c7', // Nouvelle propriété icon2
110
+ },
111
+ },
112
+ };
113
+
114
+ export const ErrorColors: Story = {
115
+ args: {
116
+ colors: {
117
+ bg: '#7f1d1d',
118
+ activeBg: '#ef4444',
119
+ border: '#b91c1c',
120
+ circle: '#fff',
121
+ icon: '#fecaca',
122
+ icon2: '#fee2e2', // Nouvelle propriété icon2
123
+ },
124
+ },
125
+ };
126
+
127
+ // Nouvelle story pour démontrer l'utilisation de icon2
128
+ export const WithDifferentIconColors: Story = {
129
+ args: {
130
+ colors: {
131
+ bg: '#e5e7eb',
132
+ activeBg: '#3b82f6',
133
+ border: '#ffffff',
134
+ circle: '#ffffff',
135
+ icon: '#3b82f6',
136
+ icon2: '#9ca3af',
137
+ },
138
+ },
139
+ };
140
+
141
+ // Story avec gestion d'état contrôlée
142
+ export const Controlled: Story = {
143
+ render: (args) => {
144
+ const [checked, setChecked] = React.useState(false);
145
+
146
+ return (
147
+ <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
148
+ <Switcher {...args} checked={checked} onToggle={setChecked} />
149
+ <span>{checked ? 'ON' : 'OFF'}</span>
150
+ </div>
151
+ );
152
+ },
153
+ };
@@ -0,0 +1,83 @@
1
+ import React, { useMemo, type KeyboardEvent } from 'react';
2
+ import * as LucideIcons from 'lucide-react';
3
+ import type { LucideProps } from 'lucide-react';
4
+ import './Switcher.css';
5
+
6
+ export interface SwitcherProps {
7
+ colors?: {
8
+ bg?: string;
9
+ activeBg?: string;
10
+ border?: string;
11
+ circle?: string;
12
+ icon?: string;
13
+ icon2?: string;
14
+ };
15
+ leftIcon?: string;
16
+ rightIcon?: string;
17
+ checked?: boolean;
18
+ onToggle?: (checked: boolean) => void;
19
+ size?: number;
20
+ className?: string;
21
+ style?: React.CSSProperties;
22
+ }
23
+
24
+ export const Switcher: React.FC<SwitcherProps> = ({
25
+ colors = {},
26
+ leftIcon,
27
+ rightIcon,
28
+ checked = false,
29
+ onToggle,
30
+ size = 24,
31
+ className = '',
32
+ style,
33
+ }) => {
34
+ const LeftIconComponent = useMemo(() => {
35
+ return leftIcon
36
+ ? (LucideIcons[leftIcon as keyof typeof LucideIcons] as React.ComponentType<LucideProps>)
37
+ : null;
38
+ }, [leftIcon]);
39
+
40
+ const RightIconComponent = useMemo(() => {
41
+ return rightIcon
42
+ ? (LucideIcons[rightIcon as keyof typeof LucideIcons] as React.ComponentType<LucideProps>)
43
+ : null;
44
+ }, [rightIcon]);
45
+
46
+ const toggle = () => onToggle?.(!checked);
47
+
48
+ const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
49
+ if (e.key === 'Enter' || e.key === ' ') {
50
+ e.preventDefault();
51
+ toggle();
52
+ }
53
+ };
54
+
55
+ return (
56
+ <div
57
+ role='switch'
58
+ aria-checked={checked}
59
+ tabIndex={0}
60
+ className={`switcher ${checked ? 'switcher-checked' : ''} ${className}`}
61
+ style={
62
+ {
63
+ '--bg': colors.bg || '#1f2937',
64
+ '--active-bg': colors.activeBg || '#facc15',
65
+ '--border': colors.border || '#fff',
66
+ '--circle': colors.circle || '#fff',
67
+ '--icon': colors.icon || '#fbbf24',
68
+ '--icon2': colors.icon2 || colors.icon || '#fbbf24',
69
+ '--size': `${size}px`,
70
+ ...style,
71
+ } as React.CSSProperties
72
+ }
73
+ onClick={toggle}
74
+ onKeyDown={handleKeyDown}
75
+ >
76
+ <div className='switcher-circle' />
77
+ <div className='switcher-icons'>
78
+ {LeftIconComponent && <LeftIconComponent size={size * 0.5} />}
79
+ {RightIconComponent && <RightIconComponent size={size * 0.5} />}
80
+ </div>
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1 @@
1
+ export * from './Switcher';
@@ -0,0 +1,18 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Title } from './Title';
3
+
4
+ const meta: Meta<typeof Title> = {
5
+ component: Title,
6
+ title: 'Components/Title',
7
+ };
8
+ export default meta;
9
+
10
+ type Story = StoryObj<typeof Title>;
11
+
12
+ export const Default: Story = {
13
+ args: {
14
+ title: 'Gestion des clés',
15
+ fontSize: '3rem',
16
+ barColor: '#e12d2d',
17
+ },
18
+ };
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ interface TitleProps {
4
+ title: string;
5
+ fontSize?: string;
6
+ barColor?: string;
7
+ }
8
+
9
+ export const Title: React.FC<TitleProps> = ({
10
+ title,
11
+ fontSize = '1.5rem',
12
+ barColor = '#1E90FF',
13
+ }) => {
14
+ return (
15
+ <div
16
+ style={{
17
+ borderLeft: `4px solid ${barColor}`,
18
+ paddingLeft: '12px',
19
+ marginBottom: '16px',
20
+ width: '100%',
21
+ }}
22
+ >
23
+ <h2 style={{ fontSize, wordWrap: 'break-word' }}>{title}</h2>
24
+ </div>
25
+ );
26
+ };
@@ -0,0 +1 @@
1
+ export * from './Title';