@myelmut/design-system 0.1.47 → 0.1.48

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 (313) hide show
  1. package/package.json +3 -2
  2. package/src/assets/fonts/PPMori-Regular.woff2 +0 -0
  3. package/src/assets/fonts/PPMori-RegularItalic.woff2 +0 -0
  4. package/src/assets/fonts/PPMori-SemiBold.woff2 +0 -0
  5. package/src/assets/fonts/PPPangaia-Medium.woff2 +0 -0
  6. package/src/assets/fonts/PPPangaia-MediumItalic.woff2 +0 -0
  7. package/src/assets/fonts/PPPangaia-Ultralight.woff2 +0 -0
  8. package/src/assets/illustrations/balls-light.webp +0 -0
  9. package/src/assets/illustrations/balls.webp +0 -0
  10. package/src/assets/illustrations/basket-light.webp +0 -0
  11. package/src/assets/illustrations/basket.webp +0 -0
  12. package/src/assets/illustrations/bowl-2-light.webp +0 -0
  13. package/src/assets/illustrations/bowl-2.webp +0 -0
  14. package/src/assets/illustrations/bowl-light.webp +0 -0
  15. package/src/assets/illustrations/bowl.webp +0 -0
  16. package/src/assets/illustrations/box-2-light.webp +0 -0
  17. package/src/assets/illustrations/box-2.webp +0 -0
  18. package/src/assets/illustrations/box-light.webp +0 -0
  19. package/src/assets/illustrations/box.webp +0 -0
  20. package/src/assets/illustrations/calendar-light.webp +0 -0
  21. package/src/assets/illustrations/calendar.webp +0 -0
  22. package/src/assets/illustrations/can-light.webp +0 -0
  23. package/src/assets/illustrations/can.webp +0 -0
  24. package/src/assets/illustrations/carrot-light.webp +0 -0
  25. package/src/assets/illustrations/carrot.webp +0 -0
  26. package/src/assets/illustrations/cat-light.webp +0 -0
  27. package/src/assets/illustrations/cat.webp +0 -0
  28. package/src/assets/illustrations/check-rounded.webp +0 -0
  29. package/src/assets/illustrations/chicken-light.webp +0 -0
  30. package/src/assets/illustrations/chicken.webp +0 -0
  31. package/src/assets/illustrations/cross-rounded.webp +0 -0
  32. package/src/assets/illustrations/crown-light.webp +0 -0
  33. package/src/assets/illustrations/crown.webp +0 -0
  34. package/src/assets/illustrations/dog-light.webp +0 -0
  35. package/src/assets/illustrations/dog.webp +0 -0
  36. package/src/assets/illustrations/face-light.webp +0 -0
  37. package/src/assets/illustrations/face.webp +0 -0
  38. package/src/assets/illustrations/food-bag-light.webp +0 -0
  39. package/src/assets/illustrations/food-bag.webp +0 -0
  40. package/src/assets/illustrations/france-light.webp +0 -0
  41. package/src/assets/illustrations/france.webp +0 -0
  42. package/src/assets/illustrations/fridge-light.webp +0 -0
  43. package/src/assets/illustrations/fridge.webp +0 -0
  44. package/src/assets/illustrations/glasses-light.webp +0 -0
  45. package/src/assets/illustrations/glasses.webp +0 -0
  46. package/src/assets/illustrations/half-label-light.webp +0 -0
  47. package/src/assets/illustrations/half-label.webp +0 -0
  48. package/src/assets/illustrations/kitten-light.webp +0 -0
  49. package/src/assets/illustrations/kitten.webp +0 -0
  50. package/src/assets/illustrations/label-light.webp +0 -0
  51. package/src/assets/illustrations/label.webp +0 -0
  52. package/src/assets/illustrations/leaf-light.webp +0 -0
  53. package/src/assets/illustrations/leaf.webp +0 -0
  54. package/src/assets/illustrations/liquid-light.webp +0 -0
  55. package/src/assets/illustrations/liquid.webp +0 -0
  56. package/src/assets/illustrations/magnifying-glass-light.webp +0 -0
  57. package/src/assets/illustrations/magnifying-glass.webp +0 -0
  58. package/src/assets/illustrations/meat-light.webp +0 -0
  59. package/src/assets/illustrations/meat.webp +0 -0
  60. package/src/assets/illustrations/molecule-light.webp +0 -0
  61. package/src/assets/illustrations/molecule.webp +0 -0
  62. package/src/assets/illustrations/paws-light.webp +0 -0
  63. package/src/assets/illustrations/paws.webp +0 -0
  64. package/src/assets/illustrations/plate-light.webp +0 -0
  65. package/src/assets/illustrations/plate.webp +0 -0
  66. package/src/assets/illustrations/pot-big-2-light.webp +0 -0
  67. package/src/assets/illustrations/pot-big-2.webp +0 -0
  68. package/src/assets/illustrations/pot-big-light.webp +0 -0
  69. package/src/assets/illustrations/pot-big.webp +0 -0
  70. package/src/assets/illustrations/pot-light.webp +0 -0
  71. package/src/assets/illustrations/pot.webp +0 -0
  72. package/src/assets/illustrations/puppy-light.webp +0 -0
  73. package/src/assets/illustrations/puppy.webp +0 -0
  74. package/src/assets/illustrations/quantity-light.webp +0 -0
  75. package/src/assets/illustrations/quantity.webp +0 -0
  76. package/src/assets/illustrations/sausage-light.webp +0 -0
  77. package/src/assets/illustrations/sausage.webp +0 -0
  78. package/src/assets/illustrations/sausages-light.webp +0 -0
  79. package/src/assets/illustrations/sausages.webp +0 -0
  80. package/src/assets/illustrations/skeleton-light.webp +0 -0
  81. package/src/assets/illustrations/skeleton.webp +0 -0
  82. package/src/assets/illustrations/sofa-light.webp +0 -0
  83. package/src/assets/illustrations/sofa.webp +0 -0
  84. package/src/assets/illustrations/sport-light.webp +0 -0
  85. package/src/assets/illustrations/sport.webp +0 -0
  86. package/src/assets/illustrations/steak-light.webp +0 -0
  87. package/src/assets/illustrations/steak.webp +0 -0
  88. package/src/assets/illustrations/truck-light.webp +0 -0
  89. package/src/assets/illustrations/truck.webp +0 -0
  90. package/src/assets/illustrations/warning-light.webp +0 -0
  91. package/src/assets/illustrations/warning.webp +0 -0
  92. package/src/assets/images/cat.webp +0 -0
  93. package/src/assets/images/dog.webp +0 -0
  94. package/src/assets/images/frequency/food-in-fridge.webp +0 -0
  95. package/src/assets/images/ingredients/beef.webp +0 -0
  96. package/src/assets/images/ingredients/beer-yeast.webp +0 -0
  97. package/src/assets/images/ingredients/calcium.webp +0 -0
  98. package/src/assets/images/ingredients/carrot.webp +0 -0
  99. package/src/assets/images/ingredients/chicken.webp +0 -0
  100. package/src/assets/images/ingredients/courgette.webp +0 -0
  101. package/src/assets/images/ingredients/dry-apple.webp +0 -0
  102. package/src/assets/images/ingredients/dry-carrot.webp +0 -0
  103. package/src/assets/images/ingredients/duck.webp +0 -0
  104. package/src/assets/images/ingredients/fish.webp +0 -0
  105. package/src/assets/images/ingredients/liquid.webp +0 -0
  106. package/src/assets/images/ingredients/oil.webp +0 -0
  107. package/src/assets/images/ingredients/pork.webp +0 -0
  108. package/src/assets/images/ingredients/potato.webp +0 -0
  109. package/src/assets/images/ingredients/quinoa.webp +0 -0
  110. package/src/assets/images/ingredients/rice.webp +0 -0
  111. package/src/assets/images/ingredients/seaweed.webp +0 -0
  112. package/src/assets/images/ingredients/turkey.webp +0 -0
  113. package/src/assets/images/ingredients/vitamins.webp +0 -0
  114. package/src/assets/images/tips/claudine-head.webp +0 -0
  115. package/src/assets/images/tips/claudine-tips-head-mobile.webp +0 -0
  116. package/src/assets/images/tips/claudine-tips-head-mobile@2x.webp +0 -0
  117. package/src/assets/images/tips/claudine-tips-head.webp +0 -0
  118. package/src/assets/images/tips/claudine-tips-head@2x.webp +0 -0
  119. package/src/assets/images/tips/claudine-tips-mobile.webp +0 -0
  120. package/src/assets/images/tips/claudine-tips-mobile@2x.webp +0 -0
  121. package/src/assets/images/tips/claudine-tips.webp +0 -0
  122. package/src/assets/images/tips/claudine-tips@2x.webp +0 -0
  123. package/src/assets/images/tips/payment-mobile.webp +0 -0
  124. package/src/assets/images/tips/payment-mobile@2x.webp +0 -0
  125. package/src/assets/images/tips/payment.webp +0 -0
  126. package/src/assets/images/tips/payment@2x.webp +0 -0
  127. package/src/assets/images/trash/dog-product-mobile.webp +0 -0
  128. package/src/assets/images/trash/dog-product.webp +0 -0
  129. package/src/assets/images/trash/full-cat.png +0 -0
  130. package/src/assets/images/trash/testimonial-1-mobile.webp +0 -0
  131. package/src/assets/images/trash/testimonial-1-mobile@2x.webp +0 -0
  132. package/src/assets/images/trash/testimonial-1.webp +0 -0
  133. package/src/assets/images/trash/testimonial-1@2x.webp +0 -0
  134. package/src/components/Accordions/FAQ/FaqItem.stories.tsx +61 -0
  135. package/src/components/Accordions/FAQ/FaqItem.tsx +55 -0
  136. package/src/components/Accordions/Ingredient/Ingredient.stories.tsx +38 -0
  137. package/src/components/Accordions/Ingredient/Ingredient.tsx +93 -0
  138. package/src/components/Accordions/index.tsx +4 -0
  139. package/src/components/Base/Banner/Banner.stories.tsx +33 -0
  140. package/src/components/Base/Banner/Banner.tsx +23 -0
  141. package/src/components/Base/Emblem/Emblem.stories.tsx +40 -0
  142. package/src/components/Base/Emblem/Emblem.tsx +22 -0
  143. package/src/components/Base/Logo/Logo.stories.tsx +46 -0
  144. package/src/components/Base/Logo/Logo.tsx +34 -0
  145. package/src/components/Base/ResponsiveImage/ResponsiveImage.stories.tsx +78 -0
  146. package/src/components/Base/ResponsiveImage/ResponsiveImage.tsx +56 -0
  147. package/src/components/Base/Text/Text.stories.tsx +115 -0
  148. package/src/components/Base/Text/Text.tsx +60 -0
  149. package/src/components/Base/Title/Title.stories.tsx +145 -0
  150. package/src/components/Base/Title/Title.tsx +77 -0
  151. package/src/components/Base/VideoPlayer/VideoPlayer.stories.tsx +60 -0
  152. package/src/components/Base/VideoPlayer/VideoPlayer.tsx +78 -0
  153. package/src/components/Base/index.tsx +9 -0
  154. package/src/components/Buttons/Button/Button.stories.tsx +158 -0
  155. package/src/components/Buttons/Button/Button.tsx +68 -0
  156. package/src/components/Buttons/CardButton/CardButton.stories.tsx +47 -0
  157. package/src/components/Buttons/CardButton/CardButton.tsx +25 -0
  158. package/src/components/Buttons/ClearButton/ClearButton.stories.tsx +26 -0
  159. package/src/components/Buttons/ClearButton/ClearButton.tsx +18 -0
  160. package/src/components/Buttons/FAQButton/FAQButton.stories.tsx +33 -0
  161. package/src/components/Buttons/FAQButton/FAQButton.tsx +27 -0
  162. package/src/components/Buttons/IllustratedCardButton/IllustratedCardButton.stories.tsx +71 -0
  163. package/src/components/Buttons/IllustratedCardButton/IllustratedCardButton.tsx +45 -0
  164. package/src/components/Buttons/SimpleIllustratedCardButton/SimpleIllustratedCardButton.stories.tsx +74 -0
  165. package/src/components/Buttons/SimpleIllustratedCardButton/SimpleIllustratedCardButton.tsx +43 -0
  166. package/src/components/Buttons/SocialButton/SocialButton.stories.tsx +56 -0
  167. package/src/components/Buttons/SocialButton/SocialButton.tsx +28 -0
  168. package/src/components/Buttons/Toggle/Toggle.tsx +64 -0
  169. package/src/components/Buttons/Toggle/Toogle.stories.tsx +66 -0
  170. package/src/components/Buttons/index.ts +10 -0
  171. package/src/components/Cards/CTACard/CTACard.stories.tsx +83 -0
  172. package/src/components/Cards/CTACard/CTACard.tsx +47 -0
  173. package/src/components/Cards/FeatureCard/FeatureCard.stories.tsx +96 -0
  174. package/src/components/Cards/FeatureCard/FeatureCard.tsx +50 -0
  175. package/src/components/Cards/FeatureIllustration/FeatureIllustration.stories.tsx +96 -0
  176. package/src/components/Cards/FeatureIllustration/FeatureIllustration.tsx +56 -0
  177. package/src/components/Cards/FeatureIllustration/index.ts +2 -0
  178. package/src/components/Cards/FoodCard/FoodCard.stories.tsx +43 -0
  179. package/src/components/Cards/FoodCard/FoodCard.tsx +37 -0
  180. package/src/components/Cards/FrequencySelectorCard/FrequencySelectorCard.stories.tsx +140 -0
  181. package/src/components/Cards/FrequencySelectorCard/FrequencySelectorCard.tsx +90 -0
  182. package/src/components/Cards/FrequencySelectorCard/index.ts +2 -0
  183. package/src/components/Cards/IllustratedCard/IllustratedCard.stories.tsx +54 -0
  184. package/src/components/Cards/IllustratedCard/IllustratedCard.tsx +44 -0
  185. package/src/components/Cards/PaymentCard/PaymentCard.stories.tsx +35 -0
  186. package/src/components/Cards/PaymentCard/PaymentCard.tsx +31 -0
  187. package/src/components/Cards/PlanCard/PlanCard.stories.tsx +140 -0
  188. package/src/components/Cards/PlanCard/PlanCard.tsx +119 -0
  189. package/src/components/Cards/Polaroid/Polaroid.stories.tsx +118 -0
  190. package/src/components/Cards/Polaroid/Polaroid.tsx +66 -0
  191. package/src/components/Cards/RecetteCard/RecetteCard.stories.tsx +86 -0
  192. package/src/components/Cards/RecetteCard/RecetteCard.tsx +47 -0
  193. package/src/components/Cards/StatCard/StatCard.stories.tsx +69 -0
  194. package/src/components/Cards/StatCard/StatCard.tsx +45 -0
  195. package/src/components/Cards/Testimonial/Testimonial.stories.tsx +65 -0
  196. package/src/components/Cards/Testimonial/Testimonial.tsx +62 -0
  197. package/src/components/Cards/TestimonialSlider/TestimonialSlider.stories.ts +53 -0
  198. package/src/components/Cards/TestimonialSlider/TestimonialSlider.tsx +50 -0
  199. package/src/components/Cards/Tips/Tips.stories.tsx +32 -0
  200. package/src/components/Cards/Tips/Tips.tsx +40 -0
  201. package/src/components/Cards/TransitionCard/TransitionCard.stories.tsx +50 -0
  202. package/src/components/Cards/TransitionCard/TransitionCard.tsx +66 -0
  203. package/src/components/Cards/UpCard/UpCard.stories.tsx +94 -0
  204. package/src/components/Cards/UpCard/UpCard.tsx +50 -0
  205. package/src/components/Cards/WizardTips/WizardTips.stories.tsx +48 -0
  206. package/src/components/Cards/WizardTips/WizardTips.tsx +33 -0
  207. package/src/components/Cards/index.ts +19 -0
  208. package/src/components/Inputs/ButtonSelect/ButtonSelect.stories.tsx +51 -0
  209. package/src/components/Inputs/ButtonSelect/ButtonSelect.tsx +34 -0
  210. package/src/components/Inputs/Checkbox/Checkbox.stories.tsx +47 -0
  211. package/src/components/Inputs/Checkbox/Checkbox.tsx +35 -0
  212. package/src/components/Inputs/Dropdown/Dropdown.stories.tsx +61 -0
  213. package/src/components/Inputs/Dropdown/Dropdown.tsx +108 -0
  214. package/src/components/Inputs/DropdownMenu/DropdownMenu.stories.tsx +75 -0
  215. package/src/components/Inputs/DropdownMenu/DropdownMenu.tsx +109 -0
  216. package/src/components/Inputs/ErrorMessage/ErrorMessage.stories.tsx +33 -0
  217. package/src/components/Inputs/ErrorMessage/ErrorMessage.tsx +18 -0
  218. package/src/components/Inputs/Filters/Filters.stories.tsx +54 -0
  219. package/src/components/Inputs/Filters/Filters.tsx +75 -0
  220. package/src/components/Inputs/Label/Label.stories.tsx +34 -0
  221. package/src/components/Inputs/Label/Label.tsx +16 -0
  222. package/src/components/Inputs/Newsletter/Newsletter.stories.tsx +67 -0
  223. package/src/components/Inputs/Newsletter/Newsletter.tsx +70 -0
  224. package/src/components/Inputs/QuantityInput/QuantityInput.stories.tsx +54 -0
  225. package/src/components/Inputs/QuantityInput/QuantityInput.tsx +46 -0
  226. package/src/components/Inputs/Tag/Tag.stories.tsx +33 -0
  227. package/src/components/Inputs/Tag/Tag.tsx +19 -0
  228. package/src/components/Inputs/TagSelect/TagSelect.stories.tsx +50 -0
  229. package/src/components/Inputs/TagSelect/TagSelect.tsx +48 -0
  230. package/src/components/Inputs/TextInput/TextInput.stories.tsx +40 -0
  231. package/src/components/Inputs/TextInput/TextInput.tsx +38 -0
  232. package/src/components/Inputs/WizardDropdown/WizardDropdown.stories.tsx +59 -0
  233. package/src/components/Inputs/WizardDropdown/WizardDropdown.tsx +93 -0
  234. package/src/components/Inputs/WizardTextInput/WizardTextInput.stories.tsx +40 -0
  235. package/src/components/Inputs/WizardTextInput/WizardTextInput.tsx +31 -0
  236. package/src/components/Inputs/index.ts +16 -0
  237. package/src/components/Navigation/Footer/Footer.stories.tsx +28 -0
  238. package/src/components/Navigation/Footer/Footer.tsx +130 -0
  239. package/src/components/Navigation/FooterTips/FooterTips.stories.tsx +22 -0
  240. package/src/components/Navigation/FooterTips/FooterTips.tsx +24 -0
  241. package/src/components/Navigation/MobileMenu/MobileMenu.stories.tsx +56 -0
  242. package/src/components/Navigation/MobileMenu/MobileMenu.tsx +45 -0
  243. package/src/components/Navigation/Navbar/Navbar.stories.tsx +128 -0
  244. package/src/components/Navigation/Navbar/Navbar.tsx +66 -0
  245. package/src/components/Navigation/NavbarDesktop/NavbarDesktop.stories.tsx +57 -0
  246. package/src/components/Navigation/NavbarDesktop/NavbarDesktop.tsx +48 -0
  247. package/src/components/Navigation/NavbarMobile/NavbarMobile.stories.tsx +59 -0
  248. package/src/components/Navigation/NavbarMobile/NavbarMobile.tsx +75 -0
  249. package/src/components/Navigation/Stepper/Stepper.stories.tsx +41 -0
  250. package/src/components/Navigation/Stepper/Stepper.tsx +19 -0
  251. package/src/components/Navigation/Tabs/Tabs.stories.tsx +120 -0
  252. package/src/components/Navigation/Tabs/Tabs.tsx +92 -0
  253. package/src/components/Navigation/WizardNavbar/WizardNavbar.stories.tsx +59 -0
  254. package/src/components/Navigation/WizardNavbar/WizardNavbar.tsx +83 -0
  255. package/src/components/Navigation/index.ts +11 -0
  256. package/src/components/SVG/Facebook.svg +3 -0
  257. package/src/components/SVG/Instagram.svg +5 -0
  258. package/src/components/SVG/Linkedin.svg +5 -0
  259. package/src/components/SVG/Tiktok.svg +7 -0
  260. package/src/components/SVG/arrow-plain.svg +3 -0
  261. package/src/components/SVG/arrow.svg +3 -0
  262. package/src/components/SVG/calendar.svg +4 -0
  263. package/src/components/SVG/check-circle.svg +5 -0
  264. package/src/components/SVG/check-rounded.svg +4 -0
  265. package/src/components/SVG/check.svg +3 -0
  266. package/src/components/SVG/cross-rounded.svg +3 -0
  267. package/src/components/SVG/cross.svg +4 -0
  268. package/src/components/SVG/dollar-rounded.svg +6 -0
  269. package/src/components/SVG/double-arrow.svg +3 -0
  270. package/src/components/SVG/filters.svg +8 -0
  271. package/src/components/SVG/hat-cook.svg +4 -0
  272. package/src/components/SVG/home-hero-shape-mobile.svg +3 -0
  273. package/src/components/SVG/home-hero-shape-small.svg +3 -0
  274. package/src/components/SVG/home-hero-shape.svg +3 -0
  275. package/src/components/SVG/index.ts +75 -0
  276. package/src/components/SVG/info.svg +3 -0
  277. package/src/components/SVG/magic-wand.svg +9 -0
  278. package/src/components/SVG/menu.svg +5 -0
  279. package/src/components/SVG/minus.svg +3 -0
  280. package/src/components/SVG/mute.svg +4 -0
  281. package/src/components/SVG/pause.svg +3 -0
  282. package/src/components/SVG/play.svg +3 -0
  283. package/src/components/SVG/plus.svg +4 -0
  284. package/src/components/SVG/polaroid-thread.svg +17 -0
  285. package/src/components/SVG/profil.svg +4 -0
  286. package/src/components/SVG/quote.svg +3 -0
  287. package/src/components/SVG/recipe-bg-shape.svg +3 -0
  288. package/src/components/SVG/star.svg +3 -0
  289. package/src/components/SVG/subtract.svg +3 -0
  290. package/src/components/SVG/trustpilot.svg +14 -0
  291. package/src/components/SVG/unmute.svg +5 -0
  292. package/src/components/index.ts +20 -0
  293. package/src/components/styles/Color/Color.stories.tsx +89 -0
  294. package/src/components/styles/Color/Color.tsx +47 -0
  295. package/src/components/styles/Icon/Icon.stories.tsx +103 -0
  296. package/src/components/styles/Icon/Icon.tsx +6 -0
  297. package/src/components/styles/Illustration/Illustration.stories.tsx +253 -0
  298. package/src/components/styles/Illustration/Illustration.tsx +45 -0
  299. package/src/components/styles/Typography/Typography.stories.tsx +37 -0
  300. package/src/components/styles/Typography/Typography.tsx +19 -0
  301. package/src/helpers/accordions.ts +39 -0
  302. package/src/helpers/clickoutside.ts +31 -0
  303. package/src/helpers/debounce.ts +10 -0
  304. package/src/helpers/index.ts +9 -0
  305. package/src/helpers/intersectionObserver.ts +101 -0
  306. package/src/helpers/keyboardControl.ts +58 -0
  307. package/src/helpers/responsive.ts +29 -0
  308. package/src/helpers/scroll.ts +16 -0
  309. package/src/index.ts +8 -0
  310. package/src/lib/i18n.ts +20 -0
  311. package/src/lib/locales/fr/design.json +42 -0
  312. package/src/styles/globals.css +205 -0
  313. package/src/types/svg.d.ts +8 -0
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+
5
+ import clsx from 'clsx';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { Checkbox } from '../../../components';
9
+ import { onKeyDown, useArrowNavigation } from '../../../helpers';
10
+
11
+ type CommonProps = {
12
+ isOpen: boolean;
13
+ options: { label: string; value: string }[];
14
+ name: string;
15
+ value?: string;
16
+ values?: string[];
17
+ disabled?: boolean;
18
+ className?: string;
19
+ };
20
+
21
+ type SingleDropdownMenuProps = CommonProps & {
22
+ multiple?: false;
23
+ onChange: (value: string) => void;
24
+ onChangeMultiple?: (values: string[]) => void;
25
+ };
26
+
27
+ type MultipleDropdownMenuProps = CommonProps & {
28
+ multiple: true;
29
+ onChange?: (value: string) => void;
30
+ onChangeMultiple: (values: string[]) => void;
31
+ };
32
+
33
+ type DropdownMenuProps = SingleDropdownMenuProps | MultipleDropdownMenuProps;
34
+
35
+ export const DropdownMenu = ({ name, options, value = '', values = [], disabled = false, className = '', onChange = () => {}, onChangeMultiple = () => {}, isOpen, multiple = false }: DropdownMenuProps) => {
36
+ const menuRef = useArrowNavigation<HTMLUListElement>(multiple ? 'label' : '[role="option"]');
37
+ const { t } = useTranslation('design');
38
+ const handleOptionClick = useCallback(
39
+ (selection: string) => {
40
+ onChange(selection);
41
+ },
42
+ [onChange],
43
+ );
44
+
45
+ const handleOptionClickMultiple = useCallback(
46
+ (selection: string) => {
47
+ if (values.includes(selection)) {
48
+ onChangeMultiple(values.filter((value) => value !== selection));
49
+ } else {
50
+ onChangeMultiple([...values, selection]);
51
+ }
52
+ },
53
+ [onChangeMultiple, values],
54
+ );
55
+
56
+ const handleKeyDown = useCallback(
57
+ (e: React.KeyboardEvent<HTMLLIElement>, selection: string) => {
58
+ onKeyDown(e, () => handleOptionClick(selection));
59
+ },
60
+ [handleOptionClick],
61
+ );
62
+
63
+ const activeDescendantId = value ? `${name}-option-${value}` : undefined;
64
+
65
+ // prettier-ignore
66
+ const menuClasses = clsx(
67
+ 'rounded-input border-claret-violet-dark transition-duration-3000 will-change-height will-change-max-height w-full overflow-auto border bg-white px-3 py-2.5 opacity-0 transition-all md:min-w-max',
68
+ isOpen && 'max-h-33 h-auto opacity-100',
69
+ !isOpen && 'max-h-0 h-0',
70
+ className,
71
+ );
72
+
73
+ return (
74
+ <ul tabIndex={-1} aria-activedescendant={activeDescendantId} role="listbox" className={menuClasses} ref={menuRef}>
75
+ {options.map((option) =>
76
+ multiple ? (
77
+ <li
78
+ id={`${name}-option-${option.value}`}
79
+ aria-disabled={disabled}
80
+ aria-selected={option.value === value}
81
+ role="option"
82
+ tabIndex={-1}
83
+ key={option.value}
84
+ className={clsx('text-md hover:bg-beige-light transition-duration-300 cursor-pointer rounded-md transition-colors', option.value === value && 'bg-beige-light/40')}
85
+ >
86
+ <Checkbox name={option.value} checked={values.includes(option.value)} onChange={() => handleOptionClickMultiple(option.value)}>
87
+ <span>{option.label}</span>
88
+ </Checkbox>
89
+ </li>
90
+ ) : (
91
+ <li
92
+ id={`${name}-option-${option.value}`}
93
+ aria-disabled={disabled}
94
+ aria-selected={option.value === value}
95
+ role="option"
96
+ onClick={() => handleOptionClick(option.value)}
97
+ onKeyDown={(e) => handleKeyDown(e, option.value)}
98
+ tabIndex={isOpen ? 0 : -1}
99
+ key={option.value}
100
+ className={clsx('text-md hover:bg-beige-light transition-duration-300 outline-claret-violet-dark cursor-pointer rounded-md px-2 py-4 transition-colors', option.value === value && 'bg-beige-light/40')}
101
+ >
102
+ {option.label}
103
+ </li>
104
+ ),
105
+ )}
106
+ {options.length <= 0 && <li className="text-claret-violet-dark/50 text-legend cursor-default">{t('dropdown.noResults')}</li>}
107
+ </ul>
108
+ );
109
+ };
@@ -0,0 +1,33 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { ErrorMessage } from './ErrorMessage';
4
+
5
+ const meta = {
6
+ title: 'Components/Inputs/ErrorMessage',
7
+ component: ErrorMessage,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ argTypes: {
13
+ message: { control: 'text', description: 'The error message to display' },
14
+ hasError: { control: 'boolean', description: 'Whether the error message should be displayed' },
15
+ className: { control: 'text', description: 'Additional CSS classes to add to the component' },
16
+ },
17
+ args: {
18
+ message: 'This is an error message',
19
+ hasError: true,
20
+ className: '',
21
+ },
22
+ } satisfies Meta<typeof ErrorMessage>;
23
+
24
+ export default meta;
25
+
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Default: Story = {
29
+ args: {
30
+ message: 'This is an error message',
31
+ hasError: true,
32
+ },
33
+ };
@@ -0,0 +1,18 @@
1
+ import clsx from 'clsx';
2
+
3
+ import { Text } from '../../../components';
4
+
5
+ export interface ErrorMessageProps {
6
+ hasError: boolean;
7
+ message: string;
8
+ id?: string;
9
+ className?: string;
10
+ }
11
+
12
+ export const ErrorMessage = ({ message, hasError, className, id = '' }: ErrorMessageProps) => {
13
+ return (
14
+ <Text aria-live="polite" id={id} role={hasError ? 'alert' : undefined} size="legend" className={clsx('text-red-error mt-2 min-h-5 opacity-0 transition-opacity duration-300', hasError && 'opacity-100', className)}>
15
+ {message || ''}
16
+ </Text>
17
+ );
18
+ };
@@ -0,0 +1,54 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { Meta, StoryObj } from '@storybook/react-vite';
4
+
5
+ import { Filters } from './Filters';
6
+
7
+ const meta = {
8
+ title: 'Components/Inputs/Filters',
9
+ component: Filters,
10
+ tags: ['autodocs'],
11
+ parameters: {
12
+ layout: 'centered',
13
+ },
14
+ argTypes: {
15
+ placeholder: { control: 'text' },
16
+ label: { control: 'text' },
17
+ options: { control: 'object' },
18
+ onChange: { action: 'onChange' },
19
+ disabled: { control: 'boolean' },
20
+ name: { control: 'text' },
21
+ values: { control: 'object' },
22
+ },
23
+ globals: {
24
+ backgrounds: {
25
+ value: 'light',
26
+ },
27
+ },
28
+ } satisfies Meta<typeof Filters>;
29
+
30
+ export default meta;
31
+
32
+ type Story = StoryObj<typeof meta>;
33
+
34
+ export const Default: Story = {
35
+ args: {
36
+ label: 'Filters',
37
+ placeholder: 'Voir tout',
38
+ options: [
39
+ { label: 'Option 1', value: 'option1' },
40
+ { label: 'Option 2', value: 'option2' },
41
+ { label: 'Option 3', value: 'option3' },
42
+ ],
43
+ onChange: () => {},
44
+ disabled: false,
45
+ name: 'filters',
46
+ className: 'max-md:ms-50',
47
+ values: [],
48
+ },
49
+ render: (arg) => {
50
+ const [values, setValues] = useState<string[]>([]);
51
+
52
+ return <Filters {...arg} onChange={(values) => setValues(values)} values={values} />;
53
+ },
54
+ };
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useMemo, useRef, useState } from 'react';
4
+
5
+ import clsx from 'clsx';
6
+
7
+ import { ClearButton, FiltersIcon } from '../../../components';
8
+ import { onKeyDown, useClickOutside } from '../../../helpers';
9
+ import { DropdownMenu } from '../DropdownMenu/DropdownMenu';
10
+
11
+ export interface FiltersProps {
12
+ placeholder: string;
13
+ label: string;
14
+ options: { label: string; value: string }[];
15
+ values: string[];
16
+ onChange: (values: string[]) => void;
17
+ disabled: boolean;
18
+ name: string;
19
+ className?: string;
20
+ }
21
+
22
+ export const Filters = ({ placeholder, label, options, values, onChange, disabled, name, className }: FiltersProps) => {
23
+ const [isOpen, setIsOpen] = useState(false);
24
+ const ref = useRef<HTMLDivElement>(null);
25
+ useClickOutside(ref as React.RefObject<HTMLDivElement>, () => setIsOpen(false));
26
+
27
+ const handleChange = useCallback(
28
+ (selection: string[]) => {
29
+ onChange(selection);
30
+ },
31
+ [onChange],
32
+ );
33
+
34
+ const handleKeyDown = useCallback(
35
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
36
+ onKeyDown(e, () => setIsOpen(!isOpen));
37
+ },
38
+ [setIsOpen],
39
+ );
40
+
41
+ const clearFilters = useCallback(
42
+ (e: React.MouseEvent<HTMLButtonElement>) => {
43
+ e.stopPropagation();
44
+ onChange([]);
45
+ },
46
+ [onChange],
47
+ );
48
+
49
+ const buttonLabel = useMemo(() => {
50
+ return values?.length > 0 ? `${label} (${values.length})` : placeholder;
51
+ }, [values, label, placeholder]);
52
+
53
+ return (
54
+ <div className={clsx('relative', className)} ref={ref}>
55
+ <div
56
+ role="button"
57
+ tabIndex={0}
58
+ onClick={() => setIsOpen(!isOpen)}
59
+ onKeyDown={handleKeyDown}
60
+ className={clsx(
61
+ 'rounded-input bg-pink-oyster-dark text-claret-violet-dark border-claret-violet-dark outline-claret-violet-dark relative flex cursor-pointer items-center justify-between gap-4 border p-2.5 transition-colors duration-300 md:w-54 md:px-3 md:py-4',
62
+ 'max-md:after:bg-claret-violet-dark max-md:after:border-pink-oyster-dark max-md:after:absolute max-md:after:-top-[2px] max-md:after:-right-[2px] max-md:after:h-2 max-md:after:w-2 max-md:after:rounded-full max-md:after:border max-md:after:opacity-0 max-md:after:transition-opacity max-md:after:duration-300 max-md:after:content-[""]',
63
+ values.length > 0 && 'max-md:bg-claret-violet-dark max-md:text-pink-oyster-dark max-md:after:opacity-100',
64
+ )}
65
+ >
66
+ <span className="text-md flex items-center gap-2 max-md:hidden">
67
+ {buttonLabel}
68
+ {values.length > 0 && <ClearButton onClick={clearFilters} className="max-md:hidden" />}
69
+ </span>
70
+ <FiltersIcon className="w-6" />
71
+ </div>
72
+ <DropdownMenu name={name} options={options} multiple={true} values={values} onChangeMultiple={handleChange} disabled={disabled} isOpen={isOpen} className="absolute top-full right-0 mt-2.5 max-md:w-54" />
73
+ </div>
74
+ );
75
+ };
@@ -0,0 +1,34 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Label } from './Label';
4
+
5
+ const meta = {
6
+ title: 'Components/Inputs/Label',
7
+ component: Label,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ argTypes: {
13
+ label: { control: 'text' },
14
+ className: { control: 'text' },
15
+ id: { control: 'text' },
16
+ required: { control: 'boolean' },
17
+ },
18
+ globals: {
19
+ backgrounds: {
20
+ value: 'light',
21
+ },
22
+ },
23
+ } satisfies Meta<typeof Label>;
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ args: {
31
+ label: 'Label',
32
+ id: 'label',
33
+ },
34
+ };
@@ -0,0 +1,16 @@
1
+ import clsx from 'clsx';
2
+
3
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
4
+ label: string;
5
+ required?: boolean;
6
+ className?: string;
7
+ }
8
+
9
+ export const Label = ({ label, id, className = 'mb-4', required = false, ...props }: LabelProps) => {
10
+ return (
11
+ <label htmlFor={id} {...props} className={clsx('text-md block', className)}>
12
+ {label}
13
+ {!required && <>&nbsp;*</>}
14
+ </label>
15
+ );
16
+ };
@@ -0,0 +1,67 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Newsletter } from './Newsletter';
4
+
5
+ const meta = {
6
+ title: 'Components/Inputs/Newsletter',
7
+ component: Newsletter,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ argTypes: {
13
+ placeholder: { control: 'text', description: 'Placeholder text for the input' },
14
+ label: { control: 'text', description: 'Label text for the button' },
15
+ variant: { control: 'select', options: ['light', 'dark', 'footer'], description: 'Variant of the component light or dark' },
16
+ disabled: { control: 'boolean', description: 'Whether the input is disabled' },
17
+ className: { control: 'text', description: 'Additional CSS classes to add to the component' },
18
+ onSubscribe: { action: 'clicked' },
19
+ hasError: { control: 'boolean', description: 'Whether the input has an error' },
20
+ error: { control: 'text', description: 'Error message to display' },
21
+ },
22
+ args: {
23
+ placeholder: 'fake-email@mail.com',
24
+ className: '',
25
+ onSubscribe: () => {},
26
+ },
27
+ } satisfies Meta<typeof Newsletter>;
28
+
29
+ export default meta;
30
+
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ export const Light: Story = {
34
+ args: {
35
+ variant: 'light',
36
+ className: 'max-md:w-80',
37
+ },
38
+ globals: {
39
+ backgrounds: {
40
+ value: 'light',
41
+ },
42
+ },
43
+ };
44
+
45
+ export const Dark: Story = {
46
+ args: {
47
+ variant: 'dark',
48
+ className: 'max-md:w-80',
49
+ },
50
+ globals: {
51
+ backgrounds: {
52
+ value: 'dark',
53
+ },
54
+ },
55
+ };
56
+
57
+ export const Footer: Story = {
58
+ args: {
59
+ variant: 'footer',
60
+ className: 'max-md:w-80',
61
+ },
62
+ globals: {
63
+ backgrounds: {
64
+ value: 'lavender',
65
+ },
66
+ },
67
+ };
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ import clsx from 'clsx';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { Button, ErrorMessage } from '../../../components';
7
+
8
+ export interface NewsletterProps {
9
+ placeholder?: string;
10
+ label?: string;
11
+ variant?: 'light' | 'dark' | 'footer';
12
+ disabled?: boolean;
13
+ className?: string;
14
+ onSubscribe: (email: string) => void;
15
+ hasError?: boolean;
16
+ error?: string;
17
+ }
18
+
19
+ export const Newsletter = ({ variant = 'light', className, placeholder, label, disabled = false, onSubscribe = () => {}, hasError = false, error = '' }: NewsletterProps) => {
20
+ const { t } = useTranslation('design');
21
+ const defaultPlaceholder = placeholder || t('newsletter.emailPlaceholder');
22
+ const defaultLabel = label || t('newsletter.subscribeLabel');
23
+
24
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
25
+ e.preventDefault();
26
+ const data = new FormData(e.currentTarget);
27
+ const rawEmail = (data.get('email') as string) ?? '';
28
+ const email = rawEmail.trim();
29
+ if (email.length === 0) return;
30
+ onSubscribe(email);
31
+ };
32
+
33
+ return (
34
+ <form onSubmit={handleSubmit} className={className}>
35
+ <div
36
+ className={clsx(
37
+ 'flex w-full items-center justify-between gap-3 max-lg:flex-col lg:gap-4 lg:rounded-full',
38
+ variant === 'light' && 'lg:bg-beige-dark lg:text-claret-violet-dark',
39
+ variant === 'dark' && 'lg:bg-beige-light/20 lg:text-beige-light',
40
+ variant === 'footer' && 'lg:bg-claret-violet-dark/10 lg:text-claret-violet-dark',
41
+ )}
42
+ >
43
+ <input
44
+ aria-label={defaultPlaceholder}
45
+ aria-invalid={hasError || undefined}
46
+ aria-describedby={hasError ? 'newsletter-error' : undefined}
47
+ autoComplete="email"
48
+ inputMode="email"
49
+ enterKeyHint="go"
50
+ data-invalid={hasError}
51
+ required
52
+ disabled={disabled}
53
+ name="email"
54
+ type="email"
55
+ placeholder={defaultPlaceholder}
56
+ className={clsx(
57
+ 'text-button-mobile lg:text-button w-full ps-8 font-semibold transition-opacity duration-300 placeholder:opacity-50 focus:outline-none disabled:opacity-50 max-lg:h-13.5 max-lg:rounded-full',
58
+ variant === 'light' && 'max-lg:bg-beige-dark max-lg:text-claret-violet-dark',
59
+ variant === 'dark' && 'max-lg:bg-beige-light/20 max-lg:text-beige-light',
60
+ variant === 'footer' && 'max-lg:bg-claret-violet-dark/10 max-lg:text-claret-violet-dark',
61
+ )}
62
+ />
63
+ <Button type="submit" variant="primary" disabled={disabled} color={variant === 'dark' ? 'light' : 'dark'} className="max-lg:w-full">
64
+ {defaultLabel}
65
+ </Button>
66
+ </div>
67
+ <ErrorMessage id="newsletter-error" hasError={hasError} message={error} className={variant === 'light' ? '!text-claret-violet-dark ps-8' : '!text-beige-light ps-8'} />
68
+ </form>
69
+ );
70
+ };
@@ -0,0 +1,54 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { Meta, StoryObj } from '@storybook/react-vite';
4
+
5
+ import { QuantityInput } from './QuantityInput';
6
+
7
+ const meta = {
8
+ title: 'Components/Inputs/QuantityInput',
9
+ component: QuantityInput,
10
+ tags: ['autodocs'],
11
+ parameters: {
12
+ layout: 'centered',
13
+ },
14
+ globals: {
15
+ backgrounds: {
16
+ value: 'light',
17
+ },
18
+ },
19
+ argTypes: {
20
+ label: { control: 'text', description: 'The label of the quantity input' },
21
+ id: { control: 'text', description: 'The id of the quantity input' },
22
+ value: { control: 'number', description: 'The value of the quantity input' },
23
+ onChange: { action: 'onChange' },
24
+ required: { control: 'boolean', description: 'Whether the quantity input is required' },
25
+ metric: { control: 'text', description: 'The metric of the quantity input' },
26
+ min: { control: 'number', description: 'The minimum value of the quantity input' },
27
+ max: { control: 'number', description: 'The maximum value of the quantity input' },
28
+ hasError: { control: 'boolean', description: 'Whether the quantity input has an error' },
29
+ error: { control: 'text', description: 'The error message of the quantity input' },
30
+ },
31
+ } satisfies Meta<typeof QuantityInput>;
32
+
33
+ export default meta;
34
+
35
+ type Story = StoryObj<typeof meta>;
36
+
37
+ export const Default: Story = {
38
+ args: {
39
+ label: 'Quantity',
40
+ id: 'quantity',
41
+ value: 0,
42
+ onChange: () => {},
43
+ required: false,
44
+ hasError: false,
45
+ error: '',
46
+ metric: 'kg',
47
+ disabled: false,
48
+ },
49
+ render: (arg) => {
50
+ const [quantity, setQuantity] = useState(0);
51
+
52
+ return <QuantityInput {...arg} value={quantity} onChange={(value) => setQuantity(value)} />;
53
+ },
54
+ };
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { ErrorMessage, FAQButton, Label } from '../../../components';
4
+ import { onNumericInput } from '../../../helpers';
5
+
6
+ export interface QuantityInputProps {
7
+ label: string;
8
+ id: string;
9
+ value: number;
10
+ onChange: (value: number) => void;
11
+ required: boolean;
12
+ metric: string;
13
+ min?: number;
14
+ max?: number;
15
+ hasError?: boolean;
16
+ error?: string;
17
+ disabled?: boolean;
18
+ }
19
+
20
+ export const QuantityInput = ({ label = '', id, value, onChange, required, metric, min = 0, max = 99, hasError = false, error = '', disabled = false }: QuantityInputProps) => {
21
+ return (
22
+ <div className="flex items-center gap-4 max-md:flex-col max-md:items-start md:gap-8">
23
+ {label.length > 0 && <Label label={label} required={required} id={id} className="mb-0" />}
24
+ <div className="flex items-center gap-4">
25
+ <FAQButton variant="minus" onClick={() => onChange(value - 1)} disabled={value === min} />
26
+ <div className="flex items-center gap-1.5">
27
+ <input
28
+ disabled={disabled}
29
+ required={required}
30
+ type="number"
31
+ placeholder="0"
32
+ min={min}
33
+ max={max}
34
+ value={value == 0 ? '' : value}
35
+ onChange={(e) => onChange(Number(e.target.value))}
36
+ onKeyDown={onNumericInput}
37
+ className="border-claret-violet-dark rounded-input bg-beige-light text-claret-violet-dark text-md placeholder:text-claret-violet-dark/40 h-8.5 w-12 border px-3 focus:outline-none"
38
+ />
39
+ <span className="text-md">{metric}</span>
40
+ </div>
41
+ <FAQButton variant="plus" onClick={() => onChange(value + 1)} disabled={value === max} />
42
+ </div>
43
+ <ErrorMessage id={`${id}-error`} hasError={hasError} message={error} />
44
+ </div>
45
+ );
46
+ };
@@ -0,0 +1,33 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Tag } from './Tag';
4
+
5
+ const meta = {
6
+ title: 'Components/Inputs/Tag',
7
+ component: Tag,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ argTypes: {
13
+ label: { control: 'text' },
14
+ value: { control: 'text' },
15
+ },
16
+ globals: {
17
+ backgrounds: {
18
+ value: 'light',
19
+ },
20
+ },
21
+ } satisfies Meta<typeof Tag>;
22
+
23
+ export default meta;
24
+
25
+ type Story = StoryObj<typeof meta>;
26
+
27
+ export const Default: Story = {
28
+ args: {
29
+ label: 'Tag',
30
+ value: 'Tag',
31
+ onRemove: () => {},
32
+ },
33
+ };
@@ -0,0 +1,19 @@
1
+ import clsx from 'clsx';
2
+
3
+ import { ClearButton } from '../../../components';
4
+
5
+ export interface TagProps {
6
+ label: string;
7
+ value: string;
8
+ onRemove: (value: string) => void;
9
+ className?: string;
10
+ }
11
+
12
+ export const Tag = ({ label, value, onRemove, className }: TagProps) => {
13
+ return (
14
+ <div key={value} className={clsx('bg-claret-violet-dark/10 border-claret-violet-light text-button-mobile md:text-button rounded-input flex cursor-default items-center justify-between gap-2.5 border p-2.5 font-semibold', className)}>
15
+ {label}
16
+ <ClearButton onClick={() => onRemove(value)} />
17
+ </div>
18
+ );
19
+ };