@myelmut/design-system 0.1.46 → 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.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +720 -697
- package/dist/index.es.js.map +1 -1
- package/dist/types/index.d.ts +6 -1
- package/package.json +3 -2
- package/src/assets/fonts/PPMori-Regular.woff2 +0 -0
- package/src/assets/fonts/PPMori-RegularItalic.woff2 +0 -0
- package/src/assets/fonts/PPMori-SemiBold.woff2 +0 -0
- package/src/assets/fonts/PPPangaia-Medium.woff2 +0 -0
- package/src/assets/fonts/PPPangaia-MediumItalic.woff2 +0 -0
- package/src/assets/fonts/PPPangaia-Ultralight.woff2 +0 -0
- package/src/assets/illustrations/balls-light.webp +0 -0
- package/src/assets/illustrations/balls.webp +0 -0
- package/src/assets/illustrations/basket-light.webp +0 -0
- package/src/assets/illustrations/basket.webp +0 -0
- package/src/assets/illustrations/bowl-2-light.webp +0 -0
- package/src/assets/illustrations/bowl-2.webp +0 -0
- package/src/assets/illustrations/bowl-light.webp +0 -0
- package/src/assets/illustrations/bowl.webp +0 -0
- package/src/assets/illustrations/box-2-light.webp +0 -0
- package/src/assets/illustrations/box-2.webp +0 -0
- package/src/assets/illustrations/box-light.webp +0 -0
- package/src/assets/illustrations/box.webp +0 -0
- package/src/assets/illustrations/calendar-light.webp +0 -0
- package/src/assets/illustrations/calendar.webp +0 -0
- package/src/assets/illustrations/can-light.webp +0 -0
- package/src/assets/illustrations/can.webp +0 -0
- package/src/assets/illustrations/carrot-light.webp +0 -0
- package/src/assets/illustrations/carrot.webp +0 -0
- package/src/assets/illustrations/cat-light.webp +0 -0
- package/src/assets/illustrations/cat.webp +0 -0
- package/src/assets/illustrations/check-rounded.webp +0 -0
- package/src/assets/illustrations/chicken-light.webp +0 -0
- package/src/assets/illustrations/chicken.webp +0 -0
- package/src/assets/illustrations/cross-rounded.webp +0 -0
- package/src/assets/illustrations/crown-light.webp +0 -0
- package/src/assets/illustrations/crown.webp +0 -0
- package/src/assets/illustrations/dog-light.webp +0 -0
- package/src/assets/illustrations/dog.webp +0 -0
- package/src/assets/illustrations/face-light.webp +0 -0
- package/src/assets/illustrations/face.webp +0 -0
- package/src/assets/illustrations/food-bag-light.webp +0 -0
- package/src/assets/illustrations/food-bag.webp +0 -0
- package/src/assets/illustrations/france-light.webp +0 -0
- package/src/assets/illustrations/france.webp +0 -0
- package/src/assets/illustrations/fridge-light.webp +0 -0
- package/src/assets/illustrations/fridge.webp +0 -0
- package/src/assets/illustrations/glasses-light.webp +0 -0
- package/src/assets/illustrations/glasses.webp +0 -0
- package/src/assets/illustrations/half-label-light.webp +0 -0
- package/src/assets/illustrations/half-label.webp +0 -0
- package/src/assets/illustrations/kitten-light.webp +0 -0
- package/src/assets/illustrations/kitten.webp +0 -0
- package/src/assets/illustrations/label-light.webp +0 -0
- package/src/assets/illustrations/label.webp +0 -0
- package/src/assets/illustrations/leaf-light.webp +0 -0
- package/src/assets/illustrations/leaf.webp +0 -0
- package/src/assets/illustrations/liquid-light.webp +0 -0
- package/src/assets/illustrations/liquid.webp +0 -0
- package/src/assets/illustrations/magnifying-glass-light.webp +0 -0
- package/src/assets/illustrations/magnifying-glass.webp +0 -0
- package/src/assets/illustrations/meat-light.webp +0 -0
- package/src/assets/illustrations/meat.webp +0 -0
- package/src/assets/illustrations/molecule-light.webp +0 -0
- package/src/assets/illustrations/molecule.webp +0 -0
- package/src/assets/illustrations/paws-light.webp +0 -0
- package/src/assets/illustrations/paws.webp +0 -0
- package/src/assets/illustrations/plate-light.webp +0 -0
- package/src/assets/illustrations/plate.webp +0 -0
- package/src/assets/illustrations/pot-big-2-light.webp +0 -0
- package/src/assets/illustrations/pot-big-2.webp +0 -0
- package/src/assets/illustrations/pot-big-light.webp +0 -0
- package/src/assets/illustrations/pot-big.webp +0 -0
- package/src/assets/illustrations/pot-light.webp +0 -0
- package/src/assets/illustrations/pot.webp +0 -0
- package/src/assets/illustrations/puppy-light.webp +0 -0
- package/src/assets/illustrations/puppy.webp +0 -0
- package/src/assets/illustrations/quantity-light.webp +0 -0
- package/src/assets/illustrations/quantity.webp +0 -0
- package/src/assets/illustrations/sausage-light.webp +0 -0
- package/src/assets/illustrations/sausage.webp +0 -0
- package/src/assets/illustrations/sausages-light.webp +0 -0
- package/src/assets/illustrations/sausages.webp +0 -0
- package/src/assets/illustrations/skeleton-light.webp +0 -0
- package/src/assets/illustrations/skeleton.webp +0 -0
- package/src/assets/illustrations/sofa-light.webp +0 -0
- package/src/assets/illustrations/sofa.webp +0 -0
- package/src/assets/illustrations/sport-light.webp +0 -0
- package/src/assets/illustrations/sport.webp +0 -0
- package/src/assets/illustrations/steak-light.webp +0 -0
- package/src/assets/illustrations/steak.webp +0 -0
- package/src/assets/illustrations/truck-light.webp +0 -0
- package/src/assets/illustrations/truck.webp +0 -0
- package/src/assets/illustrations/warning-light.webp +0 -0
- package/src/assets/illustrations/warning.webp +0 -0
- package/src/assets/images/cat.webp +0 -0
- package/src/assets/images/dog.webp +0 -0
- package/src/assets/images/frequency/food-in-fridge.webp +0 -0
- package/src/assets/images/ingredients/beef.webp +0 -0
- package/src/assets/images/ingredients/beer-yeast.webp +0 -0
- package/src/assets/images/ingredients/calcium.webp +0 -0
- package/src/assets/images/ingredients/carrot.webp +0 -0
- package/src/assets/images/ingredients/chicken.webp +0 -0
- package/src/assets/images/ingredients/courgette.webp +0 -0
- package/src/assets/images/ingredients/dry-apple.webp +0 -0
- package/src/assets/images/ingredients/dry-carrot.webp +0 -0
- package/src/assets/images/ingredients/duck.webp +0 -0
- package/src/assets/images/ingredients/fish.webp +0 -0
- package/src/assets/images/ingredients/liquid.webp +0 -0
- package/src/assets/images/ingredients/oil.webp +0 -0
- package/src/assets/images/ingredients/pork.webp +0 -0
- package/src/assets/images/ingredients/potato.webp +0 -0
- package/src/assets/images/ingredients/quinoa.webp +0 -0
- package/src/assets/images/ingredients/rice.webp +0 -0
- package/src/assets/images/ingredients/seaweed.webp +0 -0
- package/src/assets/images/ingredients/turkey.webp +0 -0
- package/src/assets/images/ingredients/vitamins.webp +0 -0
- package/src/assets/images/tips/claudine-head.webp +0 -0
- package/src/assets/images/tips/claudine-tips-head-mobile.webp +0 -0
- package/src/assets/images/tips/claudine-tips-head-mobile@2x.webp +0 -0
- package/src/assets/images/tips/claudine-tips-head.webp +0 -0
- package/src/assets/images/tips/claudine-tips-head@2x.webp +0 -0
- package/src/assets/images/tips/claudine-tips-mobile.webp +0 -0
- package/src/assets/images/tips/claudine-tips-mobile@2x.webp +0 -0
- package/src/assets/images/tips/claudine-tips.webp +0 -0
- package/src/assets/images/tips/claudine-tips@2x.webp +0 -0
- package/src/assets/images/tips/payment-mobile.webp +0 -0
- package/src/assets/images/tips/payment-mobile@2x.webp +0 -0
- package/src/assets/images/tips/payment.webp +0 -0
- package/src/assets/images/tips/payment@2x.webp +0 -0
- package/src/assets/images/trash/dog-product-mobile.webp +0 -0
- package/src/assets/images/trash/dog-product.webp +0 -0
- package/src/assets/images/trash/full-cat.png +0 -0
- package/src/assets/images/trash/testimonial-1-mobile.webp +0 -0
- package/src/assets/images/trash/testimonial-1-mobile@2x.webp +0 -0
- package/src/assets/images/trash/testimonial-1.webp +0 -0
- package/src/assets/images/trash/testimonial-1@2x.webp +0 -0
- package/src/components/Accordions/FAQ/FaqItem.stories.tsx +61 -0
- package/src/components/Accordions/FAQ/FaqItem.tsx +55 -0
- package/src/components/Accordions/Ingredient/Ingredient.stories.tsx +38 -0
- package/src/components/Accordions/Ingredient/Ingredient.tsx +93 -0
- package/src/components/Accordions/index.tsx +4 -0
- package/src/components/Base/Banner/Banner.stories.tsx +33 -0
- package/src/components/Base/Banner/Banner.tsx +23 -0
- package/src/components/Base/Emblem/Emblem.stories.tsx +40 -0
- package/src/components/Base/Emblem/Emblem.tsx +22 -0
- package/src/components/Base/Logo/Logo.stories.tsx +46 -0
- package/src/components/Base/Logo/Logo.tsx +34 -0
- package/src/components/Base/ResponsiveImage/ResponsiveImage.stories.tsx +78 -0
- package/src/components/Base/ResponsiveImage/ResponsiveImage.tsx +56 -0
- package/src/components/Base/Text/Text.stories.tsx +115 -0
- package/src/components/Base/Text/Text.tsx +60 -0
- package/src/components/Base/Title/Title.stories.tsx +145 -0
- package/src/components/Base/Title/Title.tsx +77 -0
- package/src/components/Base/VideoPlayer/VideoPlayer.stories.tsx +60 -0
- package/src/components/Base/VideoPlayer/VideoPlayer.tsx +78 -0
- package/src/components/Base/index.tsx +9 -0
- package/src/components/Buttons/Button/Button.stories.tsx +158 -0
- package/src/components/Buttons/Button/Button.tsx +68 -0
- package/src/components/Buttons/CardButton/CardButton.stories.tsx +47 -0
- package/src/components/Buttons/CardButton/CardButton.tsx +25 -0
- package/src/components/Buttons/ClearButton/ClearButton.stories.tsx +26 -0
- package/src/components/Buttons/ClearButton/ClearButton.tsx +18 -0
- package/src/components/Buttons/FAQButton/FAQButton.stories.tsx +33 -0
- package/src/components/Buttons/FAQButton/FAQButton.tsx +27 -0
- package/src/components/Buttons/IllustratedCardButton/IllustratedCardButton.stories.tsx +71 -0
- package/src/components/Buttons/IllustratedCardButton/IllustratedCardButton.tsx +45 -0
- package/src/components/Buttons/SimpleIllustratedCardButton/SimpleIllustratedCardButton.stories.tsx +74 -0
- package/src/components/Buttons/SimpleIllustratedCardButton/SimpleIllustratedCardButton.tsx +43 -0
- package/src/components/Buttons/SocialButton/SocialButton.stories.tsx +56 -0
- package/src/components/Buttons/SocialButton/SocialButton.tsx +28 -0
- package/src/components/Buttons/Toggle/Toggle.tsx +64 -0
- package/src/components/Buttons/Toggle/Toogle.stories.tsx +66 -0
- package/src/components/Buttons/index.ts +10 -0
- package/src/components/Cards/CTACard/CTACard.stories.tsx +83 -0
- package/src/components/Cards/CTACard/CTACard.tsx +47 -0
- package/src/components/Cards/FeatureCard/FeatureCard.stories.tsx +96 -0
- package/src/components/Cards/FeatureCard/FeatureCard.tsx +50 -0
- package/src/components/Cards/FeatureIllustration/FeatureIllustration.stories.tsx +96 -0
- package/src/components/Cards/FeatureIllustration/FeatureIllustration.tsx +56 -0
- package/src/components/Cards/FeatureIllustration/index.ts +2 -0
- package/src/components/Cards/FoodCard/FoodCard.stories.tsx +43 -0
- package/src/components/Cards/FoodCard/FoodCard.tsx +37 -0
- package/src/components/Cards/FrequencySelectorCard/FrequencySelectorCard.stories.tsx +140 -0
- package/src/components/Cards/FrequencySelectorCard/FrequencySelectorCard.tsx +90 -0
- package/src/components/Cards/FrequencySelectorCard/index.ts +2 -0
- package/src/components/Cards/IllustratedCard/IllustratedCard.stories.tsx +54 -0
- package/src/components/Cards/IllustratedCard/IllustratedCard.tsx +44 -0
- package/src/components/Cards/PaymentCard/PaymentCard.stories.tsx +35 -0
- package/src/components/Cards/PaymentCard/PaymentCard.tsx +31 -0
- package/src/components/Cards/PlanCard/PlanCard.stories.tsx +140 -0
- package/src/components/Cards/PlanCard/PlanCard.tsx +119 -0
- package/src/components/Cards/Polaroid/Polaroid.stories.tsx +118 -0
- package/src/components/Cards/Polaroid/Polaroid.tsx +66 -0
- package/src/components/Cards/RecetteCard/RecetteCard.stories.tsx +86 -0
- package/src/components/Cards/RecetteCard/RecetteCard.tsx +47 -0
- package/src/components/Cards/StatCard/StatCard.stories.tsx +69 -0
- package/src/components/Cards/StatCard/StatCard.tsx +45 -0
- package/src/components/Cards/Testimonial/Testimonial.stories.tsx +65 -0
- package/src/components/Cards/Testimonial/Testimonial.tsx +62 -0
- package/src/components/Cards/TestimonialSlider/TestimonialSlider.stories.ts +53 -0
- package/src/components/Cards/TestimonialSlider/TestimonialSlider.tsx +50 -0
- package/src/components/Cards/Tips/Tips.stories.tsx +32 -0
- package/src/components/Cards/Tips/Tips.tsx +40 -0
- package/src/components/Cards/TransitionCard/TransitionCard.stories.tsx +50 -0
- package/src/components/Cards/TransitionCard/TransitionCard.tsx +66 -0
- package/src/components/Cards/UpCard/UpCard.stories.tsx +94 -0
- package/src/components/Cards/UpCard/UpCard.tsx +50 -0
- package/src/components/Cards/WizardTips/WizardTips.stories.tsx +48 -0
- package/src/components/Cards/WizardTips/WizardTips.tsx +33 -0
- package/src/components/Cards/index.ts +19 -0
- package/src/components/Inputs/ButtonSelect/ButtonSelect.stories.tsx +51 -0
- package/src/components/Inputs/ButtonSelect/ButtonSelect.tsx +34 -0
- package/src/components/Inputs/Checkbox/Checkbox.stories.tsx +47 -0
- package/src/components/Inputs/Checkbox/Checkbox.tsx +35 -0
- package/src/components/Inputs/Dropdown/Dropdown.stories.tsx +61 -0
- package/src/components/Inputs/Dropdown/Dropdown.tsx +108 -0
- package/src/components/Inputs/DropdownMenu/DropdownMenu.stories.tsx +75 -0
- package/src/components/Inputs/DropdownMenu/DropdownMenu.tsx +109 -0
- package/src/components/Inputs/ErrorMessage/ErrorMessage.stories.tsx +33 -0
- package/src/components/Inputs/ErrorMessage/ErrorMessage.tsx +18 -0
- package/src/components/Inputs/Filters/Filters.stories.tsx +54 -0
- package/src/components/Inputs/Filters/Filters.tsx +75 -0
- package/src/components/Inputs/Label/Label.stories.tsx +34 -0
- package/src/components/Inputs/Label/Label.tsx +16 -0
- package/src/components/Inputs/Newsletter/Newsletter.stories.tsx +67 -0
- package/src/components/Inputs/Newsletter/Newsletter.tsx +70 -0
- package/src/components/Inputs/QuantityInput/QuantityInput.stories.tsx +54 -0
- package/src/components/Inputs/QuantityInput/QuantityInput.tsx +46 -0
- package/src/components/Inputs/Tag/Tag.stories.tsx +33 -0
- package/src/components/Inputs/Tag/Tag.tsx +19 -0
- package/src/components/Inputs/TagSelect/TagSelect.stories.tsx +50 -0
- package/src/components/Inputs/TagSelect/TagSelect.tsx +48 -0
- package/src/components/Inputs/TextInput/TextInput.stories.tsx +40 -0
- package/src/components/Inputs/TextInput/TextInput.tsx +38 -0
- package/src/components/Inputs/WizardDropdown/WizardDropdown.stories.tsx +59 -0
- package/src/components/Inputs/WizardDropdown/WizardDropdown.tsx +93 -0
- package/src/components/Inputs/WizardTextInput/WizardTextInput.stories.tsx +40 -0
- package/src/components/Inputs/WizardTextInput/WizardTextInput.tsx +31 -0
- package/src/components/Inputs/index.ts +16 -0
- package/src/components/Navigation/Footer/Footer.stories.tsx +28 -0
- package/src/components/Navigation/Footer/Footer.tsx +130 -0
- package/src/components/Navigation/FooterTips/FooterTips.stories.tsx +22 -0
- package/src/components/Navigation/FooterTips/FooterTips.tsx +24 -0
- package/src/components/Navigation/MobileMenu/MobileMenu.stories.tsx +56 -0
- package/src/components/Navigation/MobileMenu/MobileMenu.tsx +45 -0
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +128 -0
- package/src/components/Navigation/Navbar/Navbar.tsx +66 -0
- package/src/components/Navigation/NavbarDesktop/NavbarDesktop.stories.tsx +57 -0
- package/src/components/Navigation/NavbarDesktop/NavbarDesktop.tsx +48 -0
- package/src/components/Navigation/NavbarMobile/NavbarMobile.stories.tsx +59 -0
- package/src/components/Navigation/NavbarMobile/NavbarMobile.tsx +75 -0
- package/src/components/Navigation/Stepper/Stepper.stories.tsx +41 -0
- package/src/components/Navigation/Stepper/Stepper.tsx +19 -0
- package/src/components/Navigation/Tabs/Tabs.stories.tsx +120 -0
- package/src/components/Navigation/Tabs/Tabs.tsx +92 -0
- package/src/components/Navigation/WizardNavbar/WizardNavbar.stories.tsx +59 -0
- package/src/components/Navigation/WizardNavbar/WizardNavbar.tsx +83 -0
- package/src/components/Navigation/index.ts +11 -0
- package/src/components/SVG/Facebook.svg +3 -0
- package/src/components/SVG/Instagram.svg +5 -0
- package/src/components/SVG/Linkedin.svg +5 -0
- package/src/components/SVG/Tiktok.svg +7 -0
- package/src/components/SVG/arrow-plain.svg +3 -0
- package/src/components/SVG/arrow.svg +3 -0
- package/src/components/SVG/calendar.svg +4 -0
- package/src/components/SVG/check-circle.svg +5 -0
- package/src/components/SVG/check-rounded.svg +4 -0
- package/src/components/SVG/check.svg +3 -0
- package/src/components/SVG/cross-rounded.svg +3 -0
- package/src/components/SVG/cross.svg +4 -0
- package/src/components/SVG/dollar-rounded.svg +6 -0
- package/src/components/SVG/double-arrow.svg +3 -0
- package/src/components/SVG/filters.svg +8 -0
- package/src/components/SVG/hat-cook.svg +4 -0
- package/src/components/SVG/home-hero-shape-mobile.svg +3 -0
- package/src/components/SVG/home-hero-shape-small.svg +3 -0
- package/src/components/SVG/home-hero-shape.svg +3 -0
- package/src/components/SVG/index.ts +75 -0
- package/src/components/SVG/info.svg +3 -0
- package/src/components/SVG/magic-wand.svg +9 -0
- package/src/components/SVG/menu.svg +5 -0
- package/src/components/SVG/minus.svg +3 -0
- package/src/components/SVG/mute.svg +4 -0
- package/src/components/SVG/pause.svg +3 -0
- package/src/components/SVG/play.svg +3 -0
- package/src/components/SVG/plus.svg +4 -0
- package/src/components/SVG/polaroid-thread.svg +17 -0
- package/src/components/SVG/profil.svg +4 -0
- package/src/components/SVG/quote.svg +3 -0
- package/src/components/SVG/recipe-bg-shape.svg +3 -0
- package/src/components/SVG/star.svg +3 -0
- package/src/components/SVG/subtract.svg +3 -0
- package/src/components/SVG/trustpilot.svg +14 -0
- package/src/components/SVG/unmute.svg +5 -0
- package/src/components/index.ts +20 -0
- package/src/components/styles/Color/Color.stories.tsx +89 -0
- package/src/components/styles/Color/Color.tsx +47 -0
- package/src/components/styles/Icon/Icon.stories.tsx +103 -0
- package/src/components/styles/Icon/Icon.tsx +6 -0
- package/src/components/styles/Illustration/Illustration.stories.tsx +253 -0
- package/src/components/styles/Illustration/Illustration.tsx +45 -0
- package/src/components/styles/Typography/Typography.stories.tsx +37 -0
- package/src/components/styles/Typography/Typography.tsx +19 -0
- package/src/helpers/accordions.ts +39 -0
- package/src/helpers/clickoutside.ts +31 -0
- package/src/helpers/debounce.ts +10 -0
- package/src/helpers/index.ts +9 -0
- package/src/helpers/intersectionObserver.ts +101 -0
- package/src/helpers/keyboardControl.ts +58 -0
- package/src/helpers/responsive.ts +29 -0
- package/src/helpers/scroll.ts +16 -0
- package/src/index.ts +8 -0
- package/src/lib/i18n.ts +20 -0
- package/src/lib/locales/fr/design.json +42 -0
- package/src/styles/globals.css +205 -0
- 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 && <> *</>}
|
|
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
|
+
};
|