@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,218 @@
1
+ .wrapper {
2
+ width: 100%;
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: space-between;
6
+ gap: 1rem;
7
+ padding: 12px 18px;
8
+ box-sizing: border-box;
9
+ position: relative;
10
+ }
11
+
12
+ .leftGroup {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 12px;
16
+ min-width: 0;
17
+ position: relative;
18
+ flex: 1 1 auto;
19
+ }
20
+
21
+ .filterButton {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: 8px;
25
+ padding: 8px 12px;
26
+ border-radius: 999px;
27
+ border: 1px solid rgba(16, 24, 40, 0.08);
28
+ cursor: pointer;
29
+ font-size: 14px;
30
+ white-space: nowrap;
31
+ box-shadow: 0 2px 6px rgba(2, 6, 23, 0.03);
32
+ transition:
33
+ box-shadow 160ms ease,
34
+ transform 120ms ease;
35
+ }
36
+
37
+ .filterButton:focus {
38
+ outline: 3px solid rgba(59, 130, 246, 0.14);
39
+ outline-offset: 2px;
40
+ }
41
+
42
+ .filterButton.open .filterIcon,
43
+ .filterButton.open .filterIcon path {
44
+ transform-origin: 50% 50%;
45
+ }
46
+
47
+ .filterButton.open .filterIcon {
48
+ transform: rotate(180deg);
49
+ transition: transform 180ms ease;
50
+ }
51
+
52
+ .filterIcon {
53
+ opacity: 0.8;
54
+ width: 14px;
55
+ height: 14px;
56
+ }
57
+
58
+ .infoText {
59
+ font-size: 13px;
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+ max-width: 28ch;
64
+ }
65
+
66
+ .pickerContainer {
67
+ position: static;
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 8px;
71
+ margin-left: 8px;
72
+ z-index: 60;
73
+ width: auto;
74
+ box-sizing: border-box;
75
+ padding: 0;
76
+ background: transparent;
77
+ }
78
+
79
+ .srOnly {
80
+ position: absolute;
81
+ width: 1px;
82
+ height: 1px;
83
+ padding: 0;
84
+ margin: -1px;
85
+ overflow: hidden;
86
+ clip: rect(0, 0, 0, 0);
87
+ white-space: nowrap;
88
+ border: 0;
89
+ }
90
+
91
+ .pickerLabel {
92
+ font-size: 12px;
93
+ margin: 0;
94
+ display: block;
95
+ white-space: nowrap;
96
+ }
97
+
98
+ .datetime {
99
+ width: 220px;
100
+ padding: 8px 10px;
101
+ border-radius: 6px;
102
+ border: 1px solid rgba(16, 24, 40, 0.08);
103
+ font-size: 14px;
104
+ box-sizing: border-box;
105
+ }
106
+
107
+ .rightGroup {
108
+ margin-left: 12px;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: flex-end;
112
+ flex: 0 0 auto;
113
+ min-width: 0;
114
+ }
115
+
116
+ .searchInputWrap {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 8px;
120
+ border-radius: 10px;
121
+ padding: 6px 10px;
122
+ border: 1px solid rgba(16, 24, 40, 0.06);
123
+ min-width: 180px;
124
+ max-width: 420px;
125
+ width: 100%;
126
+ box-sizing: border-box;
127
+ }
128
+
129
+ .searchInput {
130
+ border: none;
131
+ outline: none;
132
+ font-size: 14px;
133
+ width: 100%;
134
+ background: transparent;
135
+ padding: 6px 0;
136
+ transition:
137
+ padding 160ms ease,
138
+ background-color 160ms ease;
139
+ box-sizing: border-box;
140
+ }
141
+
142
+ .searchInput::placeholder {
143
+ color: #9aa4b2;
144
+ }
145
+
146
+ .searchInput:focus,
147
+ .searchInput:not(:placeholder-shown) {
148
+ padding-left: 8px;
149
+ padding-right: 8px;
150
+ }
151
+
152
+ .searchIcon {
153
+ opacity: 0.6;
154
+ width: 16px;
155
+ height: 16px;
156
+ flex: 0 0 auto;
157
+ }
158
+
159
+ @media (max-width: 760px) {
160
+ .wrapper {
161
+ flex-direction: column;
162
+ align-items: stretch;
163
+ gap: 10px;
164
+ padding: 10px;
165
+ }
166
+
167
+ .leftGroup {
168
+ width: 100%;
169
+ justify-content: space-between;
170
+ gap: 8px;
171
+ }
172
+
173
+ .infoText {
174
+ display: none;
175
+ }
176
+
177
+ .rightGroup {
178
+ width: 100%;
179
+ margin-left: 0;
180
+ justify-content: flex-start;
181
+ }
182
+
183
+ .searchInputWrap {
184
+ max-width: 100%;
185
+ }
186
+
187
+ .pickerContainer {
188
+ position: absolute;
189
+ top: calc(100% + 10px);
190
+ left: 0;
191
+ right: 0;
192
+ width: auto;
193
+ max-width: 100%;
194
+ border: 1px solid rgba(16, 24, 40, 0.06);
195
+ border-radius: 8px;
196
+ padding: 10px;
197
+ box-shadow: 0 10px 30px rgba(2, 6, 23, 0.08);
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 8px;
201
+ }
202
+
203
+ .datetime {
204
+ width: 100%;
205
+ }
206
+ }
207
+
208
+ @media (max-width: 420px) {
209
+ .filterButton {
210
+ padding: 6px 10px;
211
+ font-size: 13px;
212
+ }
213
+
214
+ .searchInputWrap {
215
+ padding: 6px 8px;
216
+ min-width: 140px;
217
+ }
218
+ }
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import EntryControl, { type EntryControlProps } from './EntryControl';
4
+
5
+ const meta: Meta<typeof EntryControl> = {
6
+ title: 'Components/EntryControl',
7
+ component: EntryControl,
8
+ tags: ['autodocs'],
9
+ args: {
10
+ initialDate: undefined,
11
+ },
12
+ parameters: {
13
+ docs: {
14
+ source: {
15
+ language: 'tsx',
16
+ },
17
+ },
18
+ },
19
+ };
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof EntryControl>;
23
+
24
+ export const Default: Story = {
25
+ render: (args: EntryControlProps) => (
26
+ <div style={{ padding: 24, background: '#f8fafc' }}>
27
+ <EntryControl
28
+ {...args}
29
+ onDateChange={(d) => console.log('selected datetime:', d)}
30
+ onSearch={(q) => console.log('search:', q)}
31
+ />
32
+ </div>
33
+ ),
34
+ };
35
+
36
+ export const WithInitialDate: Story = {
37
+ args: {
38
+ initialDate: new Date().toISOString().slice(0, 16),
39
+ },
40
+ render: (args: EntryControlProps) => (
41
+ <div style={{ padding: 24 }}>
42
+ <EntryControl
43
+ {...args}
44
+ onDateChange={(d) => console.log('selected datetime:', d)}
45
+ onSearch={(q) => console.log('search:', q)}
46
+ />
47
+ </div>
48
+ ),
49
+ };
50
+
51
+ export const Controlled: Story = {
52
+ render: (args: EntryControlProps) => {
53
+ const [value, setValue] = React.useState<string | undefined>(args.initialDate);
54
+ return (
55
+ <div style={{ padding: 24 }}>
56
+ <EntryControl
57
+ {...args}
58
+ initialDate={value}
59
+ onDateChange={(d) => {
60
+ setValue(d);
61
+ console.log('controlled selected datetime:', d);
62
+ }}
63
+ onSearch={(q) => console.log('search:', q)}
64
+ />
65
+ <div style={{ marginTop: 12, fontSize: 13, color: '#6b7280' }}>
66
+ Valeur contrôlée: {value ?? '—'}
67
+ </div>
68
+ </div>
69
+ );
70
+ },
71
+ };
@@ -0,0 +1,117 @@
1
+ 'use client';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import styles from './EntryControl.module.css';
4
+
5
+ export interface EntryControlProps {
6
+ onDateChange?: (isoString: string) => void;
7
+ onSearch?: (query: string) => void;
8
+ initialDate?: string;
9
+ }
10
+
11
+ export default function EntryControl({ onDateChange, onSearch, initialDate }: EntryControlProps) {
12
+ const [showPicker, setShowPicker] = useState<boolean>(false);
13
+ const [datetime, setDatetime] = useState<string>(initialDate ?? '');
14
+ const containerRef = useRef<HTMLDivElement | null>(null);
15
+
16
+ useEffect(() => {
17
+ function handleClickOutside(e: MouseEvent) {
18
+ const target = e.target as Node | null;
19
+ if (containerRef.current && target && !containerRef.current.contains(target)) {
20
+ setShowPicker(false);
21
+ }
22
+ }
23
+
24
+ document.addEventListener('mousedown', handleClickOutside);
25
+ return () => document.removeEventListener('mousedown', handleClickOutside);
26
+ }, []);
27
+
28
+ useEffect(() => {
29
+ if (initialDate) setDatetime(initialDate);
30
+ }, [initialDate]);
31
+
32
+ const handleToggle = () => setShowPicker((v) => !v);
33
+
34
+ const handleDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
35
+ const v = e.target.value;
36
+ setDatetime(v);
37
+ onDateChange?.(v);
38
+ };
39
+
40
+ const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
41
+ onSearch?.(e.target.value);
42
+ };
43
+
44
+ return (
45
+ <div className={styles.wrapper} ref={containerRef}>
46
+ <div className={styles.leftGroup}>
47
+ <button
48
+ type='button'
49
+ className={`${styles.filterButton} ${showPicker ? styles.open : ''}`}
50
+ onClick={handleToggle}
51
+ aria-expanded={showPicker}
52
+ aria-haspopup='dialog'
53
+ aria-controls='entry-datetime'
54
+ >
55
+ <span>Entry time</span>
56
+ <svg
57
+ className={styles.filterIcon}
58
+ xmlns='http://www.w3.org/2000/svg'
59
+ viewBox='0 0 24 24'
60
+ width='14'
61
+ height='14'
62
+ aria-hidden
63
+ >
64
+ <path fill='currentColor' d='M10 18h4v-2h-4v2zm-7-8v2h18V10H3zm3-6v2h12V4H6z' />
65
+ </svg>
66
+ </button>
67
+
68
+ <div className={styles.infoText}>Confidence interval: step ± 15' / exit ± 30'</div>
69
+
70
+ {/* pickerContainer est maintenant inline sur grands écrans, absolute sur petits */}
71
+ {showPicker && (
72
+ <div
73
+ className={styles.pickerContainer}
74
+ role='dialog'
75
+ aria-label='Choisir la date et l’heure'
76
+ >
77
+ <label className={styles.pickerLabel} htmlFor='entry-datetime'>
78
+ <span className={styles.srOnly}>Choisir la date et l’heure</span>
79
+ </label>
80
+ <input
81
+ id='entry-datetime'
82
+ className={styles.datetime}
83
+ type='datetime-local'
84
+ value={datetime}
85
+ onChange={handleDateChange}
86
+ aria-label='Date et heure d’entrée'
87
+ />
88
+ </div>
89
+ )}
90
+ </div>
91
+
92
+ <div className={styles.rightGroup}>
93
+ <div className={styles.searchInputWrap}>
94
+ <input
95
+ className={styles.searchInput}
96
+ placeholder='Search...'
97
+ onChange={handleSearch}
98
+ aria-label='Search'
99
+ />
100
+ <svg
101
+ className={styles.searchIcon}
102
+ xmlns='http://www.w3.org/2000/svg'
103
+ viewBox='0 0 24 24'
104
+ width='16'
105
+ height='16'
106
+ aria-hidden
107
+ >
108
+ <path
109
+ fill='currentColor'
110
+ d='M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16a6.471 6.471 0 004.23-1.57l.27.28v.79L20 20.49 21.49 19l-5.99-5zM10 15.5A5.5 5.5 0 1115.5 10 5.5 5.5 0 0110 15.5z'
111
+ />
112
+ </svg>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ );
117
+ }
@@ -0,0 +1,2 @@
1
+ export { default as EntryControl } from './EntryControl';
2
+ export type { EntryControlProps } from './EntryControl';
@@ -0,0 +1,94 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import Grid from './Grid';
3
+
4
+ const meta: Meta<typeof Grid> = {
5
+ title: 'Components/Grid',
6
+ component: Grid,
7
+ tags: ['autodocs'],
8
+ };
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof Grid>;
12
+
13
+ /**
14
+ * Default example with 3 responsive blocks
15
+ */
16
+ export const BasicLayout: Story = {
17
+ render: () => (
18
+ <Grid parent container spacing={2} uniqueKey={1}>
19
+ <Grid item xs={12} uniqueKey={2}>
20
+ <div style={{ background: '#90caf9', padding: '1rem', textAlign: 'center' }}>Block 1</div>
21
+ </Grid>
22
+ <Grid item xs={12} uniqueKey={3}>
23
+ <div style={{ background: '#f48fb1', padding: '1rem', textAlign: 'center' }}>Block 2</div>
24
+ </Grid>
25
+ <Grid item xs={12} uniqueKey={4}>
26
+ <div style={{ background: '#a5d6a7', padding: '1rem', textAlign: 'center' }}>Block 3</div>
27
+ </Grid>
28
+ </Grid>
29
+ ),
30
+ };
31
+
32
+ /**
33
+ * Example with 4 equal columns
34
+ */
35
+ export const FourColumns: Story = {
36
+ render: () => (
37
+ <Grid parent container spacing={2} uniqueKey={1}>
38
+ {['#ffcc80', '#ce93d8', '#80cbc4', '#ef9a9a'].map((color, i) => (
39
+ <Grid key={i} item xs={12} sm={6} md={6} uniqueKey={i + 1}>
40
+ <div style={{ background: color, padding: '1rem', textAlign: 'center' }}>
41
+ Column {i + 1}
42
+ </div>
43
+ </Grid>
44
+ ))}
45
+ </Grid>
46
+ ),
47
+ };
48
+
49
+ /**
50
+ * Example with drag & drop enabled
51
+ */
52
+ export const DraggableItems: Story = {
53
+ render: () => (
54
+ <Grid parent container spacing={2} uniqueKey={1}>
55
+ {['Drag me 1', 'Drag me 2', 'Drag me 3'].map((text, i) => (
56
+ <Grid key={i} item xs={12} sm={12} md={12} lg={12} xl={12} uniqueKey={i + 1}>
57
+ <div
58
+ style={{
59
+ background: '#ffe082',
60
+ padding: '1rem',
61
+ textAlign: 'center',
62
+ border: '1px solid #999',
63
+ }}
64
+ >
65
+ {text}
66
+ </div>
67
+ </Grid>
68
+ ))}
69
+ </Grid>
70
+ ),
71
+ };
72
+
73
+ /**
74
+ * Nested grids (grid inside a grid item)
75
+ */
76
+ export const NestedGrid: Story = {
77
+ render: () => (
78
+ <Grid parent container spacing={2} uniqueKey={1}>
79
+ <Grid item xs={12} uniqueKey={2}>
80
+ <div style={{ background: '#b0bec5', padding: '1rem', textAlign: 'center' }}>
81
+ Parent Block
82
+ <Grid container spacing={1} style={{ marginTop: '1rem' }} uniqueKey={3}>
83
+ <Grid item xs={12} uniqueKey={4}>
84
+ <div style={{ background: '#ffab91', padding: '0.5rem' }}>Child 1</div>
85
+ </Grid>
86
+ <Grid item xs={12} uniqueKey={5}>
87
+ <div style={{ background: '#80deea', padding: '0.5rem' }}>Child 2</div>
88
+ </Grid>
89
+ </Grid>
90
+ </div>
91
+ </Grid>
92
+ </Grid>
93
+ ),
94
+ };
@@ -0,0 +1,214 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import './grid.css';
3
+
4
+ export interface GridProps {
5
+ children?: React.ReactNode;
6
+ container?: boolean;
7
+ item?: boolean;
8
+ parent?: boolean;
9
+ xs?: number;
10
+ sm?: number;
11
+ md?: number;
12
+ lg?: number;
13
+ xl?: number;
14
+ spacing?: number;
15
+ className?: string;
16
+ uniqueKey?: number;
17
+ style?: React.CSSProperties;
18
+ width?: number;
19
+ order?: number[];
20
+ onSwap?: (dragKey: number, dropKey: number) => void;
21
+ draggable?: boolean;
22
+ }
23
+
24
+ type ItemSlot = {
25
+ node: React.ReactNode;
26
+ classes: string;
27
+ };
28
+
29
+ const Grid: React.FC<GridProps> = ({
30
+ children,
31
+ container = false,
32
+ item = false,
33
+ parent = false,
34
+ xs,
35
+ sm,
36
+ md,
37
+ lg,
38
+ xl,
39
+ spacing = 0,
40
+ className = '',
41
+ uniqueKey = 0,
42
+ style = {},
43
+ width,
44
+ order,
45
+ onSwap,
46
+ draggable = false,
47
+ }) => {
48
+ const [items, setItems] = useState<ItemSlot[]>([]);
49
+
50
+ useEffect(() => {
51
+ const flattenChildren = (children: React.ReactNode): React.ReactElement[] => {
52
+ const result: React.ReactElement[] = [];
53
+ React.Children.forEach(children, (child) => {
54
+ if (Array.isArray(child)) {
55
+ result.push(...flattenChildren(child));
56
+ } else if (React.isValidElement(child)) {
57
+ result.push(child);
58
+ }
59
+ });
60
+ return result;
61
+ };
62
+
63
+ const childrenArray = flattenChildren(children);
64
+
65
+ const mapped: ItemSlot[] = childrenArray
66
+ .filter((child) => React.isValidElement(child))
67
+ .map((child) => {
68
+ let classes = '';
69
+ const childProps = child.props as GridProps;
70
+ classes = [
71
+ childProps.xs ? `grid-xs-${childProps.xs}` : '',
72
+ childProps.sm ? `grid-sm-${childProps.sm}` : '',
73
+ childProps.md ? `grid-md-${childProps.md}` : '',
74
+ childProps.lg ? `grid-lg-${childProps.lg}` : '',
75
+ childProps.xl ? `grid-xl-${childProps.xl}` : '',
76
+ childProps.className ? childProps.className : '',
77
+ ]
78
+ .join(' ')
79
+ .trim();
80
+ return { node: child, classes };
81
+ });
82
+
83
+ let ordered = mapped;
84
+ if (order && order.length > 0) {
85
+ ordered = mapped.map((slot, idx) => ({
86
+ ...slot,
87
+ node: mapped[order[idx]]?.node ?? slot.node,
88
+ }));
89
+ }
90
+
91
+ setItems(ordered);
92
+ }, [children]);
93
+
94
+ const handleDragStart = (e: React.DragEvent<HTMLDivElement>, index: number) => {
95
+ e.stopPropagation();
96
+ e.dataTransfer.setData('dragIndex', index.toString());
97
+ };
98
+
99
+ const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
100
+ e.preventDefault();
101
+ e.stopPropagation();
102
+ };
103
+
104
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>, dropIndex: number) => {
105
+ e.preventDefault();
106
+ const dragIndex = Number(e.dataTransfer.getData('dragIndex'));
107
+ const updatedItems = [...items];
108
+
109
+ let dropInd = -1;
110
+ let dragInd = -1;
111
+
112
+ updatedItems.forEach((item, index) => {
113
+ if (React.isValidElement<GridProps>(item.node)) {
114
+ const childProps: GridProps = item.node.props;
115
+ if (childProps.uniqueKey === dropIndex) {
116
+ dropInd = index;
117
+ }
118
+ if (childProps.uniqueKey === dragIndex) {
119
+ dragInd = index;
120
+ }
121
+ }
122
+ });
123
+
124
+ if (dropInd === -1 || dragInd === -1) {
125
+ return;
126
+ }
127
+ const tempNode = updatedItems[dragInd].node;
128
+ updatedItems[dragInd] = {
129
+ ...updatedItems[dragInd],
130
+ node: updatedItems[dropInd].node,
131
+ };
132
+ updatedItems[dropInd] = {
133
+ ...updatedItems[dropInd],
134
+ node: tempNode,
135
+ };
136
+
137
+ setItems(updatedItems);
138
+
139
+ if (typeof onSwap === 'function') {
140
+ onSwap(dragInd, dropInd);
141
+ }
142
+ };
143
+
144
+ const sizeClasses = parent
145
+ ? [
146
+ xs ? `grid-xs-${xs}` : '',
147
+ sm ? `grid-sm-${sm}` : '',
148
+ md ? `grid-md-${md}` : '',
149
+ lg ? `grid-lg-${lg}` : '',
150
+ xl ? `grid-xl-${xl}` : '',
151
+ ].join(' ')
152
+ : '';
153
+
154
+ if (!parent && width) {
155
+ style = { ...style, width: `${width}px` };
156
+ } else if (!parent && !width) {
157
+ style = { ...style, width: '100%' };
158
+ }
159
+ style = { ...style, height: '100%' };
160
+
161
+ if (container) {
162
+ return (
163
+ <div
164
+ className={`${className} grid-container ${sizeClasses}`}
165
+ style={
166
+ {
167
+ '--gap': `${spacing}rem`,
168
+ ...style,
169
+ } as React.CSSProperties
170
+ }
171
+ >
172
+ {items.map((item: any) =>
173
+ item.node.type?.displayName === 'Grid' ? (
174
+ draggable ? (
175
+ <div
176
+ key={item.node?.props?.uniqueKey}
177
+ draggable
178
+ onDragStart={(e) => handleDragStart(e, item.node?.props?.uniqueKey)}
179
+ onDragOver={(e) => handleDragOver(e)}
180
+ onDrop={(e) => handleDrop(e, item.node?.props?.uniqueKey)}
181
+ className={`grid-item grid-item-grabbing ${item.classes}`}
182
+ >
183
+ {item.node}
184
+ </div>
185
+ ) : (
186
+ <div key={item.node?.props?.uniqueKey} className={`grid-item ${item.classes}`}>
187
+ {item.node}
188
+ </div>
189
+ )
190
+ ) : (
191
+ item.node
192
+ )
193
+ )}
194
+ </div>
195
+ );
196
+ }
197
+
198
+ if (item) {
199
+ return (
200
+ <div
201
+ className={`${className} grid-item ${sizeClasses}`}
202
+ style={{ gap: `${spacing}rem`, ...style }}
203
+ >
204
+ {children}
205
+ </div>
206
+ );
207
+ }
208
+
209
+ return <>{children}</>;
210
+ };
211
+
212
+ Grid.displayName = 'Grid';
213
+
214
+ export default Grid;