From bdaadc792c84f411ccd7b779a3558cbe67273f46 Mon Sep 17 00:00:00 2001 From: MrTRyy Date: Thu, 30 Nov 2023 17:34:29 +0100 Subject: [PATCH] add git shouldnt ignore case --- .../atoms/ActionWrapper/ActionWrapper.mdx | 57 ++++++ .../atoms/ActionWrapper/ActionWrapper.tsx | 17 ++ src/components/atoms/ActionWrapper/index.ts | 1 + .../AlignedInputLabel.stories.tsx | 64 +++++++ .../AlignedInputLabel/AlignedInputLabel.tsx | 26 +++ .../TalignedInputLabel.model.ts | 13 ++ .../atoms/AlignedInputLabel/index.ts | 1 + .../AnimatedInputLabel.stories.tsx | 64 +++++++ .../AnimatedLabel/AnimatedInputLabel.tsx | 55 ++++++ .../TAnimatedInputLabel.model.ts | 12 ++ src/components/atoms/AnimatedLabel/index.ts | 1 + .../atoms/AvilableDot/AvailableDot.tsx | 29 +++ .../atoms/AvilableDot/AvilableDot.stories.tsx | 51 ++++++ src/components/atoms/AvilableDot/index.ts | 1 + .../atoms/BackDrop/BackDrop.stories.tsx | 32 ++++ src/components/atoms/BackDrop/BackDrop.tsx | 35 ++++ src/components/atoms/BackDrop/index.ts | 1 + .../CheckerBoardPattern.stories.tsx | 44 +++++ .../CheckerBoardPattern.tsx | 19 ++ .../atoms/CheckerBoardPattern/index.ts | 1 + .../ColorDisplay/ColorDisplay.stories.tsx | 58 ++++++ .../atoms/ColorDisplay/ColorDisplay.style.tsx | 77 ++++++++ .../atoms/ColorDisplay/ColorDisplay.tsx | 49 +++++ src/components/atoms/ColorDisplay/index.ts | 1 + .../ColorIndicator/ColorIndicator.style.tsx | 39 ++++ .../atoms/ColorIndicator/ColorIndicator.tsx | 20 ++ .../ColorIndicator/Colorindicator.stories.tsx | 46 +++++ src/components/atoms/ColorIndicator/index.ts | 1 + .../ComponentAsWrapper/ComponentAsWrapper.mdx | 52 ++++++ .../ComponentAsWrapper/ComponentAsWrapper.tsx | 13 ++ .../CustomeDropDown.tsx | 80 ++++++++ .../DateNumberAtom/DateNumberAtom.stories.tsx | 93 ++++++++++ .../DateNumberAtom/DateNumberAtom.style.tsx | 76 ++++++++ .../atoms/DateNumberAtom/DateNumberAtom.tsx | 40 ++++ src/components/atoms/DateNumberAtom/index.ts | 1 + .../atoms/DateOutput/DateOutput.stories.tsx | 64 +++++++ .../atoms/DateOutput/DateOutput.tsx | 65 +++++++ src/components/atoms/DateOutput/index.ts | 1 + .../DropDownSelect/DropDownSelect.model.ts | 0 .../DropDownSelect/DropDownSelect.stories.tsx | 64 +++++++ .../DropDownSelect/DropDownSelect.style.tsx | 69 +++++++ .../atoms/DropDownSelect/DropDownSelect.tsx | 54 ++++++ src/components/atoms/DropDownSelect/index.ts | 1 + .../EditBarModal/EditBarModal.stories.tsx | 61 ++++++ .../atoms/EditBarModal/EditBarModal.style.tsx | 53 ++++++ .../atoms/EditBarModal/EditBarModal.tsx | 16 ++ .../atoms/EditBarModal/IEditbarModal.model.ts | 8 + src/components/atoms/EditBarModal/index.ts | 1 + .../FancyActionWrapper.style.tsx | 18 ++ .../FancyActionWrapper/FancyActionWrapper.tsx | 28 +++ src/components/atoms/FancyBox/FancyBox.mdx | 68 +++++++ .../atoms/FancyBox/FancyBox.model.ts | 16 ++ .../atoms/FancyBox/FancyBox.stories.tsx | 169 +++++++++++++++++ .../atoms/FancyBox/FancyBox.style.tsx | 16 ++ src/components/atoms/FancyBox/FancyBox.tsx | 23 +++ src/components/atoms/FancyBox/index.ts | 1 + src/components/atoms/FancyCard/Card.model.ts | 20 ++ .../atoms/FancyCard/FancyCard.style.tsx | 32 ++++ src/components/atoms/FancyCard/FancyCard.tsx | 40 ++++ .../atoms/FancyCard/FancyCrad.stories.tsx | 76 ++++++++ src/components/atoms/FancyCard/index.ts | 1 + .../atoms/FancyImage/FancyImage.stories.tsx | 56 ++++++ .../atoms/FancyImage/FancyImage.style.tsx | 9 + .../atoms/FancyImage/FancyImage.tsx | 26 +++ src/components/atoms/FancyImage/index.ts | 1 + .../atoms/FancyLI/FancyLI.stories.tsx | 54 ++++++ src/components/atoms/FancyLI/FancyLI.tsx | 40 ++++ src/components/atoms/FancyLI/index.ts | 1 + .../atoms/FancyLine/FancyLine.stories.tsx | 74 ++++++++ src/components/atoms/FancyLine/FancyLine.tsx | 49 +++++ src/components/atoms/FancyLine/index.ts | 1 + .../FancyLoadingBar.stories.tsx | 41 +++++ .../atoms/FancyLoadingBar/FancyLoadingBar.tsx | 43 +++++ src/components/atoms/FancyLoadingBar/index.ts | 1 + .../FancyLoadingSpinner.stories.tsx | 41 +++++ .../FancyLoadingSpinner.tsx | 104 +++++++++++ .../atoms/FancyLoadingSpinner/index.ts | 1 + .../FancyProfilePicture.stories.tsx | 95 ++++++++++ .../FancyProfilePicture.style.tsx | 77 ++++++++ .../FancyProfilePicture.tsx | 59 ++++++ .../atoms/FancyProfilePicture/index.ts | 1 + .../atoms/FancySVGAtom/FancySVGAtom.model.ts | 30 +++ .../FancySVGAtom/FancySVGAtom.stories.tsx | 73 ++++++++ .../atoms/FancySVGAtom/FancySVGAtom.tsx | 72 ++++++++ src/components/atoms/FancySVGAtom/index.ts | 1 + .../atoms/FancyVideo/FancyVideo.stories.tsx | 69 +++++++ .../atoms/FancyVideo/FancyVideo.tsx | 51 ++++++ src/components/atoms/FancyVideo/index.ts | 1 + .../FancyXButton/FancyXButton.stories.tsx | 55 ++++++ .../atoms/FancyXButton/FancyXButton.tsx | 52 ++++++ src/components/atoms/FancyXButton/index.ts | 1 + .../ImageVideoOverlay.stories.tsx | 55 ++++++ .../ImageVideoOverlay/ImageVideoOverlay.tsx | 132 +++++++++++++ .../atoms/ImageVideoOverlay/index.ts | 1 + .../atoms/InputLabel/InputLabel.stories.tsx | 43 +++++ .../atoms/InputLabel/InputLabel.tsx | 21 +++ src/components/atoms/InputLabel/index.ts | 1 + .../InputUnderline/InputUnderline.stories.tsx | 77 ++++++++ .../atoms/InputUnderline/InputUnderline.tsx | 60 ++++++ src/components/atoms/InputUnderline/index.ts | 1 + .../atoms/ListDivider/ListDivider.model.ts | 22 +++ .../atoms/ListDivider/ListDivider.stories.tsx | 60 ++++++ .../atoms/ListDivider/ListDivider.style.tsx | 67 +++++++ .../atoms/ListDivider/ListDivider.tsx | 33 ++++ src/components/atoms/ListDivider/index.ts | 1 + .../LoadingSVGArrows.stories.tsx | 89 +++++++++ .../LoadingSVGArrows/LoadingSVGArrows.tsx | 32 ++++ .../atoms/MenuItem/MenuItem.stories.tsx | 60 ++++++ .../atoms/MenuItem/MenuItem.style.tsx | 29 +++ src/components/atoms/MenuItem/MenuItem.tsx | 26 +++ src/components/atoms/MenuItem/index.ts | 1 + .../PageNumberList/PageNumberList.stories.tsx | 63 +++++++ .../PageNumberList/PageNumberList.style.tsx | 21 +++ .../atoms/PageNumberList/PageNumberList.tsx | 77 ++++++++ .../atoms/PasswordEye/PasswordEye.stories.tsx | 51 ++++++ .../atoms/PasswordEye/PasswordEye.tsx | 31 ++++ src/components/atoms/PasswordEye/index.ts | 1 + .../atoms/ProgressBar/ProgressBar.stories.tsx | 75 ++++++++ .../atoms/ProgressBar/ProgressBar.tsx | 60 ++++++ src/components/atoms/ProgressBar/index.ts | 1 + .../atoms/RawCheckbox/RawCheckbox.model.ts | 7 + .../atoms/RawCheckbox/RawCheckbox.stories.tsx | 44 +++++ .../atoms/RawCheckbox/RawCheckbox.style.tsx | 71 +++++++ .../atoms/RawCheckbox/RawCheckbox.tsx | 40 ++++ src/components/atoms/RawCheckbox/index.ts | 1 + .../atoms/RawInput/RawInput.stories.tsx | 60 ++++++ src/components/atoms/RawInput/RawInput.tsx | 28 +++ src/components/atoms/RawInput/index.ts | 1 + src/components/atoms/RawLI/RawLI.tsx | 7 + src/components/atoms/RawLI/index.ts | 1 + src/components/atoms/RawNav/RawNav.tsx | 7 + .../atoms/RawRadio/RawRadio.stories.tsx | 58 ++++++ .../atoms/RawRadio/RawRadio.style.tsx | 55 ++++++ src/components/atoms/RawRadio/RawRadio.tsx | 21 +++ src/components/atoms/RawRadio/index.ts | 1 + .../atoms/RawSlider/RawSlider.stories.tsx | 79 ++++++++ .../atoms/RawSlider/RawSlider.style.tsx | 51 ++++++ src/components/atoms/RawSlider/RawSlider.tsx | 55 ++++++ src/components/atoms/RawSlider/index.ts | 1 + src/components/atoms/RawUL/RawUL.tsx | 14 ++ .../ScrollableBar/ScrollableBar.stories.tsx | 52 ++++++ .../atoms/ScrollableBar/ScrollableBar.tsx | 29 +++ .../ScrollableBar/SrollableBar.style.tsx | 11 ++ src/components/atoms/ScrollableBar/index.ts | 1 + .../SimpleDialog/SimpleDialog.stories.tsx | 58 ++++++ .../atoms/SimpleDialog/SimpleDialog.tsx | 55 ++++++ src/components/atoms/SimpleDialog/index.ts | 1 + .../SingleInputAtom.stories.tsx | 79 ++++++++ .../atoms/SingleInputAtom/SingleInputAtom.tsx | 69 +++++++ src/components/atoms/SingleInputAtom/index.ts | 1 + .../SliderMarker/SliderMarker.stories.tsx | 49 +++++ .../atoms/SliderMarker/SliderMarker.style.tsx | 26 +++ .../atoms/SliderMarker/SliderMarker.tsx | 21 +++ src/components/atoms/SliderMarker/index.ts | 1 + src/components/atoms/Slot/Slot.stories.tsx | 59 ++++++ src/components/atoms/Slot/Slot.tsx | 39 ++++ src/components/atoms/Slot/index.ts | 1 + .../SpeedDailMenueItem.stories.tsx | 67 +++++++ .../SpeedDailMenueItem.style.tsx | 77 ++++++++ .../SpeedDialMenueItem/SpeedDailMenueItem.tsx | 41 +++++ .../atoms/SpeedDialMenueItem/index.ts | 1 + .../SwipeUpContainer.stories.tsx | 71 +++++++ .../SwipeUpContainer/SwipeUpContainer.tsx | 48 +++++ .../atoms/SwipeUpContainer/index.ts | 1 + .../atoms/SwipeUpDash/SwipeUpDash.stories.tsx | 39 ++++ .../atoms/SwipeUpDash/SwipeUpDash.tsx | 33 ++++ src/components/atoms/SwipeUpDash/index.ts | 1 + .../SwitchActiveIndicator.stories.tsx | 112 ++++++++++++ .../SwitchActiveIndicator.style.tsx | 78 ++++++++ .../SwitchActiveIndicator.tsx | 30 +++ .../utils/generateBlob.ts | 49 +++++ .../utils/generateLines.ts | 39 ++++ .../atoms/Typography/Typography.stories.tsx | 60 ++++++ .../atoms/Typography/Typography.tsx | 46 +++++ .../TypographyFontVariations.style.tsx | 44 +++++ .../Typography/TypographyStyleVariants.tsx | 88 +++++++++ src/components/atoms/Typography/index.ts | 1 + .../atoms/WeekDays/WeekDays.stories.tsx | 62 +++++++ src/components/atoms/WeekDays/WeekDays.tsx | 51 ++++++ src/components/atoms/WeekDays/index.ts | 1 + .../YearSelector/YearSelector.stories.tsx | 71 +++++++ .../atoms/YearSelector/YearSelector.style.tsx | 34 ++++ .../atoms/YearSelector/YearSelector.tsx | 44 +++++ src/components/atoms/YearSelector/index.ts | 1 + .../icons/SVGCheckMark/SVGCheckMark.tsx | 7 + src/components/icons/SVGCheckMark/index.ts | 1 + .../icons/SVGChevronLeft/SVGChevronLeft.tsx | 12 ++ src/components/icons/SVGChevronLeft/index.ts | 1 + .../icons/SVGChevronRight/SVGChevronRight.tsx | 12 ++ src/components/icons/SVGChevronRight/index.ts | 1 + .../SVGClipBoardIcon/SVGClipBoardIcon.tsx | 9 + .../icons/SVGClipBoardIcon/index.ts | 1 + .../SVGClipBoardIconChecked.tsx | 9 + .../icons/SVGClipBoardIconChecked/index.ts | 1 + .../icons/SVGEyeCrossed/SVGEyeCrossed.tsx | 9 + src/components/icons/SVGEyeCrossed/index.ts | 1 + .../icons/SVGEyeOpen/SVGEyeOpen.tsx | 9 + src/components/icons/SVGEyeOpen/index.ts | 1 + .../SVGLoadingArrows/SVGLoadingArrows.tsx | 11 ++ .../icons/SVGLoadingArrows/index.ts | 1 + src/components/icons/SVGPlus/SVGPlus.tsx | 7 + src/components/icons/SVGPlus/index.ts | 1 + src/components/icons/SVGSearch/SVGSearch.tsx | 7 + src/components/icons/SVGSearch/index.ts | 1 + .../icons/SVGXCircle/SVGXCircle.tsx | 8 + src/components/icons/SVGXCircle/index.ts | 1 + .../molecules/BottomBar/BottomBar.stories.tsx | 49 +++++ .../molecules/BottomBar/BottomBar.style.tsx | 9 + .../molecules/BottomBar/BottomBar.tsx | 41 +++++ src/components/molecules/BottomBar/index.ts | 1 + .../BottomBarIcon/BottomBarIcon.stories.tsx | 73 ++++++++ .../BottomBarIcon/BottomBarIcon.style.tsx | 42 +++++ .../molecules/BottomBarIcon/BottomBarIcon.tsx | 33 ++++ .../molecules/Button/Button.stories.tsx | 94 ++++++++++ .../molecules/Button/Button.style.tsx | 30 +++ src/components/molecules/Button/Button.tsx | 49 +++++ .../molecules/Chip/Chip.stories.tsx | 87 +++++++++ src/components/molecules/Chip/Chip.style.tsx | 44 +++++ src/components/molecules/Chip/Chip.tsx | 53 ++++++ .../molecules/ChipList/ChipList.stories.tsx | 73 ++++++++ .../molecules/ChipList/ChipList.style.tsx | 54 ++++++ .../molecules/ChipList/ChipList.tsx | 28 +++ .../ComponentAndActionWrapper.mdx | 86 +++++++++ .../ComponentAndActionWrapper.tsx | 34 ++++ .../molecules/DateInput/DateInput.stories.tsx | 39 ++++ .../molecules/DateInput/DateInput.tsx | 36 ++++ src/components/molecules/DateInput/index.ts | 1 + .../DateNumberWithStatus.stories.tsx | 95 ++++++++++ .../DateNumberWithStatus.tsx | 49 +++++ .../molecules/DateNumberWithStatus/index.ts | 1 + .../DateOutputFromTo.state.ts | 14 ++ .../DateOutputFromTo.stories.tsx | 53 ++++++ .../DateOutputFromTo/DateOutputFromTo.tsx | 79 ++++++++ .../molecules/DateOutputFromTo/index.ts | 1 + .../DynamicBottomBar.stories.tsx | 53 ++++++ .../DynamicBottomScrollBar.style.tsx | 11 ++ .../DynamicBottomScrollBar.tsx | 24 +++ .../molecules/DynamicBottomScrollBar/index.ts | 1 + .../molecules/EditBar/EditBar.state.tsx | 31 ++++ src/components/molecules/EditBar/EditBar.tsx | 74 ++++++++ src/components/molecules/EditBar/index.ts | 1 + src/components/molecules/EditBar/todo.txt | 6 + .../FancyCheckbox/FancyCheckbox.model.ts | 12 ++ .../FancyCheckbox/FancyCheckbox.stories.tsx | 56 ++++++ .../FancyCheckbox/FancyCheckbox.style.tsx | 42 +++++ .../molecules/FancyCheckbox/FancyCheckbox.tsx | 33 ++++ .../molecules/FancyCheckbox/index.ts | 1 + .../FancyColorArea/FancyColorArea.stories.tsx | 42 +++++ .../FancyColorArea/FancyColorArea.style.tsx | 75 ++++++++ .../FancyColorArea/FancyColorArea.tsx | 94 ++++++++++ .../molecules/FancyColorArea/index.ts | 1 + .../FancyColorOutput/ColorTransformator.ts | 50 +++++ .../FancyColorOutput.model.ts | 47 +++++ .../FancyColorOutput.stories.tsx | 57 ++++++ .../FancyColorOutput.style.tsx | 24 +++ .../FancyColorOutput/FancyColorOutput.tsx | 112 ++++++++++++ .../InputFields/AdaptInput/AdaptInputs.tsx | 81 ++++++++ .../InputFields/AdaptInput/index.ts | 1 + .../InputFields/InputFields.style.tsx | 25 +++ .../InputFields/InputFields.tsx | 57 ++++++ .../FancyColorOutput/InputFields/index.ts | 1 + .../molecules/FancyColorOutput/index.ts | 1 + .../FancyContent/FancyContent.stories.tsx | 54 ++++++ .../molecules/FancyContent/FancyContent.tsx | 90 +++++++++ .../FancyContent/utils/FancyContentIcon.tsx | 15 ++ .../FancyContent/utils/FancyContentText.tsx | 58 ++++++ .../FancyContent/utils/sizeSettings.ts | 23 +++ .../FancyContentCard.stories.tsx | 33 ++++ .../FancyContentCard/FancyContentCard.tsx | 58 ++++++ .../molecules/FancyContentCard/index.ts | 1 + .../FancyDropDownUL.stories.tsx | 61 ++++++ .../FancyDropDownUL/FancyDropDownUL.style.tsx | 53 ++++++ .../FancyDropDownUL/FancyDropDownUL.tsx | 52 ++++++ .../molecules/FancyDropDownUL/index.ts | 1 + .../FancyHueSlider/FancyHueSlider.stories.tsx | 41 +++++ .../FancyHueSlider/FancyHueSlider.style.tsx | 18 ++ .../FancyHueSlider/FancyHueSlider.tsx | 69 +++++++ .../molecules/FancyHueSlider/index.ts | 1 + .../FancyImageText/FancyImageText.stories.tsx | 43 +++++ .../FancyImageText/FancyImageText.tsx | 28 +++ .../molecules/FancyImageText/index.ts | 1 + .../molecules/FancyListBox/FancyListBox.mdx | 53 ++++++ .../FancyListBox/FancyListBox.stories.tsx | 38 ++++ .../FancyListBox/FancyListBox.style.tsx | 12 ++ .../molecules/FancyListBox/FancyListBox.tsx | 23 +++ .../FancyListBox/FancyListBoxItem.tsx | 34 ++++ .../molecules/FancyListBox/index.ts | 1 + .../FancyMiniProfile.stories.tsx | 71 +++++++ .../FancyMiniProfile.style.tsx | 52 ++++++ .../FancyMiniProfile/FancyMiniProfile.tsx | 96 ++++++++++ .../molecules/FancyMiniProfile/index.ts | 1 + .../FancyModalHeadLine.model.ts | 8 + .../FancyModalHeadLine.stories.tsx | 57 ++++++ .../FancyModalHeadLine/FancyModalHeadLine.tsx | 44 +++++ .../molecules/FancyModalHeadLine/index.ts | 1 + .../FancyOpacitySlider.stories.tsx | 50 +++++ .../FancyOpacitySlider.style.tsx | 40 ++++ .../FancyOpacitySlider/FancyOpacitySlider.tsx | 68 +++++++ .../molecules/FancyOpacitySlider/index.ts | 1 + .../FancyPageList/FancyPageList.stories.tsx | 67 +++++++ .../molecules/FancyPageList/FancyPageList.tsx | 70 +++++++ .../molecules/FancyPageList/index.ts | 1 + .../FancyProgressBar.stories.tsx | 94 ++++++++++ .../FancyProgressBar/FancyProgressBar.tsx | 72 ++++++++ .../molecules/FancyProgressBar/index.ts | 1 + .../FancySelectWrapper.style.tsx | 43 +++++ .../FancySelectWrapper/FancySelectWrapper.tsx | 61 ++++++ .../FancySpeedDailButton.stories.tsx | 46 +++++ .../FancySpeedDailButton.tsx | 45 +++++ .../SpeedDailButton.style.tsx | 96 ++++++++++ .../molecules/FancySpeedDailButton/index.ts | 1 + .../FancyTabSwitch/FancyTabSwitch.stories.tsx | 105 +++++++++++ .../FancyTabSwitch/FancyTabSwitch.style.tsx | 55 ++++++ .../FancyTabSwitch/FancyTabSwitch.tsx | 48 +++++ .../molecules/FancyTabSwitch/index.ts | 1 + .../utils/generateColorDesign.ts | 76 ++++++++ .../FancyTabSwitchButton.model.ts | 25 +++ .../FancyTabSwitchButton.stories.tsx | 95 ++++++++++ .../FancyTabSwitchButton.style.tsx | 130 +++++++++++++ .../FancyTabSwitchButton.tsx | 53 ++++++ .../molecules/FancyTabSwitchButton/index.ts | 1 + .../FancyVideoText/FancyVideoText.stories.tsx | 71 +++++++ .../FancyVideoText/FancyVideoText.tsx | 24 +++ .../molecules/FancyVideoText/index.ts | 1 + .../molecules/Fieldset/Fieldset.stories.tsx | 50 +++++ .../molecules/Fieldset/Fieldset.style.tsx | 16 ++ .../molecules/Fieldset/Fieldset.tsx | 31 ++++ src/components/molecules/Fieldset/index.ts | 1 + .../molecules/Header/Header.stories.tsx | 62 +++++++ .../molecules/Header/Header.style.tsx | 14 ++ src/components/molecules/Header/Header.tsx | 25 +++ src/components/molecules/Header/index.ts | 1 + .../HeaderTitleWithLogo.stories.tsx | 53 ++++++ .../HeaderTitleWithLogo.tsx | 62 +++++++ .../molecules/HeaderTitleWithLogo/index.ts | 1 + .../molecules/InfoCard/InfoCard.stories.tsx | 66 +++++++ .../molecules/InfoCard/InfoCard.tsx | 20 ++ .../molecules/InfoCard/InfoCrad.style.tsx | 28 +++ src/components/molecules/InfoCard/index.ts | 1 + .../InputWrapper/InputWrapper.stories.tsx | 103 +++++++++++ .../InputWrapper/InputWrapper.style.tsx | 49 +++++ .../molecules/InputWrapper/InputWrapper.tsx | 114 ++++++++++++ .../molecules/InputWrapper/index.ts | 1 + .../molecules/MenuList/MenuList.stories.tsx | 60 ++++++ .../molecules/MenuList/MenuList.style.tsx | 18 ++ .../molecules/MenuList/MenuList.tsx | 23 +++ .../molecules/Modal/Modal.stories.tsx | 85 +++++++++ .../molecules/Modal/Modal.style.tsx | 15 ++ src/components/molecules/Modal/Modal.tsx | 46 +++++ src/components/molecules/Modal/index.ts | 1 + .../IDisableDateSettings.model.ts | 7 + .../IExternalMonthWithDays.model.ts | 17 ++ .../MonthWithDays/MonthWithDays.stories.tsx | 111 +++++++++++ .../MonthWithDays/MonthWithDays.style.tsx | 12 ++ .../molecules/MonthWithDays/MonthWithDays.tsx | 97 ++++++++++ .../molecules/MonthWithDays/day.model.ts | 12 ++ .../helperFunctions/createDayFunction.ts | 65 +++++++ .../helperFunctions/createDaysOfMonth.ts | 40 ++++ .../molecules/MonthWithDays/index.ts | 1 + .../NumberInput/NumberInput.stories.tsx | 53 ++++++ .../NumberInput/NumberInput.styled.tsx | 27 +++ .../molecules/NumberInput/NumberInput.tsx | 89 +++++++++ src/components/molecules/NumberInput/index.ts | 1 + .../molecules/Paginator/Paginator.stories.tsx | 59 ++++++ .../molecules/Paginator/Paginator.style.tsx | 41 +++++ .../molecules/Paginator/Paginator.tsx | 72 ++++++++ src/components/molecules/Paginator/index.ts | 1 + .../PasswordInput/PasswordInput.stories.tsx | 64 +++++++ .../molecules/PasswordInput/PasswordInput.tsx | 56 ++++++ .../molecules/PasswordInput/index.ts | 1 + .../RangeCalendar/IDateArray.model.ts | 2 + .../RangeCalendar/RangeCalendar.stories.tsx | 114 ++++++++++++ .../RangeCalendar/RangeCalendar.style.tsx | 36 ++++ .../molecules/RangeCalendar/RangeCalendar.tsx | 106 +++++++++++ .../helperFunctions/selectDayFunction.ts | 64 +++++++ .../helperFunctions/useSelectedDates.ts | 37 ++++ .../helperFunctions/useVisibleMonths.ts | 44 +++++ .../molecules/RangeCalendar/index.ts | 1 + .../ScalingSection/ScalingSection.stories.tsx | 44 +++++ .../ScalingSection/ScalingSection.tsx | 31 ++++ .../molecules/ScalingSection/index.ts | 1 + .../molecules/SearchBar/SearchBar.stories.tsx | 71 +++++++ .../molecules/SearchBar/SearchBar.tsx | 81 ++++++++ src/components/molecules/SearchBar/index.ts | 1 + .../SearchBarList/SearchBarList.stories.tsx | 52 ++++++ .../SearchBarList/SearchBarList.style.tsx | 20 ++ .../molecules/SearchBarList/SearchBarList.tsx | 32 ++++ .../molecules/SearchBarList/index.ts | 1 + .../SingleInputs/SingleInputs.stories.tsx | 85 +++++++++ .../SingleInputs/SingleInputs.style.tsx | 22 +++ .../molecules/SingleInputs/SingleInputs.tsx | 148 +++++++++++++++ .../molecules/SingleInputs/index.ts | 1 + .../IToastMessage.model.tsx | 13 ++ .../SingleToastMessage/SingleToastMessage.mdx | 58 ++++++ .../SingleToastMessage.stories.tsx | 70 +++++++ .../SingleToastMessage.style.tsx | 70 +++++++ .../SingleToastMessage/SingleToastMessage.tsx | 44 +++++ .../molecules/SingleToastMessage/index.ts | 1 + .../SwipeUpModal/ISwipeUpModal.model.ts | 13 ++ .../SwipeUpModal/SwipeUpModal.stories.tsx | 89 +++++++++ .../SwipeUpModal/SwipeUpModal.style.tsx | 48 +++++ .../molecules/SwipeUpModal/SwipeUpModal.tsx | 132 +++++++++++++ .../molecules/SwipeUpModal/index.ts | 1 + .../SwitchList/SwitchList.stories.tsx | 64 +++++++ .../molecules/SwitchList/SwitchList.style.tsx | 15 ++ .../molecules/SwitchList/SwitchList.tsx | 64 +++++++ .../molecules/TabSwitch/TabSwitch.model.ts | 24 +++ .../molecules/TabSwitch/TabSwitch.style.tsx | 70 +++++++ .../molecules/TabSwitch/TabSwitch.tsx | 108 +++++++++++ .../molecules/TextInput/TextInput.stories.tsx | 41 +++++ .../molecules/TextInput/TextInput.tsx | 25 +++ src/components/molecules/TextInput/index.ts | 1 + .../FancyBoxSet/FancyBoxSet.stories.tsx | 123 +++++++++++++ .../organisms/FancyBoxSet/FancyBoxSet.tsx | 48 +++++ src/components/organisms/FancyBoxSet/index.ts | 1 + .../FancyButton/FancyButton.stories.tsx | 130 +++++++++++++ .../FancyButton/FancyButton.style.tsx | 36 ++++ .../organisms/FancyButton/FancyButton.tsx | 78 ++++++++ .../FancyButton/IFancyButton.model.ts | 12 ++ src/components/organisms/FancyButton/index.ts | 1 + .../organisms/FancyChip/FancyChip.model.ts | 1 + .../organisms/FancyChip/FancyChip.stories.tsx | 150 +++++++++++++++ .../organisms/FancyChip/FancyChip.style.tsx | 107 +++++++++++ .../organisms/FancyChip/FancyChip.tsx | 106 +++++++++++ src/components/organisms/FancyChip/index.ts | 1 + .../FancyChipList/FancyChipList.stories.tsx | 74 ++++++++ .../FancyChipList/FancyChipList.style.tsx | 25 +++ .../organisms/FancyChipList/FancyChipList.tsx | 134 ++++++++++++++ .../FancyCodeVerificationInput.mdx | 73 ++++++++ .../FancyCodeVerificationInput.stories.tsx | 120 ++++++++++++ .../FancyCodeVerificationInput.style.tsx | 43 +++++ .../FancyCodeVerificationInput.tsx | 58 ++++++ .../FancyCodeVerificationInput/index.ts | 1 + .../FancyColorPicker.stories.tsx | 71 +++++++ .../FancyColorPicker/FancyColorPicker.tsx | 103 +++++++++++ .../FancyColorPicker/colorPickerUtils.ts | 47 +++++ .../organisms/FancyColorPicker/index.ts | 1 + .../FancyDateInput/FancyDateInput.stories.tsx | 157 ++++++++++++++++ .../FancyDateInput/FancyDateInput.style.tsx | 48 +++++ .../FancyDateInput/FancyDateInput.tsx | 53 ++++++ .../organisms/FancyDateInput/index.ts | 1 + .../FancyDatePicker.stories.tsx | 132 +++++++++++++ .../FancyDatePicker/FancyDatePicker.style.tsx | 31 ++++ .../FancyDatePicker/FancyDatePicker.tsx | 86 +++++++++ .../organisms/FancyDatePicker/index.ts | 1 + .../FancyDropDownMenue.stories.tsx | 103 +++++++++++ .../FancyDropDownMenue/FancyDropDownMenue.tsx | 40 ++++ .../organisms/FancyDropDownMenue/index.ts | 1 + .../FancyDropDownSelect.stories.tsx | 148 +++++++++++++++ .../FancyDropDownSelect.tsx | 55 ++++++ .../organisms/FancyDropDownSelect/index.ts | 1 + .../EditBarItemsStructure/ButtonSettings.tsx | 105 +++++++++++ .../EditBarItemsStructure/EditBarIcons.tsx | 89 +++++++++ .../IEditbarObjectSturcture.model.ts | 58 ++++++ .../objectSettingsSection.ts | 157 ++++++++++++++++ .../FancyEditBar/EditBarSettings.tsx | 17 ++ .../FancyEditBar/FancyEditBar.state.tsx | 44 +++++ .../organisms/FancyEditBar/FancyEditBar.tsx | 65 +++++++ .../functions/ElementsObjectMapper.tsx | 65 +++++++ .../organisms/FancyEditBar/index.ts | 1 + .../FancyEditBar/languagesEditBar.ts | 23 +++ .../organisms/FancyModal/FancyModal.mdx | 72 ++++++++ .../organisms/FancyModal/FancyModal.state.tsx | 44 +++++ .../FancyModal/FancyModal.stories.tsx | 72 ++++++++ .../organisms/FancyModal/FancyModal.tsx | 53 ++++++ src/components/organisms/FancyModal/index.ts | 1 + .../FancyNumberInput/FancyNumberInput.tsx | 55 ++++++ .../organisms/FancyNumberInput/index.ts | 1 + .../FancyPasswordInput.stories.tsx | 102 +++++++++++ .../FancyPasswordInput/FancyPasswordInput.tsx | 54 ++++++ .../organisms/FancyPasswordInput/index.ts | 1 + .../organisms/FancyRadio/FancyRadio.tsx | 33 ++++ src/components/organisms/FancyRadio/index.ts | 1 + .../FancyRangeSlider.model.ts | 19 ++ .../FancyRangeSlider.stories.tsx | 133 ++++++++++++++ .../FancyRangeSlider.style.tsx | 33 ++++ .../FancyRangeSlider/FancyRangeSlider.tsx | 97 ++++++++++ .../organisms/FancyRangeSlider/index.ts | 1 + .../FancySearchBar/FancySearchBar.stories.tsx | 82 +++++++++ .../FancySearchBar/FancySearchBar.tsx | 74 ++++++++ .../organisms/FancySearchBar/index.ts | 1 + .../FancySwipeUpModal/FancySwipeUpModal.mdx | 80 ++++++++ .../FancySwipeUpModal.state.ts | 46 +++++ .../FancySwipeUpModal.stories.tsx | 135 ++++++++++++++ .../FancySwipeUpModal/FancySwipeUpModal.tsx | 43 +++++ .../organisms/FancySwipeUpModal/index.ts | 1 + .../FancyTextInput/FancyTextInput.stories.tsx | 97 ++++++++++ .../FancyTextInput/FancyTextInput.tsx | 51 ++++++ .../organisms/FancyTextInput/index.ts | 1 + .../FancyToastMessage.state.tsx | 23 +++ .../FancyToastMessage.stories.tsx | 49 +++++ .../FancyToastMessage.style.tsx | 14 ++ .../FancyToastMessage/FancyToastMessage.tsx | 60 ++++++ .../organisms/FancyToastMessage/index.ts | 1 + .../FancyBottomBarIcon.stories.tsx | 103 +++++++++++ .../FancyBottomBarIcon.style.tsx | 17 ++ .../FancyBottomBarIcon/FancyBottomBarIcon.tsx | 25 +++ .../templates/FancyBottomBarIcon/index.ts | 1 + .../FancyFlexBox/FancyFlexBox.model.ts | 12 ++ .../FancyFlexBox/FancyFlexBox.style.tsx | 26 +++ .../templates/FancyFlexBox/FancyFlexBox.tsx | 39 ++++ .../templates/FancyFlexBox/index.ts | 1 + .../templates/FancyGrid/FancyGrid.mdx | 64 +++++++ .../templates/FancyGrid/FancyGrid.stories.tsx | 70 +++++++ .../templates/FancyGrid/FancyGrid.tsx | 35 ++++ .../FancyGrid/FancyGridItem/FancyGridItem.mdx | 46 +++++ .../FancyGridItem/FancyGridItem.stories.tsx | 45 +++++ .../FancyGrid/FancyGridItem/FancyGridItem.tsx | 19 ++ .../FancyGrid/FancyGridItem/index.ts | 1 + src/components/templates/FancyGrid/index.ts | 1 + .../FancyHandyNav/FancyHandyNav.store.ts | 19 ++ .../FancyHandyNav/FancyHandyNav.stories.tsx | 151 +++++++++++++++ .../templates/FancyHandyNav/FancyHandyNav.tsx | 82 +++++++++ .../templates/FancyHandyNav/index.ts | 1 + .../FancyInfoCard/FancyInfoCard.stories.tsx | 68 +++++++ .../templates/FancyInfoCard/FancyInfoCard.tsx | 57 ++++++ .../templates/FancyInfoCard/index.ts | 1 + .../FancyMenu/FancyMenu.stories.tsx | 65 +++++++ .../FancyMenu/FancyMenu.tsx | 21 +++ .../FancyMenueComponent/FancyMenu/index.ts | 1 + .../FancyMenuItem/FancyMenuItem.stories.tsx | 84 +++++++++ .../FancyMenuItem/FancyMenuItem.tsx | 23 +++ .../FancyMenuItem/index.ts | 1 + .../FancyRadioList/FancyRadioList.model.ts | 15 ++ .../FancyRadioList/FancyRadioList.stories.tsx | 95 ++++++++++ .../FancyRadioList/FancyRadioList.style.tsx | 0 .../FancyRadioList/FancyRadioList.tsx | 76 ++++++++ .../templates/FancyRadioList/index.ts | 1 + .../FancyDateDropDown/FancyDateDropDown.tsx | 56 ++++++ .../Inputs/FancyDateDropDown/index.ts | 1 + .../hooks/useIntersectionObserver/index.ts | 1 + .../useIntersectionObserver.tsx | 33 ++++ .../utils/hooks/useSlider/IUseSlider.model.ts | 29 +++ .../utils/hooks/useSlider/useSilder.tsx | 173 ++++++++++++++++++ .../utils/hooks/useWindowDimensions/index.ts | 1 + .../useWindowDimensions.tsx | 27 +++ .../utils/variables/colorFormats.ts | 6 + 537 files changed, 21718 insertions(+) create mode 100644 src/components/atoms/ActionWrapper/ActionWrapper.mdx create mode 100644 src/components/atoms/ActionWrapper/ActionWrapper.tsx create mode 100644 src/components/atoms/ActionWrapper/index.ts create mode 100644 src/components/atoms/AlignedInputLabel/AlignedInputLabel.stories.tsx create mode 100644 src/components/atoms/AlignedInputLabel/AlignedInputLabel.tsx create mode 100644 src/components/atoms/AlignedInputLabel/TalignedInputLabel.model.ts create mode 100644 src/components/atoms/AlignedInputLabel/index.ts create mode 100644 src/components/atoms/AnimatedLabel/AnimatedInputLabel.stories.tsx create mode 100644 src/components/atoms/AnimatedLabel/AnimatedInputLabel.tsx create mode 100644 src/components/atoms/AnimatedLabel/TAnimatedInputLabel.model.ts create mode 100644 src/components/atoms/AnimatedLabel/index.ts create mode 100644 src/components/atoms/AvilableDot/AvailableDot.tsx create mode 100644 src/components/atoms/AvilableDot/AvilableDot.stories.tsx create mode 100644 src/components/atoms/AvilableDot/index.ts create mode 100644 src/components/atoms/BackDrop/BackDrop.stories.tsx create mode 100644 src/components/atoms/BackDrop/BackDrop.tsx create mode 100644 src/components/atoms/BackDrop/index.ts create mode 100644 src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.stories.tsx create mode 100644 src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.tsx create mode 100644 src/components/atoms/CheckerBoardPattern/index.ts create mode 100644 src/components/atoms/ColorDisplay/ColorDisplay.stories.tsx create mode 100644 src/components/atoms/ColorDisplay/ColorDisplay.style.tsx create mode 100644 src/components/atoms/ColorDisplay/ColorDisplay.tsx create mode 100644 src/components/atoms/ColorDisplay/index.ts create mode 100644 src/components/atoms/ColorIndicator/ColorIndicator.style.tsx create mode 100644 src/components/atoms/ColorIndicator/ColorIndicator.tsx create mode 100644 src/components/atoms/ColorIndicator/Colorindicator.stories.tsx create mode 100644 src/components/atoms/ColorIndicator/index.ts create mode 100644 src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.mdx create mode 100644 src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.tsx create mode 100644 src/components/atoms/CustomeDropDownNOTINUSE/CustomeDropDown.tsx create mode 100644 src/components/atoms/DateNumberAtom/DateNumberAtom.stories.tsx create mode 100644 src/components/atoms/DateNumberAtom/DateNumberAtom.style.tsx create mode 100644 src/components/atoms/DateNumberAtom/DateNumberAtom.tsx create mode 100644 src/components/atoms/DateNumberAtom/index.ts create mode 100644 src/components/atoms/DateOutput/DateOutput.stories.tsx create mode 100644 src/components/atoms/DateOutput/DateOutput.tsx create mode 100644 src/components/atoms/DateOutput/index.ts create mode 100644 src/components/atoms/DropDownSelect/DropDownSelect.model.ts create mode 100644 src/components/atoms/DropDownSelect/DropDownSelect.stories.tsx create mode 100644 src/components/atoms/DropDownSelect/DropDownSelect.style.tsx create mode 100644 src/components/atoms/DropDownSelect/DropDownSelect.tsx create mode 100644 src/components/atoms/DropDownSelect/index.ts create mode 100644 src/components/atoms/EditBarModal/EditBarModal.stories.tsx create mode 100644 src/components/atoms/EditBarModal/EditBarModal.style.tsx create mode 100644 src/components/atoms/EditBarModal/EditBarModal.tsx create mode 100644 src/components/atoms/EditBarModal/IEditbarModal.model.ts create mode 100644 src/components/atoms/EditBarModal/index.ts create mode 100644 src/components/atoms/FancyActionWrapper/FancyActionWrapper.style.tsx create mode 100644 src/components/atoms/FancyActionWrapper/FancyActionWrapper.tsx create mode 100644 src/components/atoms/FancyBox/FancyBox.mdx create mode 100644 src/components/atoms/FancyBox/FancyBox.model.ts create mode 100644 src/components/atoms/FancyBox/FancyBox.stories.tsx create mode 100644 src/components/atoms/FancyBox/FancyBox.style.tsx create mode 100644 src/components/atoms/FancyBox/FancyBox.tsx create mode 100644 src/components/atoms/FancyBox/index.ts create mode 100644 src/components/atoms/FancyCard/Card.model.ts create mode 100644 src/components/atoms/FancyCard/FancyCard.style.tsx create mode 100644 src/components/atoms/FancyCard/FancyCard.tsx create mode 100644 src/components/atoms/FancyCard/FancyCrad.stories.tsx create mode 100644 src/components/atoms/FancyCard/index.ts create mode 100644 src/components/atoms/FancyImage/FancyImage.stories.tsx create mode 100644 src/components/atoms/FancyImage/FancyImage.style.tsx create mode 100644 src/components/atoms/FancyImage/FancyImage.tsx create mode 100644 src/components/atoms/FancyImage/index.ts create mode 100644 src/components/atoms/FancyLI/FancyLI.stories.tsx create mode 100644 src/components/atoms/FancyLI/FancyLI.tsx create mode 100644 src/components/atoms/FancyLI/index.ts create mode 100644 src/components/atoms/FancyLine/FancyLine.stories.tsx create mode 100644 src/components/atoms/FancyLine/FancyLine.tsx create mode 100644 src/components/atoms/FancyLine/index.ts create mode 100644 src/components/atoms/FancyLoadingBar/FancyLoadingBar.stories.tsx create mode 100644 src/components/atoms/FancyLoadingBar/FancyLoadingBar.tsx create mode 100644 src/components/atoms/FancyLoadingBar/index.ts create mode 100644 src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.stories.tsx create mode 100644 src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.tsx create mode 100644 src/components/atoms/FancyLoadingSpinner/index.ts create mode 100644 src/components/atoms/FancyProfilePicture/FancyProfilePicture.stories.tsx create mode 100644 src/components/atoms/FancyProfilePicture/FancyProfilePicture.style.tsx create mode 100644 src/components/atoms/FancyProfilePicture/FancyProfilePicture.tsx create mode 100644 src/components/atoms/FancyProfilePicture/index.ts create mode 100644 src/components/atoms/FancySVGAtom/FancySVGAtom.model.ts create mode 100644 src/components/atoms/FancySVGAtom/FancySVGAtom.stories.tsx create mode 100644 src/components/atoms/FancySVGAtom/FancySVGAtom.tsx create mode 100644 src/components/atoms/FancySVGAtom/index.ts create mode 100644 src/components/atoms/FancyVideo/FancyVideo.stories.tsx create mode 100644 src/components/atoms/FancyVideo/FancyVideo.tsx create mode 100644 src/components/atoms/FancyVideo/index.ts create mode 100644 src/components/atoms/FancyXButton/FancyXButton.stories.tsx create mode 100644 src/components/atoms/FancyXButton/FancyXButton.tsx create mode 100644 src/components/atoms/FancyXButton/index.ts create mode 100644 src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.stories.tsx create mode 100644 src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.tsx create mode 100644 src/components/atoms/ImageVideoOverlay/index.ts create mode 100644 src/components/atoms/InputLabel/InputLabel.stories.tsx create mode 100644 src/components/atoms/InputLabel/InputLabel.tsx create mode 100644 src/components/atoms/InputLabel/index.ts create mode 100644 src/components/atoms/InputUnderline/InputUnderline.stories.tsx create mode 100644 src/components/atoms/InputUnderline/InputUnderline.tsx create mode 100644 src/components/atoms/InputUnderline/index.ts create mode 100644 src/components/atoms/ListDivider/ListDivider.model.ts create mode 100644 src/components/atoms/ListDivider/ListDivider.stories.tsx create mode 100644 src/components/atoms/ListDivider/ListDivider.style.tsx create mode 100644 src/components/atoms/ListDivider/ListDivider.tsx create mode 100644 src/components/atoms/ListDivider/index.ts create mode 100644 src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.stories.tsx create mode 100644 src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.tsx create mode 100644 src/components/atoms/MenuItem/MenuItem.stories.tsx create mode 100644 src/components/atoms/MenuItem/MenuItem.style.tsx create mode 100644 src/components/atoms/MenuItem/MenuItem.tsx create mode 100644 src/components/atoms/MenuItem/index.ts create mode 100644 src/components/atoms/PageNumberList/PageNumberList.stories.tsx create mode 100644 src/components/atoms/PageNumberList/PageNumberList.style.tsx create mode 100644 src/components/atoms/PageNumberList/PageNumberList.tsx create mode 100644 src/components/atoms/PasswordEye/PasswordEye.stories.tsx create mode 100644 src/components/atoms/PasswordEye/PasswordEye.tsx create mode 100644 src/components/atoms/PasswordEye/index.ts create mode 100644 src/components/atoms/ProgressBar/ProgressBar.stories.tsx create mode 100644 src/components/atoms/ProgressBar/ProgressBar.tsx create mode 100644 src/components/atoms/ProgressBar/index.ts create mode 100644 src/components/atoms/RawCheckbox/RawCheckbox.model.ts create mode 100644 src/components/atoms/RawCheckbox/RawCheckbox.stories.tsx create mode 100644 src/components/atoms/RawCheckbox/RawCheckbox.style.tsx create mode 100644 src/components/atoms/RawCheckbox/RawCheckbox.tsx create mode 100644 src/components/atoms/RawCheckbox/index.ts create mode 100644 src/components/atoms/RawInput/RawInput.stories.tsx create mode 100644 src/components/atoms/RawInput/RawInput.tsx create mode 100644 src/components/atoms/RawInput/index.ts create mode 100644 src/components/atoms/RawLI/RawLI.tsx create mode 100644 src/components/atoms/RawLI/index.ts create mode 100644 src/components/atoms/RawNav/RawNav.tsx create mode 100644 src/components/atoms/RawRadio/RawRadio.stories.tsx create mode 100644 src/components/atoms/RawRadio/RawRadio.style.tsx create mode 100644 src/components/atoms/RawRadio/RawRadio.tsx create mode 100644 src/components/atoms/RawRadio/index.ts create mode 100644 src/components/atoms/RawSlider/RawSlider.stories.tsx create mode 100644 src/components/atoms/RawSlider/RawSlider.style.tsx create mode 100644 src/components/atoms/RawSlider/RawSlider.tsx create mode 100644 src/components/atoms/RawSlider/index.ts create mode 100644 src/components/atoms/RawUL/RawUL.tsx create mode 100644 src/components/atoms/ScrollableBar/ScrollableBar.stories.tsx create mode 100644 src/components/atoms/ScrollableBar/ScrollableBar.tsx create mode 100644 src/components/atoms/ScrollableBar/SrollableBar.style.tsx create mode 100644 src/components/atoms/ScrollableBar/index.ts create mode 100644 src/components/atoms/SimpleDialog/SimpleDialog.stories.tsx create mode 100644 src/components/atoms/SimpleDialog/SimpleDialog.tsx create mode 100644 src/components/atoms/SimpleDialog/index.ts create mode 100644 src/components/atoms/SingleInputAtom/SingleInputAtom.stories.tsx create mode 100644 src/components/atoms/SingleInputAtom/SingleInputAtom.tsx create mode 100644 src/components/atoms/SingleInputAtom/index.ts create mode 100644 src/components/atoms/SliderMarker/SliderMarker.stories.tsx create mode 100644 src/components/atoms/SliderMarker/SliderMarker.style.tsx create mode 100644 src/components/atoms/SliderMarker/SliderMarker.tsx create mode 100644 src/components/atoms/SliderMarker/index.ts create mode 100644 src/components/atoms/Slot/Slot.stories.tsx create mode 100644 src/components/atoms/Slot/Slot.tsx create mode 100644 src/components/atoms/Slot/index.ts create mode 100644 src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.stories.tsx create mode 100644 src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.style.tsx create mode 100644 src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.tsx create mode 100644 src/components/atoms/SpeedDialMenueItem/index.ts create mode 100644 src/components/atoms/SwipeUpContainer/SwipeUpContainer.stories.tsx create mode 100644 src/components/atoms/SwipeUpContainer/SwipeUpContainer.tsx create mode 100644 src/components/atoms/SwipeUpContainer/index.ts create mode 100644 src/components/atoms/SwipeUpDash/SwipeUpDash.stories.tsx create mode 100644 src/components/atoms/SwipeUpDash/SwipeUpDash.tsx create mode 100644 src/components/atoms/SwipeUpDash/index.ts create mode 100644 src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.stories.tsx create mode 100644 src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.style.tsx create mode 100644 src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.tsx create mode 100644 src/components/atoms/SwitchActiveIndicator/utils/generateBlob.ts create mode 100644 src/components/atoms/SwitchActiveIndicator/utils/generateLines.ts create mode 100644 src/components/atoms/Typography/Typography.stories.tsx create mode 100644 src/components/atoms/Typography/Typography.tsx create mode 100644 src/components/atoms/Typography/TypographyFontVariations.style.tsx create mode 100644 src/components/atoms/Typography/TypographyStyleVariants.tsx create mode 100644 src/components/atoms/Typography/index.ts create mode 100644 src/components/atoms/WeekDays/WeekDays.stories.tsx create mode 100644 src/components/atoms/WeekDays/WeekDays.tsx create mode 100644 src/components/atoms/WeekDays/index.ts create mode 100644 src/components/atoms/YearSelector/YearSelector.stories.tsx create mode 100644 src/components/atoms/YearSelector/YearSelector.style.tsx create mode 100644 src/components/atoms/YearSelector/YearSelector.tsx create mode 100644 src/components/atoms/YearSelector/index.ts create mode 100644 src/components/icons/SVGCheckMark/SVGCheckMark.tsx create mode 100644 src/components/icons/SVGCheckMark/index.ts create mode 100644 src/components/icons/SVGChevronLeft/SVGChevronLeft.tsx create mode 100644 src/components/icons/SVGChevronLeft/index.ts create mode 100644 src/components/icons/SVGChevronRight/SVGChevronRight.tsx create mode 100644 src/components/icons/SVGChevronRight/index.ts create mode 100644 src/components/icons/SVGClipBoardIcon/SVGClipBoardIcon.tsx create mode 100644 src/components/icons/SVGClipBoardIcon/index.ts create mode 100644 src/components/icons/SVGClipBoardIconChecked/SVGClipBoardIconChecked.tsx create mode 100644 src/components/icons/SVGClipBoardIconChecked/index.ts create mode 100644 src/components/icons/SVGEyeCrossed/SVGEyeCrossed.tsx create mode 100644 src/components/icons/SVGEyeCrossed/index.ts create mode 100644 src/components/icons/SVGEyeOpen/SVGEyeOpen.tsx create mode 100644 src/components/icons/SVGEyeOpen/index.ts create mode 100644 src/components/icons/SVGLoadingArrows/SVGLoadingArrows.tsx create mode 100644 src/components/icons/SVGLoadingArrows/index.ts create mode 100644 src/components/icons/SVGPlus/SVGPlus.tsx create mode 100644 src/components/icons/SVGPlus/index.ts create mode 100644 src/components/icons/SVGSearch/SVGSearch.tsx create mode 100644 src/components/icons/SVGSearch/index.ts create mode 100644 src/components/icons/SVGXCircle/SVGXCircle.tsx create mode 100644 src/components/icons/SVGXCircle/index.ts create mode 100644 src/components/molecules/BottomBar/BottomBar.stories.tsx create mode 100644 src/components/molecules/BottomBar/BottomBar.style.tsx create mode 100644 src/components/molecules/BottomBar/BottomBar.tsx create mode 100644 src/components/molecules/BottomBar/index.ts create mode 100644 src/components/molecules/BottomBarIcon/BottomBarIcon.stories.tsx create mode 100644 src/components/molecules/BottomBarIcon/BottomBarIcon.style.tsx create mode 100644 src/components/molecules/BottomBarIcon/BottomBarIcon.tsx create mode 100644 src/components/molecules/Button/Button.stories.tsx create mode 100644 src/components/molecules/Button/Button.style.tsx create mode 100644 src/components/molecules/Button/Button.tsx create mode 100644 src/components/molecules/Chip/Chip.stories.tsx create mode 100644 src/components/molecules/Chip/Chip.style.tsx create mode 100644 src/components/molecules/Chip/Chip.tsx create mode 100644 src/components/molecules/ChipList/ChipList.stories.tsx create mode 100644 src/components/molecules/ChipList/ChipList.style.tsx create mode 100644 src/components/molecules/ChipList/ChipList.tsx create mode 100644 src/components/molecules/ComponentAndActionWrapper/ComponentAndActionWrapper.mdx create mode 100644 src/components/molecules/ComponentAndActionWrapper/ComponentAndActionWrapper.tsx create mode 100644 src/components/molecules/DateInput/DateInput.stories.tsx create mode 100644 src/components/molecules/DateInput/DateInput.tsx create mode 100644 src/components/molecules/DateInput/index.ts create mode 100644 src/components/molecules/DateNumberWithStatus/DateNumberWithStatus.stories.tsx create mode 100644 src/components/molecules/DateNumberWithStatus/DateNumberWithStatus.tsx create mode 100644 src/components/molecules/DateNumberWithStatus/index.ts create mode 100644 src/components/molecules/DateOutputFromTo/DateOutputFromTo.state.ts create mode 100644 src/components/molecules/DateOutputFromTo/DateOutputFromTo.stories.tsx create mode 100644 src/components/molecules/DateOutputFromTo/DateOutputFromTo.tsx create mode 100644 src/components/molecules/DateOutputFromTo/index.ts create mode 100644 src/components/molecules/DynamicBottomScrollBar/DynamicBottomBar.stories.tsx create mode 100644 src/components/molecules/DynamicBottomScrollBar/DynamicBottomScrollBar.style.tsx create mode 100644 src/components/molecules/DynamicBottomScrollBar/DynamicBottomScrollBar.tsx create mode 100644 src/components/molecules/DynamicBottomScrollBar/index.ts create mode 100644 src/components/molecules/EditBar/EditBar.state.tsx create mode 100644 src/components/molecules/EditBar/EditBar.tsx create mode 100644 src/components/molecules/EditBar/index.ts create mode 100644 src/components/molecules/EditBar/todo.txt create mode 100644 src/components/molecules/FancyCheckbox/FancyCheckbox.model.ts create mode 100644 src/components/molecules/FancyCheckbox/FancyCheckbox.stories.tsx create mode 100644 src/components/molecules/FancyCheckbox/FancyCheckbox.style.tsx create mode 100644 src/components/molecules/FancyCheckbox/FancyCheckbox.tsx create mode 100644 src/components/molecules/FancyCheckbox/index.ts create mode 100644 src/components/molecules/FancyColorArea/FancyColorArea.stories.tsx create mode 100644 src/components/molecules/FancyColorArea/FancyColorArea.style.tsx create mode 100644 src/components/molecules/FancyColorArea/FancyColorArea.tsx create mode 100644 src/components/molecules/FancyColorArea/index.ts create mode 100644 src/components/molecules/FancyColorOutput/ColorTransformator.ts create mode 100644 src/components/molecules/FancyColorOutput/FancyColorOutput.model.ts create mode 100644 src/components/molecules/FancyColorOutput/FancyColorOutput.stories.tsx create mode 100644 src/components/molecules/FancyColorOutput/FancyColorOutput.style.tsx create mode 100644 src/components/molecules/FancyColorOutput/FancyColorOutput.tsx create mode 100644 src/components/molecules/FancyColorOutput/InputFields/AdaptInput/AdaptInputs.tsx create mode 100644 src/components/molecules/FancyColorOutput/InputFields/AdaptInput/index.ts create mode 100644 src/components/molecules/FancyColorOutput/InputFields/InputFields.style.tsx create mode 100644 src/components/molecules/FancyColorOutput/InputFields/InputFields.tsx create mode 100644 src/components/molecules/FancyColorOutput/InputFields/index.ts create mode 100644 src/components/molecules/FancyColorOutput/index.ts create mode 100644 src/components/molecules/FancyContent/FancyContent.stories.tsx create mode 100644 src/components/molecules/FancyContent/FancyContent.tsx create mode 100644 src/components/molecules/FancyContent/utils/FancyContentIcon.tsx create mode 100644 src/components/molecules/FancyContent/utils/FancyContentText.tsx create mode 100644 src/components/molecules/FancyContent/utils/sizeSettings.ts create mode 100644 src/components/molecules/FancyContentCard/FancyContentCard.stories.tsx create mode 100644 src/components/molecules/FancyContentCard/FancyContentCard.tsx create mode 100644 src/components/molecules/FancyContentCard/index.ts create mode 100644 src/components/molecules/FancyDropDownUL/FancyDropDownUL.stories.tsx create mode 100644 src/components/molecules/FancyDropDownUL/FancyDropDownUL.style.tsx create mode 100644 src/components/molecules/FancyDropDownUL/FancyDropDownUL.tsx create mode 100644 src/components/molecules/FancyDropDownUL/index.ts create mode 100644 src/components/molecules/FancyHueSlider/FancyHueSlider.stories.tsx create mode 100644 src/components/molecules/FancyHueSlider/FancyHueSlider.style.tsx create mode 100644 src/components/molecules/FancyHueSlider/FancyHueSlider.tsx create mode 100644 src/components/molecules/FancyHueSlider/index.ts create mode 100644 src/components/molecules/FancyImageText/FancyImageText.stories.tsx create mode 100644 src/components/molecules/FancyImageText/FancyImageText.tsx create mode 100644 src/components/molecules/FancyImageText/index.ts create mode 100644 src/components/molecules/FancyListBox/FancyListBox.mdx create mode 100644 src/components/molecules/FancyListBox/FancyListBox.stories.tsx create mode 100644 src/components/molecules/FancyListBox/FancyListBox.style.tsx create mode 100644 src/components/molecules/FancyListBox/FancyListBox.tsx create mode 100644 src/components/molecules/FancyListBox/FancyListBoxItem.tsx create mode 100644 src/components/molecules/FancyListBox/index.ts create mode 100644 src/components/molecules/FancyMiniProfile/FancyMiniProfile.stories.tsx create mode 100644 src/components/molecules/FancyMiniProfile/FancyMiniProfile.style.tsx create mode 100644 src/components/molecules/FancyMiniProfile/FancyMiniProfile.tsx create mode 100644 src/components/molecules/FancyMiniProfile/index.ts create mode 100644 src/components/molecules/FancyModalHeadLine/FancyModalHeadLine.model.ts create mode 100644 src/components/molecules/FancyModalHeadLine/FancyModalHeadLine.stories.tsx create mode 100644 src/components/molecules/FancyModalHeadLine/FancyModalHeadLine.tsx create mode 100644 src/components/molecules/FancyModalHeadLine/index.ts create mode 100644 src/components/molecules/FancyOpacitySlider/FancyOpacitySlider.stories.tsx create mode 100644 src/components/molecules/FancyOpacitySlider/FancyOpacitySlider.style.tsx create mode 100644 src/components/molecules/FancyOpacitySlider/FancyOpacitySlider.tsx create mode 100644 src/components/molecules/FancyOpacitySlider/index.ts create mode 100644 src/components/molecules/FancyPageList/FancyPageList.stories.tsx create mode 100644 src/components/molecules/FancyPageList/FancyPageList.tsx create mode 100644 src/components/molecules/FancyPageList/index.ts create mode 100644 src/components/molecules/FancyProgressBar/FancyProgressBar.stories.tsx create mode 100644 src/components/molecules/FancyProgressBar/FancyProgressBar.tsx create mode 100644 src/components/molecules/FancyProgressBar/index.ts create mode 100644 src/components/molecules/FancySelectWrapper/FancySelectWrapper.style.tsx create mode 100644 src/components/molecules/FancySelectWrapper/FancySelectWrapper.tsx create mode 100644 src/components/molecules/FancySpeedDailButton/FancySpeedDailButton.stories.tsx create mode 100644 src/components/molecules/FancySpeedDailButton/FancySpeedDailButton.tsx create mode 100644 src/components/molecules/FancySpeedDailButton/SpeedDailButton.style.tsx create mode 100644 src/components/molecules/FancySpeedDailButton/index.ts create mode 100644 src/components/molecules/FancyTabSwitch/FancyTabSwitch.stories.tsx create mode 100644 src/components/molecules/FancyTabSwitch/FancyTabSwitch.style.tsx create mode 100644 src/components/molecules/FancyTabSwitch/FancyTabSwitch.tsx create mode 100644 src/components/molecules/FancyTabSwitch/index.ts create mode 100644 src/components/molecules/FancyTabSwitch/utils/generateColorDesign.ts create mode 100644 src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.model.ts create mode 100644 src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.stories.tsx create mode 100644 src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.style.tsx create mode 100644 src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.tsx create mode 100644 src/components/molecules/FancyTabSwitchButton/index.ts create mode 100644 src/components/molecules/FancyVideoText/FancyVideoText.stories.tsx create mode 100644 src/components/molecules/FancyVideoText/FancyVideoText.tsx create mode 100644 src/components/molecules/FancyVideoText/index.ts create mode 100644 src/components/molecules/Fieldset/Fieldset.stories.tsx create mode 100644 src/components/molecules/Fieldset/Fieldset.style.tsx create mode 100644 src/components/molecules/Fieldset/Fieldset.tsx create mode 100644 src/components/molecules/Fieldset/index.ts create mode 100644 src/components/molecules/Header/Header.stories.tsx create mode 100644 src/components/molecules/Header/Header.style.tsx create mode 100644 src/components/molecules/Header/Header.tsx create mode 100644 src/components/molecules/Header/index.ts create mode 100644 src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.stories.tsx create mode 100644 src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.tsx create mode 100644 src/components/molecules/HeaderTitleWithLogo/index.ts create mode 100644 src/components/molecules/InfoCard/InfoCard.stories.tsx create mode 100644 src/components/molecules/InfoCard/InfoCard.tsx create mode 100644 src/components/molecules/InfoCard/InfoCrad.style.tsx create mode 100644 src/components/molecules/InfoCard/index.ts create mode 100644 src/components/molecules/InputWrapper/InputWrapper.stories.tsx create mode 100644 src/components/molecules/InputWrapper/InputWrapper.style.tsx create mode 100644 src/components/molecules/InputWrapper/InputWrapper.tsx create mode 100644 src/components/molecules/InputWrapper/index.ts create mode 100644 src/components/molecules/MenuList/MenuList.stories.tsx create mode 100644 src/components/molecules/MenuList/MenuList.style.tsx create mode 100644 src/components/molecules/MenuList/MenuList.tsx create mode 100644 src/components/molecules/Modal/Modal.stories.tsx create mode 100644 src/components/molecules/Modal/Modal.style.tsx create mode 100644 src/components/molecules/Modal/Modal.tsx create mode 100644 src/components/molecules/Modal/index.ts create mode 100644 src/components/molecules/MonthWithDays/IDisableDateSettings.model.ts create mode 100644 src/components/molecules/MonthWithDays/IExternalMonthWithDays.model.ts create mode 100644 src/components/molecules/MonthWithDays/MonthWithDays.stories.tsx create mode 100644 src/components/molecules/MonthWithDays/MonthWithDays.style.tsx create mode 100644 src/components/molecules/MonthWithDays/MonthWithDays.tsx create mode 100644 src/components/molecules/MonthWithDays/day.model.ts create mode 100644 src/components/molecules/MonthWithDays/helperFunctions/createDayFunction.ts create mode 100644 src/components/molecules/MonthWithDays/helperFunctions/createDaysOfMonth.ts create mode 100644 src/components/molecules/MonthWithDays/index.ts create mode 100644 src/components/molecules/NumberInput/NumberInput.stories.tsx create mode 100644 src/components/molecules/NumberInput/NumberInput.styled.tsx create mode 100644 src/components/molecules/NumberInput/NumberInput.tsx create mode 100644 src/components/molecules/NumberInput/index.ts create mode 100644 src/components/molecules/Paginator/Paginator.stories.tsx create mode 100644 src/components/molecules/Paginator/Paginator.style.tsx create mode 100644 src/components/molecules/Paginator/Paginator.tsx create mode 100644 src/components/molecules/Paginator/index.ts create mode 100644 src/components/molecules/PasswordInput/PasswordInput.stories.tsx create mode 100644 src/components/molecules/PasswordInput/PasswordInput.tsx create mode 100644 src/components/molecules/PasswordInput/index.ts create mode 100644 src/components/molecules/RangeCalendar/IDateArray.model.ts create mode 100644 src/components/molecules/RangeCalendar/RangeCalendar.stories.tsx create mode 100644 src/components/molecules/RangeCalendar/RangeCalendar.style.tsx create mode 100644 src/components/molecules/RangeCalendar/RangeCalendar.tsx create mode 100644 src/components/molecules/RangeCalendar/helperFunctions/selectDayFunction.ts create mode 100644 src/components/molecules/RangeCalendar/helperFunctions/useSelectedDates.ts create mode 100644 src/components/molecules/RangeCalendar/helperFunctions/useVisibleMonths.ts create mode 100644 src/components/molecules/RangeCalendar/index.ts create mode 100644 src/components/molecules/ScalingSection/ScalingSection.stories.tsx create mode 100644 src/components/molecules/ScalingSection/ScalingSection.tsx create mode 100644 src/components/molecules/ScalingSection/index.ts create mode 100644 src/components/molecules/SearchBar/SearchBar.stories.tsx create mode 100644 src/components/molecules/SearchBar/SearchBar.tsx create mode 100644 src/components/molecules/SearchBar/index.ts create mode 100644 src/components/molecules/SearchBarList/SearchBarList.stories.tsx create mode 100644 src/components/molecules/SearchBarList/SearchBarList.style.tsx create mode 100644 src/components/molecules/SearchBarList/SearchBarList.tsx create mode 100644 src/components/molecules/SearchBarList/index.ts create mode 100644 src/components/molecules/SingleInputs/SingleInputs.stories.tsx create mode 100644 src/components/molecules/SingleInputs/SingleInputs.style.tsx create mode 100644 src/components/molecules/SingleInputs/SingleInputs.tsx create mode 100644 src/components/molecules/SingleInputs/index.ts create mode 100644 src/components/molecules/SingleToastMessage/IToastMessage.model.tsx create mode 100644 src/components/molecules/SingleToastMessage/SingleToastMessage.mdx create mode 100644 src/components/molecules/SingleToastMessage/SingleToastMessage.stories.tsx create mode 100644 src/components/molecules/SingleToastMessage/SingleToastMessage.style.tsx create mode 100644 src/components/molecules/SingleToastMessage/SingleToastMessage.tsx create mode 100644 src/components/molecules/SingleToastMessage/index.ts create mode 100644 src/components/molecules/SwipeUpModal/ISwipeUpModal.model.ts create mode 100644 src/components/molecules/SwipeUpModal/SwipeUpModal.stories.tsx create mode 100644 src/components/molecules/SwipeUpModal/SwipeUpModal.style.tsx create mode 100644 src/components/molecules/SwipeUpModal/SwipeUpModal.tsx create mode 100644 src/components/molecules/SwipeUpModal/index.ts create mode 100644 src/components/molecules/SwitchList/SwitchList.stories.tsx create mode 100644 src/components/molecules/SwitchList/SwitchList.style.tsx create mode 100644 src/components/molecules/SwitchList/SwitchList.tsx create mode 100644 src/components/molecules/TabSwitch/TabSwitch.model.ts create mode 100644 src/components/molecules/TabSwitch/TabSwitch.style.tsx create mode 100644 src/components/molecules/TabSwitch/TabSwitch.tsx create mode 100644 src/components/molecules/TextInput/TextInput.stories.tsx create mode 100644 src/components/molecules/TextInput/TextInput.tsx create mode 100644 src/components/molecules/TextInput/index.ts create mode 100644 src/components/organisms/FancyBoxSet/FancyBoxSet.stories.tsx create mode 100644 src/components/organisms/FancyBoxSet/FancyBoxSet.tsx create mode 100644 src/components/organisms/FancyBoxSet/index.ts create mode 100644 src/components/organisms/FancyButton/FancyButton.stories.tsx create mode 100644 src/components/organisms/FancyButton/FancyButton.style.tsx create mode 100644 src/components/organisms/FancyButton/FancyButton.tsx create mode 100644 src/components/organisms/FancyButton/IFancyButton.model.ts create mode 100644 src/components/organisms/FancyButton/index.ts create mode 100644 src/components/organisms/FancyChip/FancyChip.model.ts create mode 100644 src/components/organisms/FancyChip/FancyChip.stories.tsx create mode 100644 src/components/organisms/FancyChip/FancyChip.style.tsx create mode 100644 src/components/organisms/FancyChip/FancyChip.tsx create mode 100644 src/components/organisms/FancyChip/index.ts create mode 100644 src/components/organisms/FancyChipList/FancyChipList.stories.tsx create mode 100644 src/components/organisms/FancyChipList/FancyChipList.style.tsx create mode 100644 src/components/organisms/FancyChipList/FancyChipList.tsx create mode 100644 src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.mdx create mode 100644 src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.stories.tsx create mode 100644 src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.style.tsx create mode 100644 src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.tsx create mode 100644 src/components/organisms/FancyCodeVerificationInput/index.ts create mode 100644 src/components/organisms/FancyColorPicker/FancyColorPicker.stories.tsx create mode 100644 src/components/organisms/FancyColorPicker/FancyColorPicker.tsx create mode 100644 src/components/organisms/FancyColorPicker/colorPickerUtils.ts create mode 100644 src/components/organisms/FancyColorPicker/index.ts create mode 100644 src/components/organisms/FancyDateInput/FancyDateInput.stories.tsx create mode 100644 src/components/organisms/FancyDateInput/FancyDateInput.style.tsx create mode 100644 src/components/organisms/FancyDateInput/FancyDateInput.tsx create mode 100644 src/components/organisms/FancyDateInput/index.ts create mode 100644 src/components/organisms/FancyDatePicker/FancyDatePicker.stories.tsx create mode 100644 src/components/organisms/FancyDatePicker/FancyDatePicker.style.tsx create mode 100644 src/components/organisms/FancyDatePicker/FancyDatePicker.tsx create mode 100644 src/components/organisms/FancyDatePicker/index.ts create mode 100644 src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.stories.tsx create mode 100644 src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.tsx create mode 100644 src/components/organisms/FancyDropDownMenue/index.ts create mode 100644 src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.stories.tsx create mode 100644 src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.tsx create mode 100644 src/components/organisms/FancyDropDownSelect/index.ts create mode 100644 src/components/organisms/FancyEditBar/EditBarItemsStructure/ButtonSettings.tsx create mode 100644 src/components/organisms/FancyEditBar/EditBarItemsStructure/EditBarIcons.tsx create mode 100644 src/components/organisms/FancyEditBar/EditBarItemsStructure/IEditbarObjectSturcture.model.ts create mode 100644 src/components/organisms/FancyEditBar/EditBarItemsStructure/objectSettingsSection.ts create mode 100644 src/components/organisms/FancyEditBar/EditBarSettings.tsx create mode 100644 src/components/organisms/FancyEditBar/FancyEditBar.state.tsx create mode 100644 src/components/organisms/FancyEditBar/FancyEditBar.tsx create mode 100644 src/components/organisms/FancyEditBar/functions/ElementsObjectMapper.tsx create mode 100644 src/components/organisms/FancyEditBar/index.ts create mode 100644 src/components/organisms/FancyEditBar/languagesEditBar.ts create mode 100644 src/components/organisms/FancyModal/FancyModal.mdx create mode 100644 src/components/organisms/FancyModal/FancyModal.state.tsx create mode 100644 src/components/organisms/FancyModal/FancyModal.stories.tsx create mode 100644 src/components/organisms/FancyModal/FancyModal.tsx create mode 100644 src/components/organisms/FancyModal/index.ts create mode 100644 src/components/organisms/FancyNumberInput/FancyNumberInput.tsx create mode 100644 src/components/organisms/FancyNumberInput/index.ts create mode 100644 src/components/organisms/FancyPasswordInput/FancyPasswordInput.stories.tsx create mode 100644 src/components/organisms/FancyPasswordInput/FancyPasswordInput.tsx create mode 100644 src/components/organisms/FancyPasswordInput/index.ts create mode 100644 src/components/organisms/FancyRadio/FancyRadio.tsx create mode 100644 src/components/organisms/FancyRadio/index.ts create mode 100644 src/components/organisms/FancyRangeSlider/FancyRangeSlider.model.ts create mode 100644 src/components/organisms/FancyRangeSlider/FancyRangeSlider.stories.tsx create mode 100644 src/components/organisms/FancyRangeSlider/FancyRangeSlider.style.tsx create mode 100644 src/components/organisms/FancyRangeSlider/FancyRangeSlider.tsx create mode 100644 src/components/organisms/FancyRangeSlider/index.ts create mode 100644 src/components/organisms/FancySearchBar/FancySearchBar.stories.tsx create mode 100644 src/components/organisms/FancySearchBar/FancySearchBar.tsx create mode 100644 src/components/organisms/FancySearchBar/index.ts create mode 100644 src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.mdx create mode 100644 src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.state.ts create mode 100644 src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.stories.tsx create mode 100644 src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.tsx create mode 100644 src/components/organisms/FancySwipeUpModal/index.ts create mode 100644 src/components/organisms/FancyTextInput/FancyTextInput.stories.tsx create mode 100644 src/components/organisms/FancyTextInput/FancyTextInput.tsx create mode 100644 src/components/organisms/FancyTextInput/index.ts create mode 100644 src/components/organisms/FancyToastMessage/FancyToastMessage.state.tsx create mode 100644 src/components/organisms/FancyToastMessage/FancyToastMessage.stories.tsx create mode 100644 src/components/organisms/FancyToastMessage/FancyToastMessage.style.tsx create mode 100644 src/components/organisms/FancyToastMessage/FancyToastMessage.tsx create mode 100644 src/components/organisms/FancyToastMessage/index.ts create mode 100644 src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.stories.tsx create mode 100644 src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.style.tsx create mode 100644 src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.tsx create mode 100644 src/components/templates/FancyBottomBarIcon/index.ts create mode 100644 src/components/templates/FancyFlexBox/FancyFlexBox.model.ts create mode 100644 src/components/templates/FancyFlexBox/FancyFlexBox.style.tsx create mode 100644 src/components/templates/FancyFlexBox/FancyFlexBox.tsx create mode 100644 src/components/templates/FancyFlexBox/index.ts create mode 100644 src/components/templates/FancyGrid/FancyGrid.mdx create mode 100644 src/components/templates/FancyGrid/FancyGrid.stories.tsx create mode 100644 src/components/templates/FancyGrid/FancyGrid.tsx create mode 100644 src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.mdx create mode 100644 src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.stories.tsx create mode 100644 src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.tsx create mode 100644 src/components/templates/FancyGrid/FancyGridItem/index.ts create mode 100644 src/components/templates/FancyGrid/index.ts create mode 100644 src/components/templates/FancyHandyNav/FancyHandyNav.store.ts create mode 100644 src/components/templates/FancyHandyNav/FancyHandyNav.stories.tsx create mode 100644 src/components/templates/FancyHandyNav/FancyHandyNav.tsx create mode 100644 src/components/templates/FancyHandyNav/index.ts create mode 100644 src/components/templates/FancyInfoCard/FancyInfoCard.stories.tsx create mode 100644 src/components/templates/FancyInfoCard/FancyInfoCard.tsx create mode 100644 src/components/templates/FancyInfoCard/index.ts create mode 100644 src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.stories.tsx create mode 100644 src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.tsx create mode 100644 src/components/templates/FancyMenueComponent/FancyMenu/index.ts create mode 100644 src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.stories.tsx create mode 100644 src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.tsx create mode 100644 src/components/templates/FancyMenueComponent/FancyMenuItem/index.ts create mode 100644 src/components/templates/FancyRadioList/FancyRadioList.model.ts create mode 100644 src/components/templates/FancyRadioList/FancyRadioList.stories.tsx create mode 100644 src/components/templates/FancyRadioList/FancyRadioList.style.tsx create mode 100644 src/components/templates/FancyRadioList/FancyRadioList.tsx create mode 100644 src/components/templates/FancyRadioList/index.ts create mode 100644 src/components/templates/Inputs/FancyDateDropDown/FancyDateDropDown.tsx create mode 100644 src/components/templates/Inputs/FancyDateDropDown/index.ts create mode 100644 src/components/utils/hooks/useIntersectionObserver/index.ts create mode 100644 src/components/utils/hooks/useIntersectionObserver/useIntersectionObserver.tsx create mode 100644 src/components/utils/hooks/useSlider/IUseSlider.model.ts create mode 100644 src/components/utils/hooks/useSlider/useSilder.tsx create mode 100644 src/components/utils/hooks/useWindowDimensions/index.ts create mode 100644 src/components/utils/hooks/useWindowDimensions/useWindowDimensions.tsx create mode 100644 src/components/utils/variables/colorFormats.ts diff --git a/src/components/atoms/ActionWrapper/ActionWrapper.mdx b/src/components/atoms/ActionWrapper/ActionWrapper.mdx new file mode 100644 index 000000000..9a9b8e5af --- /dev/null +++ b/src/components/atoms/ActionWrapper/ActionWrapper.mdx @@ -0,0 +1,57 @@ +# ActionWrapper + +`ActionWrapper` is a versatile React functional component designed to conditionally render either a `; +}; + +export default ActionWrapper; diff --git a/src/components/atoms/ActionWrapper/index.ts b/src/components/atoms/ActionWrapper/index.ts new file mode 100644 index 000000000..bf8df9187 --- /dev/null +++ b/src/components/atoms/ActionWrapper/index.ts @@ -0,0 +1 @@ +export { default as ActionWrapper } from './ActionWrapper'; diff --git a/src/components/atoms/AlignedInputLabel/AlignedInputLabel.stories.tsx b/src/components/atoms/AlignedInputLabel/AlignedInputLabel.stories.tsx new file mode 100644 index 000000000..958337587 --- /dev/null +++ b/src/components/atoms/AlignedInputLabel/AlignedInputLabel.stories.tsx @@ -0,0 +1,64 @@ +import { HTMLAttributes } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { AlignedInputLabel } from './AlignedInputLabel'; +import { IAlignedInputLabel } from './TalignedInputLabel.model'; +import Typography from '../Typography/Typography'; + +//This is a helper component to show the styled component in the story +const HelperComponent = (props: IAlignedInputLabel & HTMLAttributes) => ( + + Hello World + +); + +// Give the component a more meaningful name in the storybook +HelperComponent.displayName = 'AlignedInputLabel'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + component: HelperComponent, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + docs: { + description: { + component: + 'The input label wich is aligned left or centered.
- the alignment is set by the $align prop.
- the color changes depending on the $isActive prop', + }, + }, + }, + argTypes: { + $align: { + options: ['left', 'center'], + control: { type: 'radio' }, + }, + $colorState: { + control: { type: 'radio' }, + options: ['error', 'active', 'default'], + }, + $layer: { + control: { type: 'range', min: 1, max: 10 }, + }, + $themeType: { + options: ['primary', 'secondary'], + control: { type: 'radio' }, + }, + }, + + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Primary: Story = { + args: { + $align: 'left', + $colorState: 'default', + $themeType: 'secondary', + $layer: 4, + }, + render: (args: IAlignedInputLabel) => , +}; diff --git a/src/components/atoms/AlignedInputLabel/AlignedInputLabel.tsx b/src/components/atoms/AlignedInputLabel/AlignedInputLabel.tsx new file mode 100644 index 000000000..b1c80d3bd --- /dev/null +++ b/src/components/atoms/AlignedInputLabel/AlignedInputLabel.tsx @@ -0,0 +1,26 @@ +import { styled } from 'styled-components'; + +import InputLabel from '../InputLabel/InputLabel'; +import { getTextColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { IAlignedInputLabel } from './TalignedInputLabel.model'; + +//the aligned label is only with align left or centerd {align?: string; active?: boolean} +// eslint-disable-next-line react-refresh/only-export-components +export const AlignedInputLabel = styled(InputLabel)` + display: flex; + align-items: flex-end; + margin-bottom: ${({ theme }) => theme.spacing.xxs}; + justify-content: ${({ $align }) => ($align === 'left' ? 'flex-start' : $align === 'center' ? 'center' : 'flex-end')}; + color: ${({ $colorState, theme, $themeType = 'secondary', $layer = 4 }) => { + switch ($colorState) { + case 'error': + return theme.colors.error[0]; + case 'active': + return theme.colors.accent[0]; + default: + return getTextColor({ theme, $themeType, $textLayer: $layer }); + } + }}; +`; + +export default AlignedInputLabel; diff --git a/src/components/atoms/AlignedInputLabel/TalignedInputLabel.model.ts b/src/components/atoms/AlignedInputLabel/TalignedInputLabel.model.ts new file mode 100644 index 000000000..b24a60833 --- /dev/null +++ b/src/components/atoms/AlignedInputLabel/TalignedInputLabel.model.ts @@ -0,0 +1,13 @@ +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; +import { TThemeTypes } from '@/interface/TUiColors'; + +export type TAlign = 'left' | 'center'; + +export interface IAlignedInputLabel { + $align?: TAlign; + $colorState?: 'error' | 'active' | 'default'; + theme?: TTheme; + $themeType?: TThemeTypes; + $layer?: TLayer; +} diff --git a/src/components/atoms/AlignedInputLabel/index.ts b/src/components/atoms/AlignedInputLabel/index.ts new file mode 100644 index 000000000..eb359373b --- /dev/null +++ b/src/components/atoms/AlignedInputLabel/index.ts @@ -0,0 +1 @@ +export { default as AlignedInputLabel } from './AlignedInputLabel'; diff --git a/src/components/atoms/AnimatedLabel/AnimatedInputLabel.stories.tsx b/src/components/atoms/AnimatedLabel/AnimatedInputLabel.stories.tsx new file mode 100644 index 000000000..bae20dbb2 --- /dev/null +++ b/src/components/atoms/AnimatedLabel/AnimatedInputLabel.stories.tsx @@ -0,0 +1,64 @@ +import React, { HTMLAttributes } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { AnimatedInputLabel } from './AnimatedInputLabel'; +import { IAnimatedInputLabel } from './TAnimatedInputLabel.model'; +import Typography from '../Typography/Typography'; + +// This is a helper component to show the styled component in the story +const HelperComponent = (props: IAnimatedInputLabel & HTMLAttributes) => ( + + Hello World + +); + +// Give the component a more meaningful name in the storybook +HelperComponent.displayName = 'AnimatedInputLabel'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + title: 'components/atoms/AnimatedInputLabel', + component: HelperComponent, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + layout: 'centered', + docs: { + description: { + component: + 'The input label wich hase some colors and an animation.
- the color changes depending on the $colorState prop.
- the animation is triggered by the $moveUp prop', + }, + }, + }, + argTypes: { + $moveUp: { + control: { type: 'boolean' }, + }, + $colorState: { + control: { type: 'radio' }, + options: ['error', 'active', 'default'], + }, + $layer: { + control: { type: 'range', min: 1, max: 10 }, + }, + $themeType: { + options: ['primary', 'secondary'], + control: { type: 'radio' }, + }, + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Primary: Story = { + args: { + $moveUp: false, + $colorState: 'default', + $themeType: 'secondary', + $layer: 4, + }, + render: (args) => , +}; diff --git a/src/components/atoms/AnimatedLabel/AnimatedInputLabel.tsx b/src/components/atoms/AnimatedLabel/AnimatedInputLabel.tsx new file mode 100644 index 000000000..71c59c0f6 --- /dev/null +++ b/src/components/atoms/AnimatedLabel/AnimatedInputLabel.tsx @@ -0,0 +1,55 @@ +import { styled, css } from 'styled-components'; + +import InputLabel from '@/components/atoms/InputLabel/InputLabel'; +import { IAnimatedInputLabel } from './TAnimatedInputLabel.model'; + +import { getTextColor } from '@/design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +const activeHandler = (align: string, $moveUp?: boolean) => { + if (align !== 'center') { + return css` + bottom: 0; + left: 0; + ${$moveUp + ? css` + transform: translateY(-20px); + ` + : ''} + `; + } else { + return css` + bottom: 0; + left: 50%; + text-align: center; + ${$moveUp + ? css` + transform: translateY(-20px) translate(-50%); + ` + : 'transform: translate(-50%);'}; + `; + } +}; + +// --------------------------------------------------------------------------- // +// ---------- The input label wich hase some colors and an animation --------- // +// --------------------------------------------------------------------------- // +// eslint-disable-next-line react-refresh/only-export-components +export const AnimatedInputLabel = styled(InputLabel)` + position: absolute; + padding: 12px 0 5px; + color: ${({ $colorState, theme, $themeType = 'secondary', $layer = 4 }) => { + switch ($colorState) { + case 'error': + return theme.colors.error[0]; + case 'active': + return theme.colors.accent[0]; + default: + return getTextColor({ theme, $themeType, $textLayer: $layer }); + } + }}; + + ${({ $align, $moveUp }) => activeHandler($align!, $moveUp)}; +`; + +export default AnimatedInputLabel; diff --git a/src/components/atoms/AnimatedLabel/TAnimatedInputLabel.model.ts b/src/components/atoms/AnimatedLabel/TAnimatedInputLabel.model.ts new file mode 100644 index 000000000..2451d2cd1 --- /dev/null +++ b/src/components/atoms/AnimatedLabel/TAnimatedInputLabel.model.ts @@ -0,0 +1,12 @@ +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; +import { TThemeTypes } from '@/interface/TUiColors'; + +export interface IAnimatedInputLabel { + $align?: 'center' | 'left'; + $moveUp?: boolean; + $colorState?: 'error' | 'active' | 'default'; + $themeType?: TThemeTypes; + $layer?: TLayer; + theme?: TTheme; +} diff --git a/src/components/atoms/AnimatedLabel/index.ts b/src/components/atoms/AnimatedLabel/index.ts new file mode 100644 index 000000000..08161e5b3 --- /dev/null +++ b/src/components/atoms/AnimatedLabel/index.ts @@ -0,0 +1 @@ +export { default as AnimatedInputLabel } from './AnimatedInputLabel'; diff --git a/src/components/atoms/AvilableDot/AvailableDot.tsx b/src/components/atoms/AvilableDot/AvailableDot.tsx new file mode 100644 index 000000000..d9c6331b1 --- /dev/null +++ b/src/components/atoms/AvilableDot/AvailableDot.tsx @@ -0,0 +1,29 @@ +import { styled } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// --------------------------------------------------------------------------- // +// ---------- A little Circle that indicates if something is avilable -------- // +// --------------------------------------------------------------------------- // +export type IAvailableDot = 'completly' | 'partially' | 'not' | 'transparent'; +const AvailableDot = styled.div<{ $available?: IAvailableDot; theme?: TTheme }>` + width: 4px; + height: 4px; + border-radius: ${({ theme }) => theme.borderRadius.complete}; + background-color: ${({ $available = 'completly', theme }) => { + switch ($available) { + case 'completly': + return theme.colors.success[0]; + case 'partially': + return theme.colors.warning[0]; + case 'not': + return theme.colors.error[0]; + case 'transparent': + return 'transparent'; + } + }}; +`; + +AvailableDot.displayName = 'AvailableDot'; + +export default AvailableDot; diff --git a/src/components/atoms/AvilableDot/AvilableDot.stories.tsx b/src/components/atoms/AvilableDot/AvilableDot.stories.tsx new file mode 100644 index 000000000..69f014c35 --- /dev/null +++ b/src/components/atoms/AvilableDot/AvilableDot.stories.tsx @@ -0,0 +1,51 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import AvailableDot from './AvailableDot'; + +// Define metadata for the story +const meta = { + component: AvailableDot, + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'A little Circle that indicates if something is avilable.
- the color changes depending on the $available prop.
- the size is fixed.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + $available: { + options: ['completly', 'partially', 'not', 'transparent'], + control: { type: 'radio' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; + +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + $available: 'completly', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/AvilableDot/index.ts b/src/components/atoms/AvilableDot/index.ts new file mode 100644 index 000000000..a0cc0968b --- /dev/null +++ b/src/components/atoms/AvilableDot/index.ts @@ -0,0 +1 @@ +export { default as AvilableDot } from './AvailableDot'; diff --git a/src/components/atoms/BackDrop/BackDrop.stories.tsx b/src/components/atoms/BackDrop/BackDrop.stories.tsx new file mode 100644 index 000000000..abb961e61 --- /dev/null +++ b/src/components/atoms/BackDrop/BackDrop.stories.tsx @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import BackDrop from './BackDrop'; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + component: BackDrop, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout + layout: 'centered', + docs: { + description: { + component: 'Only a backdrop for some components with a onclick listener', + }, + }, + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Primary: Story = { + render: (args) => , + args: { + isOpen: true, + }, +}; diff --git a/src/components/atoms/BackDrop/BackDrop.tsx b/src/components/atoms/BackDrop/BackDrop.tsx new file mode 100644 index 000000000..c7a16d532 --- /dev/null +++ b/src/components/atoms/BackDrop/BackDrop.tsx @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from 'react'; +import { animated, useSpring } from '@react-spring/web'; +import { styled } from 'styled-components'; + +const BackdropContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 100; +`; + +interface IBackDrop { + isOpen: boolean; + onClick?: () => void; +} +// --------------------------------------------------------------------------- // +// ------- Only a backdrop for some components with a onclick listener ------- // +// --------------------------------------------------------------------------- // +export default function BackDrop({ isOpen, onClick }: IBackDrop) { + const [shouldRender, setRender] = useState(isOpen); + + const fade = useSpring({ + opacity: isOpen ? 0.5 : 0, + onRest: () => setRender(isOpen), + }); + + useEffect(() => { + if (isOpen) setRender(true); + }, [isOpen]); + + return shouldRender ? : null; +} diff --git a/src/components/atoms/BackDrop/index.ts b/src/components/atoms/BackDrop/index.ts new file mode 100644 index 000000000..94f74dfcd --- /dev/null +++ b/src/components/atoms/BackDrop/index.ts @@ -0,0 +1 @@ +export { default as BackDrop } from './BackDrop'; diff --git a/src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.stories.tsx b/src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.stories.tsx new file mode 100644 index 000000000..349f349ca --- /dev/null +++ b/src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.stories.tsx @@ -0,0 +1,44 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import { CheckerBoardPattern } from '.'; + +// Define metadata for the story +const meta = { + component: CheckerBoardPattern, + parameters: { + docs: { + description: { + component: 'The CheckerBoardPattern component is a Gradient pattern that can be used as a background for other components.', + }, + }, + }, + + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; + +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + opacity: 1, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.tsx b/src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.tsx new file mode 100644 index 000000000..add0abf79 --- /dev/null +++ b/src/components/atoms/CheckerBoardPattern/CheckerBoardPattern.tsx @@ -0,0 +1,19 @@ +import { styled } from 'styled-components'; + +const CheckerBoardPattern = styled.div<{ opacity?: number }>` + position: absolute; + height: 20px; + height: 100%; + width: 100%; + background-image: linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%); + background-size: 14px 14px; + background-position: + 0 0, + 0 7px, + 7px -7px, + -7px 0px; + opacity: ${({ opacity }) => opacity || 0.05}; +`; + +export default CheckerBoardPattern; diff --git a/src/components/atoms/CheckerBoardPattern/index.ts b/src/components/atoms/CheckerBoardPattern/index.ts new file mode 100644 index 000000000..7da7d04d6 --- /dev/null +++ b/src/components/atoms/CheckerBoardPattern/index.ts @@ -0,0 +1 @@ +export { default as CheckerBoardPattern } from './CheckerBoardPattern'; diff --git a/src/components/atoms/ColorDisplay/ColorDisplay.stories.tsx b/src/components/atoms/ColorDisplay/ColorDisplay.stories.tsx new file mode 100644 index 000000000..3afcf8014 --- /dev/null +++ b/src/components/atoms/ColorDisplay/ColorDisplay.stories.tsx @@ -0,0 +1,58 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import ColorDisplay from './ColorDisplay'; +// Define metadata for the story +const meta = { + component: ColorDisplay, + parameters: { + docs: { + description: { + component: 'The ColorDisplay component is for displaying a color with a text label and the color and it can be copyd on click', + }, + }, + }, + + // Define arguments for the story + argTypes: { + color: { + control: { type: 'color' }, + }, + opacity: { + control: { type: 'range', min: 0, max: 1, step: 0.1 }, + }, + fullHeight: { + control: { type: 'boolean' }, + }, + showText: { + control: { type: 'boolean' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + color: 'red', + opacity: 1, + fullHeight: false, + showText: true, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/ColorDisplay/ColorDisplay.style.tsx b/src/components/atoms/ColorDisplay/ColorDisplay.style.tsx new file mode 100644 index 000000000..cbc468ffe --- /dev/null +++ b/src/components/atoms/ColorDisplay/ColorDisplay.style.tsx @@ -0,0 +1,77 @@ +import Color from 'color'; +import { styled } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import simpleColorTransition from '../../../design/designFunctions/simpleColorTransition/simpleTransition'; +import { TTheme } from '@/interface/TTheme'; + +const colorCalculation = ({ theme, $isBright, $isDarkTheme }: { theme: TTheme; $isBright: boolean; $isDarkTheme: boolean }) => { + if ($isDarkTheme) return $isBright ? theme.colors.primary[0] : theme.colors.secondary[0]; + + return $isBright ? theme.colors.secondary[0] : theme.colors.primary[0]; +}; + +export const Content = styled.div<{ $isBright: boolean; theme: TTheme; $isDarkTheme: boolean }>` + position: absolute; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + z-index: 2; + gap: 3px; + font-size: ${fontSize.sm}; + color: ${({ theme, $isBright, $isDarkTheme }) => colorCalculation({ theme, $isBright, $isDarkTheme })}; + ${simpleColorTransition} + + p { + user-select: none; + } + + &:active, + &:hover { + color: ${({ theme, $isBright, $isDarkTheme }) => ($isBright && $isDarkTheme ? theme.colors.primary[4] : theme.colors.secondary[4])}; + } +`; + +export const WrapperSVG = styled.div` + position: absolute; + right: 8px; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 1px; +`; + +export const Wrapper = styled.div<{ $fullHeight?: boolean }>` + position: relative; + cursor: pointer; + width: 100%; + height: ${({ $fullHeight }) => ($fullHeight ? '100%' : '20px')}; + touch-action: manipulation; +`; + +interface IColorDisplayColor { + color?: string; + opacity?: number; +} + +const colorDisplayColor = ({ color, opacity }: IColorDisplayColor) => { + const checkOpacity = opacity === undefined ? 1 : opacity; + const transformedColor = Color(color).rgb().alpha(checkOpacity); + return { + style: { + background: `${transformedColor.toString()}`, + }, + }; +}; + +export const ColorDisplayContainer = styled.div.attrs(colorDisplayColor)` + position: absolute; + width: 100%; + height: 100%; + border-radius: ${({ theme }) => theme.borderRadius.sm}; + z-index: 1; +`; diff --git a/src/components/atoms/ColorDisplay/ColorDisplay.tsx b/src/components/atoms/ColorDisplay/ColorDisplay.tsx new file mode 100644 index 000000000..16151512c --- /dev/null +++ b/src/components/atoms/ColorDisplay/ColorDisplay.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import Color from 'color'; + +import Typography from '../Typography/Typography'; +import copyToClipboard from '../../utils/functions/copyToClipBoard'; +import CheckerBoardPattern from '../CheckerBoardPattern/CheckerBoardPattern'; +import { ColorDisplayContainer, Wrapper, Content, WrapperSVG } from './ColorDisplay.style'; +import ClipBoardIcon from '../../icons/SVGClipBoardIcon/SVGClipBoardIcon'; +import ClipBoardIconCheck from '../../icons/SVGClipBoardIconChecked/SVGClipBoardIconChecked'; +import themeStore from '../../../design/theme/themeStore/themeStore'; + +// --------------------------------------------------------------------------- // +// ----- The main ColorDisplay Component for display the color in a box ------ // +// --------------------------------------------------------------------------- // +interface IColorDisplay { + color: Color | string; + opacity?: number; + showText?: boolean; + fullHeight?: boolean; +} +export default function ColorDisplay({ color, opacity, showText, fullHeight }: IColorDisplay) { + const [copyd, setCopyd] = useState(false); + const isDarkTheme = themeStore.getState().isDarkTheme; + + opacity = opacity !== undefined ? opacity : 1; + const isBright = Color(color).isLight() && opacity > 0.5; + + const copyValue = () => { + copyToClipboard(color.toString()) + .then(() => { + setCopyd(true); + setTimeout(() => setCopyd(false), 1000); + }) + .catch((err) => { + console.error('Failed to copy: ', err); + }); + }; + + return ( + + + {showText && {color.toString()}} + {copyd ? : } + + + + + ); +} diff --git a/src/components/atoms/ColorDisplay/index.ts b/src/components/atoms/ColorDisplay/index.ts new file mode 100644 index 000000000..174975c64 --- /dev/null +++ b/src/components/atoms/ColorDisplay/index.ts @@ -0,0 +1 @@ +export { default as ColorDisplay } from './ColorDisplay'; diff --git a/src/components/atoms/ColorIndicator/ColorIndicator.style.tsx b/src/components/atoms/ColorIndicator/ColorIndicator.style.tsx new file mode 100644 index 000000000..536d30895 --- /dev/null +++ b/src/components/atoms/ColorIndicator/ColorIndicator.style.tsx @@ -0,0 +1,39 @@ +import { styled, css } from 'styled-components'; + +import { boxShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TTheme } from '@/interface/TTheme'; + +export const WrapperIndicator = styled.div<{ $isActive: boolean }>` + position: absolute; + width: ${({ $isActive }) => ($isActive ? '70px' : '0px')}; + height: ${({ $isActive }) => ($isActive ? '70px' : '0px')}; + z-index: 10; + left: 50%; + transform: translate(-50%); + transition: + width 0.15s ease-in-out, + height 0.15s ease-in-out; + pointer-events: none; +`; + +export const Indicator = styled.div.attrs<{ $color: string; theme: TTheme }>(({ $color }) => ({ + style: { + backgroundColor: $color, + }, +}))<{ $isActive: boolean }>` + border-radius: 50% 50% 50% 0; + border: 1.5px solid ${({ theme }) => theme.colors.secondary[0]}; + background-color: ${({ theme }) => theme.colors.secondary[0]}; + ${boxShadow.sm}; + position: absolute; + top: ${({ $isActive }) => ($isActive ? '-105px' : '-5px')}; + width: 100%; + height: 100%; + left: 50%; + transform: ${({ $isActive }) => + $isActive ? css`translate(-50%) scale(1) rotate(-45deg)` : css`translate(-50%) scale(0) rotate(-45deg)`}; + user-select: none; + transition: + transform 0.15s ease-in-out, + top 0.15s ease-in-out; +`; diff --git a/src/components/atoms/ColorIndicator/ColorIndicator.tsx b/src/components/atoms/ColorIndicator/ColorIndicator.tsx new file mode 100644 index 000000000..75b4fe33d --- /dev/null +++ b/src/components/atoms/ColorIndicator/ColorIndicator.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { Indicator, WrapperIndicator } from './ColorIndicator.style'; +import Color from 'color'; + +interface IColorIndicator { + position?: { x?: number | string; y?: number | string }; + color: string; + isActive: boolean; +} +export default function ColorIndicator({ position, color, isActive }: IColorIndicator) { + const positionTop = position?.y ?? '50%'; + const positionLeft = position?.x ?? '50%'; + + return ( + + + + ); +} diff --git a/src/components/atoms/ColorIndicator/Colorindicator.stories.tsx b/src/components/atoms/ColorIndicator/Colorindicator.stories.tsx new file mode 100644 index 000000000..106b66550 --- /dev/null +++ b/src/components/atoms/ColorIndicator/Colorindicator.stories.tsx @@ -0,0 +1,46 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import ColorIndicator from './ColorIndicator'; +// Define metadata for the story +const meta = { + title: 'components/atoms/ColorIndicator', + component: ColorIndicator, + parameters: { + docs: { + description: { + component: + 'The ColorIndicator component is for displaying a color, its can be used to show the color of a color picker and move with the position of the mouse', + }, + }, + }, + + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + color: 'red', + isActive: true, + position: { x: '50%', y: '50%' }, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/ColorIndicator/index.ts b/src/components/atoms/ColorIndicator/index.ts new file mode 100644 index 000000000..98f4ddf9e --- /dev/null +++ b/src/components/atoms/ColorIndicator/index.ts @@ -0,0 +1 @@ +export { default as ColorIndicator } from './ColorIndicator'; diff --git a/src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.mdx b/src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.mdx new file mode 100644 index 000000000..5f6837265 --- /dev/null +++ b/src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.mdx @@ -0,0 +1,52 @@ +# ComponentAsWrapper + +`ComponentAsWrapper` is a React component designed for dynamic component wrapping. It allows for the injection of children into a specified wrapper component, enabling dynamic composition of React elements. This is particularly useful when the wrapping element needs to be determined at runtime or passed as a prop from a parent component. + +## Props + +The component accepts the following props: + +- `wrapper`: A React element that will be used as the wrapper for the children. This is not just a component type but an actual element. +- `children`: The content or elements that will be wrapped inside the `wrapper`. + +## Usage + +The `ComponentAsWrapper` is straightforward to use. You pass a React element as the `wrapper` and any children you want to be wrapped. + +### Basic Example + +In a basic scenario, you might use `ComponentAsWrapper` to dynamically decide the wrapper based on certain conditions: + +```jsx +import React from 'react'; +import ComponentAsWrapper from './path/to/ComponentAsWrapper'; + +function ExampleComponent({ useDiv }) { + const WrapperElement = useDiv ?
:
; + + return ( + +

This content will be wrapped based on the condition

+
+ ); +} + +export default ExampleComponent; +``` + +In this example, `ComponentAsWrapper` is used to conditionally wrap the content in either a `div` or a `section`, depending on the `useDiv` prop. The `wrapper` prop takes a React element (`
` or `
`), and the `children` prop takes the content (`

` element) to be wrapped. + +## Advanced Usage + +`ComponentAsWrapper` can also be used in more advanced scenarios, such as when building a component library where you want to give users the ability to define their own wrapper elements. + +For example, in a library's modal component, you could allow users to specify their own wrapper element for the modal content: + +```jsx +// In your modal component file +{modalContent} +``` + +This makes `ComponentAsWrapper` a powerful tool for building flexible and reusable component libraries. + +By leveraging the `ComponentAsWrapper`, developers can create highly customizable UI components that adapt to various use cases, enhancing the overall flexibility and maintainability of the application. diff --git a/src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.tsx b/src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.tsx new file mode 100644 index 000000000..d37a964c0 --- /dev/null +++ b/src/components/atoms/ComponentAsWrapper/ComponentAsWrapper.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface Props { + wrapper: React.ReactElement; + children: React.ReactNode; +} +// --------------------------------------------------------------------------- // +// --------- This component is used when you have a dynamic wrapper ---------- // +// --------------------------------------------------------------------------- // +export default function ComponentAsWrapper({ wrapper, children }: Props) { + // Clone the element to inject children + return React.cloneElement(wrapper, {}, children); +} diff --git a/src/components/atoms/CustomeDropDownNOTINUSE/CustomeDropDown.tsx b/src/components/atoms/CustomeDropDownNOTINUSE/CustomeDropDown.tsx new file mode 100644 index 000000000..c2c18a130 --- /dev/null +++ b/src/components/atoms/CustomeDropDownNOTINUSE/CustomeDropDown.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { styled } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import { TTheme } from '@/interface/TTheme'; + +// Styled dropdown container +const DropDownContainer = styled.div<{ theme: TTheme }>` + position: relative; + display: inline-block; + font-size: ${fontSize.md}; + color: ${({ theme }) => theme.colors.secondary.main}; + user-select: none; +`; + +// Styled button to trigger the dropdown +const DropDownTrigger = styled.div<{ theme: TTheme }>` + background-color: ${({ theme }) => theme.colors.primary.main}; + padding: ${({ theme }) => theme.spacing.sm}; + cursor: pointer; + text-align: center; +`; + +// Styled dropdown menu +const DropDownMenu = styled.div<{ $isOpen: boolean; theme: TTheme }>` + display: ${(props) => (props.$isOpen ? 'block' : 'none')}; + position: absolute; + background-color: ${({ theme }) => theme.colors.primary.main}; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; +`; + +// Styled dropdown item +const DropDownItem = styled.div<{ theme: TTheme }>` + padding: 12px 16px; + display: block; + cursor: pointer; + + &:hover { + background-color: ${({ theme }) => theme.colors.accent.main}; + } +`; + +interface CustomDropdownProps { + values?: string[]; + onSelect?: (value: string) => void; +} + +//This is not a Finished component ist for later replacinge the HTML Select with a custom Dropdown (Searchselect, checkboxselect, etc.) +const CustomDropdown: React.FC = (props) => { + const { values, onSelect } = { ...defaultProps, ...props }; + const [isOpen, setIsOpen] = useState(false); + const [selectedValue, setSelectedValue] = useState(null); + + const handleSelect = (value: string) => { + setSelectedValue(value); + setIsOpen(false); + onSelect && onSelect(value); + }; + + return ( + + setIsOpen(!isOpen)}>{selectedValue || 'Select an item'} + + {values!.map((value, index) => ( + handleSelect(value)}> + {value} + + ))} + + + ); +}; + +export default CustomDropdown; + +const defaultProps: CustomDropdownProps = { + values: ['test', 'test2', 'test3'], +}; diff --git a/src/components/atoms/DateNumberAtom/DateNumberAtom.stories.tsx b/src/components/atoms/DateNumberAtom/DateNumberAtom.stories.tsx new file mode 100644 index 000000000..b7becc4b6 --- /dev/null +++ b/src/components/atoms/DateNumberAtom/DateNumberAtom.stories.tsx @@ -0,0 +1,93 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import DateNumberAtom from './DateNumberAtom'; +// Define metadata for the story +const meta = { + component: DateNumberAtom, + parameters: { + docs: { + description: { + component: + 'The DateNumberAtom component is for displaying a number in a box, it can be used for displaying the day of the month in a calendar
- selected: is the day selected
- disabled: is the day disabled
- onClick: function to be called when the day is clicked
- range: is the day in a range
- isCurrentDay: is the day the current day
- themeType: the theme type of the day
- layer: the layer of the day', + }, + }, + }, + + // Define arguments for the story + argTypes: { + dateNumber: { + control: { type: 'number' }, + }, + selected: { + description: 'is the day selected', + control: { type: 'boolean' }, + }, + disabled: { + control: { type: 'boolean' }, + description: 'is the day disabled, like a day in the previous month or next month, or the weekend', + defaultValue: { + summary: false, + }, + }, + onClick: { + control: { type: 'function' }, + }, + range: { + control: { type: 'object' }, + description: + 'is the day in a range, you can pass an object with the following properties:
- start: is the day the start of the range
- end: is the day the end of the range
- inRange: is the day in the range', + }, + isCurrentDay: { + description: 'is the day the current day it gets a different color', + control: { type: 'boolean' }, + }, + themeType: { + description: 'the theme type of the day', + control: { type: 'select' }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'the layer of the day', + control: { type: 'range', min: 0, max: 10, step: 1 }, + defaultValue: { + summary: 0, + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + dateNumber: 1, + selected: false, + disabled: false, + onClick: () => { + console.log('clicked'); + }, + range: { start: false, end: false, inRange: false }, + isCurrentDay: false, + themeType: 'secondary', + layer: 0, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/DateNumberAtom/DateNumberAtom.style.tsx b/src/components/atoms/DateNumberAtom/DateNumberAtom.style.tsx new file mode 100644 index 000000000..d0c353732 --- /dev/null +++ b/src/components/atoms/DateNumberAtom/DateNumberAtom.style.tsx @@ -0,0 +1,76 @@ +import { css, styled } from 'styled-components'; + +import { IRange } from './DateNumberAtom'; +import { disabledStyle } from '../../../design/designFunctions/disabledStyle/disableStyle'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getTextColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +// --------------------------------------------------------------------------- // +// ----------------- The Style for one Day with diferent sates --------------- // +// --------------------------------------------------------------------------- // +interface IStyledDay { + $range?: IRange; + $isCurrentDay?: boolean; + $selected?: boolean; + $themeType?: TThemeTypes; + $layer?: TLayer; +} +export const StyledDay = styled.button` + cursor: pointer; + box-sizing: border-box; + justify-content: center; + align-items: center; + border-radius: ${({ theme }) => theme.borderRadius.complete}; + color: ${({ $isCurrentDay, theme, $themeType = 'secondary', $layer }) => + $isCurrentDay ? theme.colors.accent[0] : getTextColor({ $themeType, $textLayer: $layer, theme })}; + border: ${({ $selected, theme }) => ($selected ? `1px solid ${theme.colors.accent[0]}` : `none`)}; + background-color: transparent; + padding: 0; + width: 80%; + max-width: 40px; + position: relative; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + + &:before { + content: ''; + display: block; + padding-top: 100%; + } + + &:hover, + &:active { + border: 1px solid ${({ theme }) => theme.colors.accent[0]}; + } + + ${({ $range, theme }) => + $range?.start && + css` + border-radius: 40% 5px 5px 40%; + background-image: linear-gradient(to right, ${theme.colors.accent[0]}, transparent); + color: white; + `} + + ${({ $range, theme }) => + $range?.end && + ` + border-radius: 5px 40% 40% 5px; + background-image: linear-gradient(to left, ${theme.colors.accent[0]}, transparent); + color: white; + `} + + ${({ $range, theme }) => + $range?.inRange && + ` + background-color: ${theme.colors.accent[0]}; + color: white; + `} + + &:disabled { + ${disabledStyle} + } +`; diff --git a/src/components/atoms/DateNumberAtom/DateNumberAtom.tsx b/src/components/atoms/DateNumberAtom/DateNumberAtom.tsx new file mode 100644 index 000000000..ca684bb51 --- /dev/null +++ b/src/components/atoms/DateNumberAtom/DateNumberAtom.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { StyledDay } from './DateNumberAtom.style'; +import Typography from '../Typography/Typography'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +export type IRange = { start?: boolean; end?: boolean; inRange?: boolean }; + +interface IDay { + dateNumber: number; + selected?: boolean; + disabled?: boolean; + onClick?: () => void; + range?: IRange; + isCurrentDay?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; +} +// --------------------------------------------------------------------------- // +// -------- The DateNumberAtom Displays the date number of a clendar --------- // +// --------------------------------------------------------------------------- // +export default function DateNumberAtom(props: IDay) { + const { dateNumber, selected, disabled, onClick, range, isCurrentDay, themeType, layer } = props; + + return ( + + + {dateNumber} + + + ); +} diff --git a/src/components/atoms/DateNumberAtom/index.ts b/src/components/atoms/DateNumberAtom/index.ts new file mode 100644 index 000000000..74173736f --- /dev/null +++ b/src/components/atoms/DateNumberAtom/index.ts @@ -0,0 +1 @@ +export { default as DateNumberAtom } from './DateNumberAtom'; diff --git a/src/components/atoms/DateOutput/DateOutput.stories.tsx b/src/components/atoms/DateOutput/DateOutput.stories.tsx new file mode 100644 index 000000000..0eaea041f --- /dev/null +++ b/src/components/atoms/DateOutput/DateOutput.stories.tsx @@ -0,0 +1,64 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import DateOutput from './DateOutput'; +// Define metadata for the story +const meta = { + component: DateOutput, + parameters: { + docs: { + description: { + component: + 'the DateOutput component is for displaying a date in a box, it can be used for displaying the date in a calendar
- date: the date to be displayed
- themeType: the theme type of the date
- isActive: is the date active
- onClick: function to be called when the date is clicked
- layer: the layer of the date', + }, + }, + }, + + // Define arguments for the story + argTypes: { + date: { + control: { type: 'date' }, + }, + themeType: { + control: { type: 'select', options: ['primary', 'secondary', 'tertiary'] }, + }, + isActive: { + control: { type: 'boolean' }, + }, + onClick: { + control: { type: 'function' }, + }, + layer: { + control: { type: 'range', min: 0, max: 10, step: 1 }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + date: new Date(), + themeType: 'primary', + isActive: true, + onClick: () => { + console.log('clicked'); + }, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/DateOutput/DateOutput.tsx b/src/components/atoms/DateOutput/DateOutput.tsx new file mode 100644 index 000000000..5038741a7 --- /dev/null +++ b/src/components/atoms/DateOutput/DateOutput.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { styled } from 'styled-components'; + +import Typography from '../Typography/Typography'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { getBackgroundColor, getTextColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; + +const DateOutputButton = styled.button<{ $isActive?: boolean; theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + text-align: center; + width: 100%; + background-color: ${({ theme, $isActive, $themeType = 'primary', $layer = 2 }) => + $isActive + ? getBackgroundColor({ theme, $themeType, $layer: $layer ? $layer + 1 : 3 }) + : getBackgroundColor({ theme, $themeType, $layer: $layer ? $layer : 2 })}; + color: ${({ theme, $themeType = 'secondary', $layer = 1 }) => + getTextColor({ theme, $themeType, $textLayer: $layer, turnColorTheme: true })}; + border: none; + cursor: pointer; + + &:hover { + background-color: ${({ theme, $themeType = 'primary', $layer }) => + getBackgroundColor({ theme, $themeType, $layer: $layer ? $layer : 3 })}; + } +`; + +interface IDateOutput { + date?: Date; + isActive?: boolean; + onClick?: () => void; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function DateOutput({ date = new Date(), isActive, onClick, themeType, layer }: IDateOutput) { + const [active, setActive] = useState(false); + const [selectedDate, setSelectedDate] = useState(date); + + // Format the date to display as "6. Juni" + const formattedDate = useMemo(() => { + if (!selectedDate) return ''; + const userLang = navigator.language; + const options = { day: 'numeric', month: 'short' } as const; + return selectedDate.toLocaleDateString(userLang, options); + }, [selectedDate]); + + const handleOpenCalendar = () => { + setActive(true); + onClick && onClick(); + }; + + useEffect(() => { + setSelectedDate(date); + }, [date]); + + useEffect(() => { + setActive(isActive ?? false); + }, [isActive]); + + return ( + + {formattedDate || 'Select a date'} + + ); +} diff --git a/src/components/atoms/DateOutput/index.ts b/src/components/atoms/DateOutput/index.ts new file mode 100644 index 000000000..b2e80a817 --- /dev/null +++ b/src/components/atoms/DateOutput/index.ts @@ -0,0 +1 @@ +export { default as DateOutput } from './DateOutput'; diff --git a/src/components/atoms/DropDownSelect/DropDownSelect.model.ts b/src/components/atoms/DropDownSelect/DropDownSelect.model.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/atoms/DropDownSelect/DropDownSelect.stories.tsx b/src/components/atoms/DropDownSelect/DropDownSelect.stories.tsx new file mode 100644 index 000000000..5635cebf7 --- /dev/null +++ b/src/components/atoms/DropDownSelect/DropDownSelect.stories.tsx @@ -0,0 +1,64 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import DropDownSelect from './DropDownSelect'; +// Define metadata for the story +const meta = { + component: DropDownSelect, + parameters: { + docs: { + description: { + component: + 'The DropDownSelect component is for displaying a dropdown select, it can be used for selecting a value from a list
- values: the values to be displayed in the dropdown
- onChange: function to be called when the value is changed
- placeholder: the placeholder text to be displayed when no value is selected
- value: the value to be displayed
- emptySelect: is the select empty
- disabled: is the select disabled', + }, + }, + }, + + // Define arguments for the story + argTypes: { + values: { + control: { type: 'array' }, + }, + onChange: { + control: { type: 'function' }, + }, + placeholder: { + control: { type: 'text' }, + }, + value: { + control: { type: 'text' }, + }, + emptySelect: { + control: { type: 'boolean' }, + }, + disabled: { + control: { type: 'boolean' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + values: ['one', 'two', 'three'], + emptySelect: false, + disabled: false, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/DropDownSelect/DropDownSelect.style.tsx b/src/components/atoms/DropDownSelect/DropDownSelect.style.tsx new file mode 100644 index 000000000..02e926ed4 --- /dev/null +++ b/src/components/atoms/DropDownSelect/DropDownSelect.style.tsx @@ -0,0 +1,69 @@ +import { css, styled } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import { TTheme } from '@/interface/TTheme'; + +//the styling for each option item + +//the selectfield for the options +interface ISelectField { + $align?: string; + $labelAlign?: 'center' | 'left'; + $disabledAndSelected?: boolean; +} +export const SelectField = styled.select` + position: relative; + box-sizing: border-box; + ${({ theme }) => css` + background-image: ${`url("data:image/svg+xml;utf8,")`}; + `} + background-repeat: no-repeat; + background-position: right ${({ theme }) => theme.spacing.sm} top 30%; + text-align-last: ${({ $align }) => ($align !== 'center' ? 'left' : 'center')}; + color: ${({ theme }) => theme.colors.secondary[0]}; + border: none; + width: 100%; + font-weight: 500; + user-select: none; + appearance: none; + padding: 0px 0px ${({ theme }) => theme.spacing.sm}; + font-size: ${fontSize.md}; + background-color: transparent; + + &:disabled { + opacity: 1; + } + + &:focus { + outline: none; + } + + /* When a item is checked in the dropdown */ + &:focus option:checked { + background: ${({ theme }) => theme.colors.accent[1]}; + } + + optgroup { + font-weight: bold; + color: ${({ theme }) => theme.colors.accent[2]}; + background-color: ${({ theme }) => theme.colors.primary[2]}; + text-align: ${({ $align }) => ($align !== 'left' ? 'center' : 'left')}; + + &:disabled { + background: ${({ theme }) => theme.colors.secondary[9]}; + } + } + + option { + background-color: ${({ theme }) => theme.colors.primary[1]}; + text-align: ${({ $align }) => ($align !== 'left' ? 'center' : 'left')}; + color: ${({ theme }) => theme.colors.secondary[0]}; + + &:disabled { + background: ${({ theme }) => theme.colors.secondary[9]}; + } + } +`; diff --git a/src/components/atoms/DropDownSelect/DropDownSelect.tsx b/src/components/atoms/DropDownSelect/DropDownSelect.tsx new file mode 100644 index 000000000..52995288a --- /dev/null +++ b/src/components/atoms/DropDownSelect/DropDownSelect.tsx @@ -0,0 +1,54 @@ +import React, { HTMLAttributes } from 'react'; +import { SelectField } from './DropDownSelect.style'; + +export interface IDropDownSelect extends Omit, 'type'> { + align?: 'left' | 'center'; + values?: string[]; + value?: string; + disabled?: boolean; + placeholder?: string; + emptySelect?: boolean; + children?: React.ReactNode; + activeHandler?: (value: boolean) => void; +} +// ------------------------------------------------------------------ // +// ---------------- the blank drop down select ---------------------- // +// ------------------------------------------------------------------ // +export default function DropDownSelect(props: IDropDownSelect) { + const { values, value, placeholder, children, align, onChange, activeHandler, emptySelect, ...htmlInputProps } = props; + + return ( + activeHandler && activeHandler(true)} + onBlur={() => activeHandler && activeHandler(false)} + {...htmlInputProps} + > + {/* Placeholder option */} + {placeholder && ( + + )} + + {/* Empty Select Option */} + {emptySelect && ( + + )} + {/* Children */} + + {values?.map((item, i) => ( + + ))} + + {children} + + ); +} diff --git a/src/components/atoms/DropDownSelect/index.ts b/src/components/atoms/DropDownSelect/index.ts new file mode 100644 index 000000000..9f07a3e19 --- /dev/null +++ b/src/components/atoms/DropDownSelect/index.ts @@ -0,0 +1 @@ +export { default as DropDownSelect } from './DropDownSelect'; diff --git a/src/components/atoms/EditBarModal/EditBarModal.stories.tsx b/src/components/atoms/EditBarModal/EditBarModal.stories.tsx new file mode 100644 index 000000000..8a92419f9 --- /dev/null +++ b/src/components/atoms/EditBarModal/EditBarModal.stories.tsx @@ -0,0 +1,61 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import EditBarModal from './EditBarModal'; +// Define metadata for the story +const meta = { + component: EditBarModal, + parameters: { + docs: { + description: { + component: + 'The DropDownSelect component is for displaying a dropdown select, it can be used for selecting a value from a list
- values: the values to be displayed in the dropdown
- onChange: function to be called when the value is changed
- placeholder: the placeholder text to be displayed when no value is selected
- value: the value to be displayed
- emptySelect: is the select empty
- disabled: is the select disabled', + }, + }, + }, + + // Define arguments for the story + argTypes: { + scrollable: { + control: { type: 'boolean' }, + }, + align: { + control: { type: 'select', options: ['left', 'center', 'right'] }, + }, + title: { + control: { type: 'text' }, + }, + width: { + control: { type: 'text' }, + }, + spacingLeftRight: { + control: { type: 'text' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + align: 'center', + title: 'Edit Bar', + width: '100%', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/EditBarModal/EditBarModal.style.tsx b/src/components/atoms/EditBarModal/EditBarModal.style.tsx new file mode 100644 index 000000000..f81d6b2ec --- /dev/null +++ b/src/components/atoms/EditBarModal/EditBarModal.style.tsx @@ -0,0 +1,53 @@ +import { styled, css } from 'styled-components'; + +import { boxShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TTheme } from '@/interface/TTheme'; + +//this calculates the spacing from left and right or nothing +const calcBarWidthandSpacing = (width?: string, spacingLeftRight?: string) => { + if (width) { + if (spacingLeftRight) { + return css` + width: calc(${width} - ${spacingLeftRight}); + `; + } else { + return css` + width: ${width}; + `; + } + } else { + if (spacingLeftRight) { + return css` + width: calc(100% - ${spacingLeftRight}); + `; + } else { + return css` + width: 100%; + `; + } + } +}; + +interface IWrapper { + $width?: string; + $secondBar?: boolean; + $spacingLeftRight?: string; +} +export const Wrapper = styled.div` + box-sizing: border-box; + position: relative; + padding: ${({ theme }) => theme.spacing.md}; + z-index: 99; + ${({ $width, $spacingLeftRight }) => calcBarWidthandSpacing($width, $spacingLeftRight)}; + background-color: ${({ theme }) => theme.colors.primary[1]}; + border-radius: 12px 12px 0px 0px; + ${boxShadow.sm} +`; + +export const WrapperContent = styled.div<{ theme: TTheme }>` + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: ${({ theme }) => theme.spacing.md}; +`; diff --git a/src/components/atoms/EditBarModal/EditBarModal.tsx b/src/components/atoms/EditBarModal/EditBarModal.tsx new file mode 100644 index 000000000..38344b30c --- /dev/null +++ b/src/components/atoms/EditBarModal/EditBarModal.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { IEditBarModal } from './IEditbarModal.model'; +import { Wrapper, WrapperContent } from './EditBarModal.style'; + +import { FancyModalHeadLine } from '../../molecules/FancyModalHeadLine'; + +export default function EditBarModal(props: IEditBarModal) { + const { children, width, spacingLeftRight, title } = props; + + return ( + + + {children} + + ); +} diff --git a/src/components/atoms/EditBarModal/IEditbarModal.model.ts b/src/components/atoms/EditBarModal/IEditbarModal.model.ts new file mode 100644 index 000000000..cd6ecbbc8 --- /dev/null +++ b/src/components/atoms/EditBarModal/IEditbarModal.model.ts @@ -0,0 +1,8 @@ +export interface IEditBarModal { + title?: string; + children?: React.ReactElement[]; + align?: string; + scrollable?: boolean; + width?: string; + spacingLeftRight?: string; +} diff --git a/src/components/atoms/EditBarModal/index.ts b/src/components/atoms/EditBarModal/index.ts new file mode 100644 index 000000000..5062a20c3 --- /dev/null +++ b/src/components/atoms/EditBarModal/index.ts @@ -0,0 +1 @@ +export { default as EditBarModal } from './EditBarModal'; diff --git a/src/components/atoms/FancyActionWrapper/FancyActionWrapper.style.tsx b/src/components/atoms/FancyActionWrapper/FancyActionWrapper.style.tsx new file mode 100644 index 000000000..5a5d3c394 --- /dev/null +++ b/src/components/atoms/FancyActionWrapper/FancyActionWrapper.style.tsx @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +export const StyledButton = styled.button` + padding: 0; + border: none; + background-color: transparent; + cursor: pointer; + width: 100%; +`; + +export const StyledAnchor = styled.a` + padding: 0; + border: none; + background-color: transparent; + cursor: pointer; + width: 100%; + text-decoration: none; +`; diff --git a/src/components/atoms/FancyActionWrapper/FancyActionWrapper.tsx b/src/components/atoms/FancyActionWrapper/FancyActionWrapper.tsx new file mode 100644 index 000000000..443133c02 --- /dev/null +++ b/src/components/atoms/FancyActionWrapper/FancyActionWrapper.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import ComponentAsWrapper from '../ComponentAsWrapper/ComponentAsWrapper'; +import { StyledAnchor, StyledButton } from './FancyActionWrapper.style'; + +// Define types for button and anchor attributes +type TNativeButtonProps = React.HTMLAttributes & { as?: 'button' }; +type TNativeAnchorProps = React.AnchorHTMLAttributes & { as: 'a' }; + +// Define a type that allows either specific button or anchor props along with ComponentAsWrapperProps +type IFancyActionWrapper = { + children: React.ReactNode; + WrapperComponent?: React.ReactElement; +} & (TNativeButtonProps | TNativeAnchorProps); + +export default function FancyActionWrapper(props: IFancyActionWrapper) { + const { children, WrapperComponent, ...HTMLProps } = props; + + const Wrapper = WrapperComponent ? ( + WrapperComponent + ) : HTMLProps.as === 'a' ? ( + + ) : ( + + ); + + return ; +} diff --git a/src/components/atoms/FancyBox/FancyBox.mdx b/src/components/atoms/FancyBox/FancyBox.mdx new file mode 100644 index 000000000..559ea4e6d --- /dev/null +++ b/src/components/atoms/FancyBox/FancyBox.mdx @@ -0,0 +1,68 @@ +## FancyBox Documentation + +_I have given this a lot of thought, and I am a great enthusiast of customizable components. In the present times, there is an abundance of excellent headers and bottom bars, and I believe developers should have the liberty to design interfaces as they imagine. Therefore, I developed the FancyBox. It primarily offers the theme for a component, leaving the rest to your creativity. You can effortlessly integrate your styles using inline styles, and they will be added to the existing class. Whether you are designing a side nav, a header, or a bottom bar, FancyBox has everything you need._ + +### Overview + +FancyBox is a versatile component that can act as the core element for various parts of an application's UI, such as headers, footers, sidebars, or any other type of bar. With its straightforward yet extensive property system, it enables developers to customize its look and functionality, adapting it to specific needs. + +### Initial Setup + +1. **Import the Component**: + + Begin by importing the `FancyBox` component into your file. + + ```javascript + import FancyBox from 'path-to-FancyBox'; + ``` + +2. **Use the Component**: + + Utilize the FancyBox component, inputting any necessary properties to modify its look and features. + + ```javascript + + Your content here... + + ``` + +### Using FancyBox + +1. **Customization with Properties**: + + FancyBox provides various properties for customizing its appearance: + + - `themeType`: Sets the theme color. This property can accept any keys from the `TUiColorsType`. + - `outlined`: A boolean indicating whether the bar is styled with an outline. + - `layer`: This property aids in setting the depth or z-index of the component. + - `outlinedBackgroundStrength`: Dictates the intensity of the background when `outlined` is true. + - `style`: Permits inline CSS styling on the bar. + + ```javascript + + Your themed and outlined bar content... + + ``` + +2. **Incorporating Child Components**: + + FancyBox accepts child components, enabling developers to embed other components or elements within the bar. + + ```javascript + + +

This is some text inside the FancyBox

+ + ``` + +### Advanced Styling + +You are not confined to the predefined styles. With the `style` property, developers can input inline styles to further personalize the appearance of the FancyBox. These styles will be combined with the preexisting styles, offering more versatility. + +```javascript +A FancyBox with custom style... +``` + +### Conclusion + +The FancyBox component embodies the philosophy that developers should have the freedom to mold components as they see fit. With its wide range of customization choices and the ability to style as desired, FancyBox emerges as a sturdy, adaptable, and essential tool in the UI toolkit. Begin crafting your ideal UI, one piece at a time, starting with the FancyBox. diff --git a/src/components/atoms/FancyBox/FancyBox.model.ts b/src/components/atoms/FancyBox/FancyBox.model.ts new file mode 100644 index 000000000..c309b2043 --- /dev/null +++ b/src/components/atoms/FancyBox/FancyBox.model.ts @@ -0,0 +1,16 @@ +import { CSSProp } from 'styled-components'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; + +type HTMLDivElementProps = Omit, 'style'>; +interface IFancyBox { + as?: keyof JSX.IntrinsicElements; + outlined?: boolean; + layer?: TLayer; + themeType?: TThemeTypes; + children?: React.ReactNode; + outlinedBackgroundStrength?: number; + externalStyle?: CSSProp; +} + +export type IFancyBoxProps = IFancyBox & HTMLDivElementProps; diff --git a/src/components/atoms/FancyBox/FancyBox.stories.tsx b/src/components/atoms/FancyBox/FancyBox.stories.tsx new file mode 100644 index 000000000..ec3ff465a --- /dev/null +++ b/src/components/atoms/FancyBox/FancyBox.stories.tsx @@ -0,0 +1,169 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyBox from './FancyBox'; +import { css } from 'styled-components'; +import { FancyMiniProfile } from '../../molecules/FancyMiniProfile'; +import { HeaderTitleWithLogo } from '../../molecules/HeaderTitleWithLogo'; +import { FancyBottomBarIcon } from '../../templates/FancyBottomBarIcon'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; +import React from 'react'; +import { FancySearchBar } from '../../organisms/FancySearchBar'; + +// Define metadata for the story +const meta = { + component: FancyBox, + + parameters: { + docs: { + description: { + component: + 'The FancyCard component is for displaying a card that can fill with somthing, it can be used for displaying content in a card
- height: the height of the card
- width: the width of the card
- themeType: the theme type of the card
- layer: the layer of the card
- textLayer: the layer of the text
- roundedEdges: the rounded edges of the card
- shadow: is the card shadowed', + }, + }, + }, + + // Define arguments for the story + argTypes: { + themeType: { + description: 'The theme type of the card', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the card', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 3, + }, + }, + outlined: { + description: 'Is the card outlined', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + outlinedBackgroundStrength: { + description: 'The background strength of the outlined card', + control: { + type: 'range', + min: 0, + max: 1, + step: 0.1, + }, + defaultValue: { + summary: 0.5, + }, + }, + externalStyle: { + description: 'The style of the card can porvieed with the styled-component css and react style={{width: "100px"}}', + control: { + type: 'object', + }, + }, + }, +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => Hiii, + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; + +const headerStyle = css` + position: fixed; + top: 10px; + width: 80%; + padding: 12px 18px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 50px; +`; + +export const Header: Story = { + render: (args) => ( + +
+ +
+ +
+ +
+
+ ), + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; + +const BottomBarStyle = css` + position: fixed; + bottom: 10px; + width: 100%; + padding: 12px 8px 0px 8px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 12px 12px 0 0; +`; + +const BottomBarComponent = (args: React.ComponentProps) => { + const [wichIsActive, setWichIsActive] = React.useState('0'); + + return ( + + } label="Test" isActive={wichIsActive === '0'} onClick={() => setWichIsActive('0')} /> + } label="Test" isActive={wichIsActive === '1'} onClick={() => setWichIsActive('1')} /> + } label="Test" isActive={wichIsActive === '2'} onClick={() => setWichIsActive('2')} /> + } label="Test" isActive={wichIsActive === '3'} onClick={() => setWichIsActive('3')} /> + + ); +}; + +export const BottomBar: Story = { + render: (args) => , + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyBox/FancyBox.style.tsx b/src/components/atoms/FancyBox/FancyBox.style.tsx new file mode 100644 index 000000000..28692b815 --- /dev/null +++ b/src/components/atoms/FancyBox/FancyBox.style.tsx @@ -0,0 +1,16 @@ +import { styled } from 'styled-components'; + +import generateThemeForCard from '../../../design/designFunctions/generateThemeForCard/generateThemeForCard'; +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { IFancyBoxProps } from './FancyBox.model'; +import { TTheme } from '@/interface/TTheme'; + +// the styled-component for the FancyBar +type IStyledFancyBox = IStyledPrefixAndPicker & { theme: TTheme }; +export const StyledFancyBox = styled.div` + box-sizing: border-box; + ${({ $themeType, theme, $layer, $outlined, $outlinedBackgroundStrength }) => + generateThemeForCard({ $themeType, theme, $outlined, $layer, $outlinedBackgroundStrength })}; + + ${({ $externalStyle }) => $externalStyle}; +`; diff --git a/src/components/atoms/FancyBox/FancyBox.tsx b/src/components/atoms/FancyBox/FancyBox.tsx new file mode 100644 index 000000000..b2893d627 --- /dev/null +++ b/src/components/atoms/FancyBox/FancyBox.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { IFancyBoxProps } from './FancyBox.model'; +import { StyledFancyBox } from './FancyBox.style'; + +// --------------------------------------------------------------------------- // +// ------------ A Bar that can usesd for for the most components ------------ // +// --------------------------------------------------------------------------- // +export default function FancyBar(props: IFancyBoxProps) { + const { layer, themeType, outlined, outlinedBackgroundStrength, externalStyle } = props; + + return ( + + {props.children} + + ); +} diff --git a/src/components/atoms/FancyBox/index.ts b/src/components/atoms/FancyBox/index.ts new file mode 100644 index 000000000..0a6465ed3 --- /dev/null +++ b/src/components/atoms/FancyBox/index.ts @@ -0,0 +1 @@ +export { default as FancyBox } from './FancyBox'; diff --git a/src/components/atoms/FancyCard/Card.model.ts b/src/components/atoms/FancyCard/Card.model.ts new file mode 100644 index 000000000..972d340d5 --- /dev/null +++ b/src/components/atoms/FancyCard/Card.model.ts @@ -0,0 +1,20 @@ +import { IRoundedEdges } from '../../../design/designFunctions/edgeCalculation/edgeCalculation'; +import { TLayer } from '@/interface/TLayer'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TSpacings } from '@/interface/TSpacings'; + +// the scaling types for the card +type IScaling = '100%' | 'auto' | string; +// the raw styling props for the card +export interface StyledCardProps { + themeType?: TThemeTypes; + textLayer?: TLayer; + height?: IScaling; + width?: IScaling; + layer?: TLayer; + shadow?: boolean; + radius?: TBorderRadiusSizes; + roundedEdges?: IRoundedEdges; + padding?: TSpacings | false; +} diff --git a/src/components/atoms/FancyCard/FancyCard.style.tsx b/src/components/atoms/FancyCard/FancyCard.style.tsx new file mode 100644 index 000000000..a1d9ffe41 --- /dev/null +++ b/src/components/atoms/FancyCard/FancyCard.style.tsx @@ -0,0 +1,32 @@ +import { styled } from 'styled-components'; + +import { StyledCardProps } from './Card.model'; +import IStyledPrefixAndOmiter from '../../../interface/IStyledPrefixAndOmiter.model'; +import { spacingPx } from '../../../design/theme/designSizes'; +import edgeCalculation from '../../../design/designFunctions/edgeCalculation/edgeCalculation'; + +import { boxShadow } from '../../../design/designFunctions/shadows/shadows'; +import getColorsForComponent from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +// the converted $ styling props for the card +type IStyledCard = IStyledPrefixAndOmiter; +//the main design of the card +export const StyledCard = styled.div` + ${({ theme, $themeType = 'primary', $layer, $textLayer }) => getColorsForComponent({ theme, $themeType, $layer, $textLayer })} + overflow: hidden; + width: ${({ $width }) => $width}; + height: ${({ $height }) => $height}; + box-sizing: border-box; + padding: ${({ $padding }) => ($padding ? spacingPx[$padding] : '')}; + border-radius: ${({ $roundedEdges }) => edgeCalculation($roundedEdges)}; + ${({ $shadow }) => $shadow && boxShadow.sm}; +`; + +//the innercard makes it better to align the content with absolute position +export const InnerCard = styled.div` + width: 100%; + height: 100%; + box-sizing: border-box; + position: relative; +`; diff --git a/src/components/atoms/FancyCard/FancyCard.tsx b/src/components/atoms/FancyCard/FancyCard.tsx new file mode 100644 index 000000000..e4f4dd4c1 --- /dev/null +++ b/src/components/atoms/FancyCard/FancyCard.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import { InnerCard, StyledCard } from './FancyCard.style'; +import { StyledCardProps } from './Card.model'; +import { TLayer } from '@/interface/TLayer'; + +// --------------------------------------------------------------------------- // +// ---------- The card is there to wrapp some content or components ---------- // +// --------------------------------------------------------------------------- // +interface ICard extends StyledCardProps { + children?: React.ReactNode; + layer?: TLayer; +} +export default function FancyCard(props: ICard) { + const { children, height, width, radius, padding, roundedEdges, layer, shadow, themeType, textLayer } = { ...defaultProps, ...props }; + + return ( + + {children} + + ); +} + +const defaultProps: ICard = { + height: 'auto', + width: '100%', + padding: 'xl', + shadow: true, + roundedEdges: ['xl'], +}; diff --git a/src/components/atoms/FancyCard/FancyCrad.stories.tsx b/src/components/atoms/FancyCard/FancyCrad.stories.tsx new file mode 100644 index 000000000..b625877ff --- /dev/null +++ b/src/components/atoms/FancyCard/FancyCrad.stories.tsx @@ -0,0 +1,76 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyCard from './FancyCard'; +// Define metadata for the story +const meta = { + title: 'components/atoms/FancyCard', + component: FancyCard, + + parameters: { + docs: { + description: { + component: + 'The FancyCard component is for displaying a card that can fill with somthing, it can be used for displaying content in a card
- height: the height of the card
- width: the width of the card
- themeType: the theme type of the card
- layer: the layer of the card
- textLayer: the layer of the text
- roundedEdges: the rounded edges of the card
- shadow: is the card shadowed', + }, + }, + }, + + // Define arguments for the story + argTypes: { + height: { + control: { type: 'text' }, + }, + width: { + control: { type: 'text' }, + }, + themeType: { + control: { type: 'select', options: ['primary', 'secondary', 'accent'] }, + }, + layer: { + control: { type: 'range', min: 0, max: 10, step: 1 }, + }, + textLayer: { + control: { type: 'range', min: 0, max: 10, step: 1 }, + }, + roundedEdges: { + control: { type: 'object' }, + }, + shadow: { + control: { type: 'boolean' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + +

Some Random Content

+
+ ), + args: { + roundedEdges: ['sm', 'md', 'lg', 'xl'], + shadow: true, + themeType: 'primary', + layer: 3, + width: '100%', + height: '100%', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyCard/index.ts b/src/components/atoms/FancyCard/index.ts new file mode 100644 index 000000000..9fcc02081 --- /dev/null +++ b/src/components/atoms/FancyCard/index.ts @@ -0,0 +1 @@ +export { default as FancyCard } from './FancyCard'; diff --git a/src/components/atoms/FancyImage/FancyImage.stories.tsx b/src/components/atoms/FancyImage/FancyImage.stories.tsx new file mode 100644 index 000000000..2cc3b4e07 --- /dev/null +++ b/src/components/atoms/FancyImage/FancyImage.stories.tsx @@ -0,0 +1,56 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyImage from './FancyImage'; +// Define metadata for the story +const meta = { + component: FancyImage, + + parameters: { + docs: { + description: { + component: 'A FancyImage component is a styled image component that can be used to display images in a fancy way.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + darken: { + control: { type: 'boolean' }, + }, + alt: { + control: { type: 'text' }, + }, + imageUrl: { + control: { type: 'text' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + imageUrl: 'https://www.az-online.de/bilder/2019/08/23/12938342/2113799823-tobias-rester-2tyMMSkM2R73.jpg', + aspectRatio: '2/4', + alt: 'Fannncy', + darken: false, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyImage/FancyImage.style.tsx b/src/components/atoms/FancyImage/FancyImage.style.tsx new file mode 100644 index 000000000..3d2475605 --- /dev/null +++ b/src/components/atoms/FancyImage/FancyImage.style.tsx @@ -0,0 +1,9 @@ +import { styled } from 'styled-components'; + +export const StyledImage = styled.img<{ $darken?: boolean; $aspectRatio?: string }>` + width: 100%; + object-fit: cover; + transition: filter 0.3s; + ${({ $aspectRatio }) => ($aspectRatio ? `aspect-ratio: ${$aspectRatio};` : '')} + filter: ${({ $darken }) => ($darken ? 'brightness(0.5)' : 'none')}; +`; diff --git a/src/components/atoms/FancyImage/FancyImage.tsx b/src/components/atoms/FancyImage/FancyImage.tsx new file mode 100644 index 000000000..140e0b588 --- /dev/null +++ b/src/components/atoms/FancyImage/FancyImage.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import { StyledImage } from './FancyImage.style'; +import { isAspectRatioValid } from '@/components/utils/validations/isAspectRatioValid'; + +// Define the props for the FancyImage component +export interface IFancyImage { + imageUrl: string; + aspectRatio?: string; // e.g. "16/9" + darken?: boolean; + alt?: string; +} +// --------------------------------------------------------------------------- // +// -------------- The Definition for the FancyImage Component ---------------- // +// --------------------------------------------------------------------------- // +export default function FancyImage(props: IFancyImage) { + const { imageUrl, aspectRatio, darken, alt } = props; + + // Validate the aspect ratio if it is provided + if (aspectRatio && !isAspectRatioValid(aspectRatio)) { + throw new Error('The aspect ratio is not valid. Please use the format "16/9"'); + } + + // Render the image with the provided props + return ; +} diff --git a/src/components/atoms/FancyImage/index.ts b/src/components/atoms/FancyImage/index.ts new file mode 100644 index 000000000..e731930a7 --- /dev/null +++ b/src/components/atoms/FancyImage/index.ts @@ -0,0 +1 @@ +export { default as FancyImage } from './FancyImage'; diff --git a/src/components/atoms/FancyLI/FancyLI.stories.tsx b/src/components/atoms/FancyLI/FancyLI.stories.tsx new file mode 100644 index 000000000..79f545e2a --- /dev/null +++ b/src/components/atoms/FancyLI/FancyLI.stories.tsx @@ -0,0 +1,54 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyLI from './FancyLI'; +// Define metadata for the story +const meta = { + component: FancyLI, + + parameters: { + docs: { + description: { + component: 'Fancy LI Item that can be dynamicly adjusted in size and alignment.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + size: { + control: { type: 'select' }, + options: ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'], + }, + aligned: { + control: { type: 'select' }, + options: ['left', 'center', 'right'], + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + children:

Huuu

, + size: 'md', + aligned: 'left', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyLI/FancyLI.tsx b/src/components/atoms/FancyLI/FancyLI.tsx new file mode 100644 index 000000000..3c790710b --- /dev/null +++ b/src/components/atoms/FancyLI/FancyLI.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import IStyledPrefixAndOmiter from '../../../interface/IStyledPrefixAndOmiter.model'; +import { spacingPx } from '../../../design/theme/designSizes'; +import { TSpacings } from '../../../interface/TSpacings'; + +type StyledFancyLi = IStyledPrefixAndOmiter; + +const FancyLiItem = styled.li` + list-style: none; + display: flex; + align-items: center; + width: 100%; + justify-content: ${({ $aligned }) => $aligned}; + padding: ${({ $size }) => $size && spacingPx[$size]}; +`; + +interface FancyLIProps { + aligned?: 'left' | 'center' | 'right'; + children?: React.ReactNode; + size?: TSpacings; +} +// --------------------------------------------------------------------------- // +// ------------- The fancy LI Item that can dynamicly adjusted --------------- // +// --------------------------------------------------------------------------- // +export default function FancyLI(props: FancyLIProps) { + const { children, size, aligned } = { ...defaultProps, ...props }; + + return ( + + {children} + + ); +} + +const defaultProps: FancyLIProps = { + size: 'md', + aligned: 'left', +}; diff --git a/src/components/atoms/FancyLI/index.ts b/src/components/atoms/FancyLI/index.ts new file mode 100644 index 000000000..27594c6fa --- /dev/null +++ b/src/components/atoms/FancyLI/index.ts @@ -0,0 +1 @@ +export { default as FancyLI } from './FancyLI'; diff --git a/src/components/atoms/FancyLine/FancyLine.stories.tsx b/src/components/atoms/FancyLine/FancyLine.stories.tsx new file mode 100644 index 000000000..762c850a6 --- /dev/null +++ b/src/components/atoms/FancyLine/FancyLine.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyLine from './FancyLine'; + +const meta = { + component: FancyLine, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: Only a Line thats sperate something from something else.', + }, + }, + }, + argTypes: { + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + defaultValue: { + summary: 'accent', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 0, + }, + }, + direction: { + description: 'The direction of the line', + control: { + type: 'select', + }, + defaultValue: { + summary: 'horizontal', + }, + }, + thickness: { + description: 'The thickness of the line', + control: { + type: 'text', + }, + }, + margin: { + description: 'The margin from the line to the edge', + control: { + type: 'text', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + themeType: 'accent', + layer: 0, + direction: 'horizontal', + thickness: '1px', + margin: '0', + }, +}; diff --git a/src/components/atoms/FancyLine/FancyLine.tsx b/src/components/atoms/FancyLine/FancyLine.tsx new file mode 100644 index 000000000..972fa5022 --- /dev/null +++ b/src/components/atoms/FancyLine/FancyLine.tsx @@ -0,0 +1,49 @@ +import { styled } from 'styled-components'; + +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { TTheme } from '@/interface/TTheme'; +import { TThemeTypes } from '@/interface/TUiColors'; + +type TStyledFancyLine = IStyledPrefixAndPicker & { theme?: TTheme }; +const StyledFancyLine = styled.hr` + background-color: ${({ theme, $themeType = 'accent', $layer }) => getBackgroundColor({ theme, $themeType, $layer })}; + display: block; + width: ${({ $direction, $thickness }) => ($direction === 'vertical' ? $thickness || '1px' : '')}; + height: ${({ $direction, $thickness }) => ($direction === 'horizontal' ? $thickness || '1px' : '')}; + border-radius: 2px; + align-self: stretch; + border: 0; + padding: 0; + margin: ${({ $margin }) => $margin || '0'}; + + &::after { + content: ''; + display: block; + align-self: stretch; + width: ${({ $direction, $thickness }) => ($direction === 'vertical' ? $thickness || '1px' : '')}; + height: ${({ $direction, $thickness }) => ($direction === 'horizontal' ? $thickness || '1px' : '')}; + background-color: ${({ theme, $themeType = 'accent', $layer }) => getBackgroundColor({ theme, $themeType, $layer })}; + border: 0; + padding: 0; + filter: blur(0.5px); + } +`; + +type IFancyLine = { + direction?: 'horizontal' | 'vertical'; + thickness?: string; + margin?: string; + themeType?: TThemeTypes; + layer?: TLayer; +}; +// --------------------------------------------------------------------------- // +// ------------ A dynamic line (vertical/horizontal) for better UX/UI ------- // +// --------------------------------------------------------------------------- // +export default function FancyLine(props: IFancyLine) { + const { direction, thickness, margin, themeType, layer } = props; + return ( + + ); +} diff --git a/src/components/atoms/FancyLine/index.ts b/src/components/atoms/FancyLine/index.ts new file mode 100644 index 000000000..8d35e9550 --- /dev/null +++ b/src/components/atoms/FancyLine/index.ts @@ -0,0 +1 @@ +export { default as FancyLine } from './FancyLine'; diff --git a/src/components/atoms/FancyLoadingBar/FancyLoadingBar.stories.tsx b/src/components/atoms/FancyLoadingBar/FancyLoadingBar.stories.tsx new file mode 100644 index 000000000..dd7d2a98e --- /dev/null +++ b/src/components/atoms/FancyLoadingBar/FancyLoadingBar.stories.tsx @@ -0,0 +1,41 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyLoadingBar from './FancyLoadingBar'; +// Define metadata for the story +const meta = { + component: FancyLoadingBar, + + parameters: { + docs: { + description: { + component: 'Fancy LI Item that can be dynamicly adjusted in size and alignment.', + }, + }, + }, + + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyLoadingBar/FancyLoadingBar.tsx b/src/components/atoms/FancyLoadingBar/FancyLoadingBar.tsx new file mode 100644 index 000000000..b32dccbff --- /dev/null +++ b/src/components/atoms/FancyLoadingBar/FancyLoadingBar.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { styled, keyframes } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// Define keyframe animation for the loading bar +const loadingAnimation = keyframes` + 0% { + left: -70%; + } + 100% { + left: 100%; + } +`; + +// Define a styled component for the loading container +const LoadingContainer = styled.div` + position: relative; + width: 100%; + height: 2px; + overflow: hidden; +`; + +// Define a styled component for the loading bar +const LoadingBar = styled.div<{ theme: TTheme }>` + position: absolute; + left: -70%; + height: 100%; + width: 70%; + background-image: linear-gradient(90deg, transparent, ${({ theme }) => theme.colors.accent[0]}, transparent); + animation: ${loadingAnimation} 3s ease-in-out infinite; // Set the animation duration to 3s +`; + +// ----------------------------------------------------- // +// ------------------ A Loadingbar --------------------- // +// ----------------------------------------------------- // +export default function FancyLoadingBar() { + return ( + + + + ); +} diff --git a/src/components/atoms/FancyLoadingBar/index.ts b/src/components/atoms/FancyLoadingBar/index.ts new file mode 100644 index 000000000..fc82ba895 --- /dev/null +++ b/src/components/atoms/FancyLoadingBar/index.ts @@ -0,0 +1 @@ +export { default as FancyLoadingBar } from './FancyLoadingBar'; diff --git a/src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.stories.tsx b/src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.stories.tsx new file mode 100644 index 000000000..dc7542d39 --- /dev/null +++ b/src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.stories.tsx @@ -0,0 +1,41 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyLoadingSpinner from './FancyLoadingSpinner'; +// Define metadata for the story +const meta = { + component: FancyLoadingSpinner, + + parameters: { + docs: { + description: { + component: 'FancyLooadingSpinner that indicates loading and can be dynamicly adjusted in the size.', + }, + }, + }, + + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.tsx b/src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.tsx new file mode 100644 index 000000000..bc6240d10 --- /dev/null +++ b/src/components/atoms/FancyLoadingSpinner/FancyLoadingSpinner.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { styled, keyframes, css, CSSProp } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// Define keyframe animations for the spinner +const spinner = keyframes` + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +`; + +const reverseSpinner = keyframes` + 0% { transform: rotate(360deg); } + 100% { transform: rotate(0deg); } +`; + +// Define size options for the spinner +const sizes = { + sm: { + width: '16px', + thickness: '1px', + thicknessInner: '0.5px', + }, + md: { + width: '32px', + thickness: '1.5px', + thicknessInner: '1px', + }, + lg: { + width: '48px', + thickness: '1.5px', + thicknessInner: '1px', + }, + xl: { + width: '64px', + thickness: '2px', + thicknessInner: '1.5px', + }, + xxl: { + width: '96px', + thickness: '2px', + thicknessInner: '1.5px', + }, + xxxl: { + width: '128px', + thickness: '2px', + thicknessInner: '1.5px', + }, +}; + +// Define a styled component for the spinner container +const SpinnerContainer = styled.div<{ $size?: keyof typeof sizes }>` + position: relative; + width: ${({ $size }) => ($size ? sizes[$size].width : sizes.md.width)}; + height: ${({ $size }) => ($size ? sizes[$size].width : sizes.md.width)}; + display: flex; + align-items: center; + justify-content: center; +`; + +// Define a function to generate the border for the spinner +const generateBorder = (size: string, theme: TTheme): CSSProp => { + return css` + border-top: ${size} solid transparent; + border-right: ${size} solid ${theme.colors.accent[0]}; + border-bottom: ${size} solid transparent; + border-left: ${size} solid ${theme.colors.accent[0]}; + `; +}; + +// Define a styled component for the inner spinner +const StyledInnerSpinner = styled.div<{ $size?: keyof typeof sizes; theme: TTheme }>` + ${({ $size, theme }) => generateBorder($size ? sizes[$size].thicknessInner : sizes.md.thickness, theme)} + animation: ${reverseSpinner} 2s infinite ease-in-out; + border-radius: 50%; + width: 80%; + aspect-ratio: 1 / 1; +`; + +// Define a styled component for the outer spinner +const StyledFancyLoadingSpinner = styled.div<{ $size?: keyof typeof sizes; $thickness?: string; theme: TTheme }>` + ${({ $size, theme }) => generateBorder($size ? sizes[$size].thickness : sizes.md.thickness, theme)} + position: absolute; + animation: ${spinner} 2s infinite ease-in-out; + border-radius: 50%; + width: 100%; + aspect-ratio: 1 / 1; +`; + +// Define the props for the FancyLoadingSpinner component +// --------------------------------------------------------------------------- // +// ------------------ A Loadingspinner with different sizes ------------------ // +// --------------------------------------------------------------------------- // +interface IFancyLoadingSpinner { + size?: keyof typeof sizes; +} +export default function FancyLoadingSpinner({ size = 'md' }: IFancyLoadingSpinner) { + return ( + + + + + ); +} diff --git a/src/components/atoms/FancyLoadingSpinner/index.ts b/src/components/atoms/FancyLoadingSpinner/index.ts new file mode 100644 index 000000000..d665a7930 --- /dev/null +++ b/src/components/atoms/FancyLoadingSpinner/index.ts @@ -0,0 +1 @@ +export { default as FancyLoadingSpinner } from './FancyLoadingSpinner'; diff --git a/src/components/atoms/FancyProfilePicture/FancyProfilePicture.stories.tsx b/src/components/atoms/FancyProfilePicture/FancyProfilePicture.stories.tsx new file mode 100644 index 000000000..7594d1145 --- /dev/null +++ b/src/components/atoms/FancyProfilePicture/FancyProfilePicture.stories.tsx @@ -0,0 +1,95 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyProfilePicture from './FancyProfilePicture'; +// Define metadata for the story +const meta = { + component: FancyProfilePicture, + + parameters: { + docs: { + description: { + component: 'The FancyProfilePicture component is a fancy profile picture that can be dynamicly adjusted in size and roundedness.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + rounded: { + control: { type: 'select' }, + options: ['sm', 'md', 'lg', 'complete'], + }, + size: { + control: { type: 'select' }, + options: ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'], + }, + src: { + control: { type: 'text' }, + description: 'The source of the image.', + }, + alt: { + control: { type: 'text' }, + description: 'The alt text of the image is used as a placeholder if no image is provided.', + defaultValue: { + summary: 'Profile', + }, + }, + letterLength: { + control: { type: 'number' }, + description: 'The amount of letters to be displayed in the placeholder.', + defaultValue: { + summary: 2, + }, + }, + layer: { + control: { type: 'range', min: 1, max: 10, step: 1 }, + description: 'The layer of the image.', + defaultValue: { + summary: 3, + }, + }, + themeType: { + control: { type: 'select' }, + description: 'The themeType of the image.', + defaultValue: { + summary: 'primary', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + src: 'https://avatars.githubusercontent.com/u/54409958?v=4', + rounded: 'complete', + size: 'md', + alt: 'Profile', + letterLength: 2, + layer: 3, + themeType: 'primary', + }, +}; + +export const Avatar: Story = { + render: (args) => , + args: { + rounded: 'complete', + size: 'md', + alt: 'Profile', + letterLength: 2, + layer: 3, + themeType: 'primary', + }, +}; diff --git a/src/components/atoms/FancyProfilePicture/FancyProfilePicture.style.tsx b/src/components/atoms/FancyProfilePicture/FancyProfilePicture.style.tsx new file mode 100644 index 000000000..452026b22 --- /dev/null +++ b/src/components/atoms/FancyProfilePicture/FancyProfilePicture.style.tsx @@ -0,0 +1,77 @@ +import { styled } from 'styled-components'; + +import { IFancyProfilePicture } from './FancyProfilePicture'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; + +// Set the border based on the prop +type TBorderRadius = IFancyProfilePicture['rounded']; +const getBorderRadius = (borderRadius: TBorderRadius) => { + switch (borderRadius) { + case 'sm': + return '10%'; + case 'md': + return '20%'; + case 'lg': + return '30%'; + case 'complete': + return '50%'; + default: + return ''; + } +}; + +// Set size based on prop +export type TSize = 'sm' | 'md' | 'lg' | 'xxs' | 'xs' | 'xl' | 'xxl'; +const sizeConfig: Record = { + xxs: '24px', + xs: '32px', + sm: '48px', + md: '96px', + lg: '128px', + xl: '192px', + xxl: '256px', + default: '100px', +}; + +const getSize = (size: TSize | string) => { + // Check if the size is a percentage value + if (typeof size === 'string' && size.endsWith('%')) return size; + + // Return the size from the config or default if not found + return sizeConfig[size] || sizeConfig['default']; +}; + +// Styled img component +export const StyledImage = styled.img<{ $rounded: TBorderRadius; $size: TSize | string }>` + border-radius: ${({ $rounded }) => getBorderRadius($rounded)}; + width: ${({ $size }) => getSize($size)}; + height: ${({ $size }) => getSize($size)}; + object-fit: cover; + aspect-ratio: 1/1; +`; + +// Styled placeholder component for the profile picture +interface IPlaceholderProps { + $rounded: TBorderRadius; + $size: TSize | string; + $color?: string; + $backgroundColor?: string; + theme?: TTheme; + $themeType?: TThemeTypes; + $layer?: TLayer; +} + +export const Placeholder = styled.div` + border-radius: ${({ $rounded }) => getBorderRadius($rounded)}; + width: ${({ $size }) => getSize($size)}; + height: ${({ $size }) => getSize($size)}; + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ $backgroundColor, theme, $layer }) => ($backgroundColor ? $backgroundColor : theme.colors.primary[$layer ?? 2])}; + color: ${({ $color, theme, $layer }) => ($color ? $color : theme.colors.secondary[$layer ?? 1])}; + font-size: 1.5em; + aspect-ratio: 1/1; +`; diff --git a/src/components/atoms/FancyProfilePicture/FancyProfilePicture.tsx b/src/components/atoms/FancyProfilePicture/FancyProfilePicture.tsx new file mode 100644 index 000000000..d3c080503 --- /dev/null +++ b/src/components/atoms/FancyProfilePicture/FancyProfilePicture.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import { Placeholder, StyledImage, TSize } from './FancyProfilePicture.style'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { Typography } from '../Typography'; +import { TTypography } from '@/interface/TTypography'; + +// generate the text size based on the size prop +const getTextSize = (size: TSize | string): TTypography => { + switch (size) { + case 'xxs': + case 'xs': + return 'smText'; + case 'sm': + return 'h6'; + case 'md': + return 'h3'; + case 'lg': + return 'h2'; + case 'xl': + case 'xxl': + return 'h1'; + default: + return 'h4'; + } +}; + +export interface IFancyProfilePicture { + src?: string; + rounded?: 'sm' | 'md' | 'lg' | 'complete'; + size?: TSize | string; + alt?: string; + layer?: TLayer; + themeType?: TThemeTypes; + letterLength?: number; + className?: string; +} +// --------------------------------------------------------------------------- // +// ProfilePicture component to render with different sizes and border radius // +// --------------------------------------------------------------------------- // +export default function FancyProfilePicture(props: IFancyProfilePicture) { + const { src, rounded, size, alt, letterLength, className, ...placeholderProps } = { ...defaultProps, ...props }; + + return src ? ( + + ) : ( + + + {alt.substring(0, letterLength ?? 2).toUpperCase()} + + + ); +} + +const defaultProps = { + size: 'md', + alt: 'Profile', +}; diff --git a/src/components/atoms/FancyProfilePicture/index.ts b/src/components/atoms/FancyProfilePicture/index.ts new file mode 100644 index 000000000..5aa5c1aa4 --- /dev/null +++ b/src/components/atoms/FancyProfilePicture/index.ts @@ -0,0 +1 @@ +export { default as FancyProfilePicture } from './FancyProfilePicture'; diff --git a/src/components/atoms/FancySVGAtom/FancySVGAtom.model.ts b/src/components/atoms/FancySVGAtom/FancySVGAtom.model.ts new file mode 100644 index 000000000..e44b8158e --- /dev/null +++ b/src/components/atoms/FancySVGAtom/FancySVGAtom.model.ts @@ -0,0 +1,30 @@ +import { CSSProp } from 'styled-components'; +import IStyledPrefixAndOmiter from '../../../interface/IStyledPrefixAndOmiter.model'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +export const sizes = { + xs: '14px', + sm: '16px', + md: '18px', + lg: '20px', + xl: '24px', + full: '100%', +}; + +export type ISizes = keyof typeof sizes; + +export type ISVGAtom = { + children?: React.ReactNode; + size?: ISizes; + externalStyle?: CSSProp; + isPassive?: boolean; + isActive?: boolean; + errorMessage?: string; + themeType?: TThemeTypes; + layer?: TLayer; +}; + +export type ISVGAtomProps = ISVGAtom & Omit, 'style'>; + +export type IStyledSVGAtom = IStyledPrefixAndOmiter; diff --git a/src/components/atoms/FancySVGAtom/FancySVGAtom.stories.tsx b/src/components/atoms/FancySVGAtom/FancySVGAtom.stories.tsx new file mode 100644 index 000000000..d4c6326ad --- /dev/null +++ b/src/components/atoms/FancySVGAtom/FancySVGAtom.stories.tsx @@ -0,0 +1,73 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +import SVGXCircle from '../../icons/SVGXCircle/SVGXCircle'; + +// Import the component to be tested +import FancySVGAtom from './FancySVGAtom'; +// Define metadata for the story +const meta = { + component: FancySVGAtom, + + parameters: { + docs: { + description: { + component: 'Fancy SVG Atom that can be dynamicly adjusted in size and alignment. It can also be used to display error messages.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + size: { + control: { type: 'select' }, + }, + themeType: { + control: { type: 'select' }, + }, + layer: { + control: { type: 'range', min: 1, max: 10, step: 1 }, + }, + errorMessage: { + control: { type: 'text' }, + }, + isActive: { + control: { type: 'boolean' }, + }, + isPassive: { + control: { type: 'boolean' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + + + ), + args: { + isPassive: false, + size: 'md', + themeType: 'secondary', + errorMessage: '', + layer: 1, + isActive: true, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancySVGAtom/FancySVGAtom.tsx b/src/components/atoms/FancySVGAtom/FancySVGAtom.tsx new file mode 100644 index 000000000..4e1e83fed --- /dev/null +++ b/src/components/atoms/FancySVGAtom/FancySVGAtom.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import { ISVGAtomProps, IStyledSVGAtom, sizes } from './FancySVGAtom.model'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +interface ICalcIconColor { + theme: TTheme; + $isActive?: boolean; + $errorMessage?: string | undefined; + $themeType: TThemeTypes; + $layer?: number; +} + +const calcIconColor = ({ theme, $isActive, $errorMessage, $themeType, $layer }: ICalcIconColor): string => { + if (!$errorMessage) { + return $isActive ? theme.colors.accent[0] : getBackgroundColor({ theme, $themeType, $layer }); + } else { + return theme.colors.error[0]; + } +}; + +const StyledSVG = styled.i` + display: flex; + justify-content: center; + font-style: normal; + align-items: center; + width: ${({ $size }) => sizes[$size!]}; + aspect-ratio: 1/1; + color: ${({ $isActive, $errorMessage, $isPassive, theme, $themeType = 'secondary', $layer = 0 }) => + !$isPassive && calcIconColor({ theme, $isActive, $errorMessage, $layer, $themeType })}; + ${({ $externalStyle }) => $externalStyle}; + will-change: transform; + + svg { + width: 100%; + height: 100%; + } +`; + +// --------------------------------------------------------------------------- // +// --------- This is a wrapper for SVGs to wrap them and style them ---------- // +// --------------------------------------------------------------------------- // +export default function FancySVGAtom(props: ISVGAtomProps) { + const { children, isPassive, size, isActive, errorMessage, externalStyle, themeType, layer, ...htmlProps } = { + ...defaultProps, + ...props, + }; + + return ( + + {children} + + ); +} + +const defaultProps: ISVGAtomProps = { + size: 'md', + isPassive: false, + isActive: false, +}; diff --git a/src/components/atoms/FancySVGAtom/index.ts b/src/components/atoms/FancySVGAtom/index.ts new file mode 100644 index 000000000..670e83ee5 --- /dev/null +++ b/src/components/atoms/FancySVGAtom/index.ts @@ -0,0 +1 @@ +export { default as FancySVGAtom } from './FancySVGAtom'; diff --git a/src/components/atoms/FancyVideo/FancyVideo.stories.tsx b/src/components/atoms/FancyVideo/FancyVideo.stories.tsx new file mode 100644 index 000000000..6bee9c7a2 --- /dev/null +++ b/src/components/atoms/FancyVideo/FancyVideo.stories.tsx @@ -0,0 +1,69 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyVideo from './FancyVideo'; + +// Define metadata for the story +const meta = { + component: FancyVideo, + parameters: { + docs: { + description: { + component: 'FancyLooadingSpinner that indicates loading and can be dynamicly adjusted in the size.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + src: { + control: { type: 'text' }, + }, + controls: { + control: { type: 'boolean' }, + }, + loop: { + control: { type: 'boolean' }, + }, + muted: { + control: { type: 'boolean' }, + }, + autoPlay: { + control: { type: 'boolean' }, + }, + darken: { + control: { type: 'boolean' }, + }, + poster: { + control: { type: 'text' }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + aspectRatio: '16/9', + src: 'https://www.w3schools.com/html/mov_bbb.mp4', + controls: true, + loop: false, + muted: false, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyVideo/FancyVideo.tsx b/src/components/atoms/FancyVideo/FancyVideo.tsx new file mode 100644 index 000000000..06d735cda --- /dev/null +++ b/src/components/atoms/FancyVideo/FancyVideo.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import { isAspectRatioValid } from '@/components/utils/validations/isAspectRatioValid'; + +// Define a styled video component using styled-components +const StyledVideo = styled.video<{ $aspectRatio?: string; $darken?: boolean }>` + object-fit: cover; + width: 100%; + height: auto; + ${({ $aspectRatio }) => ($aspectRatio ? `aspect-ratio: ${$aspectRatio};` : '')} + filter: ${({ $darken }) => ($darken ? 'brightness(0.5)' : 'none')}; +`; + +// Define the props for the FancyVideo component +export interface IFancyVideo { + src: string; + controls?: boolean; + autoPlay?: boolean; + loop?: boolean; + muted?: boolean; + poster?: string; + aspectRatio?: string; // e.g. "16/9" + darken?: boolean; +} +// --------------------------------------------------------------------------- // +// -------------- The Definition for the FancyVideo Component ---------------- // +// --------------------------------------------------------------------------- // +export default function FancyVideo(props: IFancyVideo) { + const { src, controls, autoPlay, loop, muted, poster, aspectRatio, darken = true } = props; + + // Validate the aspect ratio if it is provided + if (aspectRatio && !isAspectRatioValid(aspectRatio)) { + throw new Error('The aspect ratio is not valid. Please use the format "16/9"'); + } + + // Render the video with the appropriate props + return ( + + + + ); +} diff --git a/src/components/atoms/FancyVideo/index.ts b/src/components/atoms/FancyVideo/index.ts new file mode 100644 index 000000000..e86391b91 --- /dev/null +++ b/src/components/atoms/FancyVideo/index.ts @@ -0,0 +1 @@ +export { default as FancyVideo } from './FancyVideo'; diff --git a/src/components/atoms/FancyXButton/FancyXButton.stories.tsx b/src/components/atoms/FancyXButton/FancyXButton.stories.tsx new file mode 100644 index 000000000..23f1fc6cb --- /dev/null +++ b/src/components/atoms/FancyXButton/FancyXButton.stories.tsx @@ -0,0 +1,55 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyXButton from './FancyXButton'; + +// Define metadata for the story +const meta = { + component: FancyXButton, + parameters: { + docs: { + description: { + component: 'FancyXButton is a button with a fancy X in it. It is used to close a modal or a popup.', + }, + }, + }, + + // Define arguments for the story + argTypes: { + themeType: { + description: + 'The themeType is used to define the color of the X in the button. It can be any of the colors defined in the theme or systemMessages.', + control: { + type: 'select', + options: ['primary', 'secondary', 'success', 'warning', 'info'], + }, + defaultValue: { + summary: 'accent', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + themeType: 'secondary', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/FancyXButton/FancyXButton.tsx b/src/components/atoms/FancyXButton/FancyXButton.tsx new file mode 100644 index 000000000..8df877704 --- /dev/null +++ b/src/components/atoms/FancyXButton/FancyXButton.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { styled } from 'styled-components'; +import Color from 'color'; + +import { fontSize } from '../../../design/theme/designSizes'; +import simpleColorTransition from '../../../design/designFunctions/simpleColorTransition/simpleTransition'; +import { TThemeTypesNotTrasparent } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +type FancyXButtonDesign = TThemeTypesNotTrasparent; + +interface IStyledFancyXButton { + $themeType?: FancyXButtonDesign; + $layer?: TLayer; + theme: TTheme; +} + +const StyledFancyXButton = styled.button` + padding: 0 ${({ theme }) => theme.spacing.sm} ${({ theme }) => theme.spacing.xxs}; + background: none; + color: ${({ $themeType = 'accent', theme, $layer }) => getBackgroundColor({ $themeType, theme, $layer })}; + border: none; + font-size: ${fontSize.xl}; + font-weight: bolder; + cursor: pointer; + outline: none; + ${simpleColorTransition} + + &:hover { + color: ${({ $themeType = 'accent', $layer, theme }) => Color(getBackgroundColor({ $themeType, $layer, theme })).darken(0.1).hex()}; + } +`; + +// --------------------------------------------------------------------------- // +// --------------- The main Component for the X Close Button------- ---------- // +// --------------------------------------------------------------------------- // +interface IFancyXButton { + onClick?: () => void; + themeType?: FancyXButtonDesign; + layer?: TLayer; +} +export default function FancyXButton({ onClick, themeType, layer }: IFancyXButton) { + //check wich design comes in and add the right color object uiColor or systemMessages to the button + + return ( + + x + + ); +} diff --git a/src/components/atoms/FancyXButton/index.ts b/src/components/atoms/FancyXButton/index.ts new file mode 100644 index 000000000..9efa5ea43 --- /dev/null +++ b/src/components/atoms/FancyXButton/index.ts @@ -0,0 +1 @@ +export { default as FancyXButton } from './FancyXButton'; diff --git a/src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.stories.tsx b/src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.stories.tsx new file mode 100644 index 000000000..8f976e800 --- /dev/null +++ b/src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.stories.tsx @@ -0,0 +1,55 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import ImageVideoOverlay from './ImageVideoOverlay'; +import { Typography } from '../Typography'; +import { FancyVideo } from '../FancyVideo'; + +// Define metadata for the story +const meta = { + component: ImageVideoOverlay, + parameters: { + docs: { + description: { + component: + 'The ImageVideoOverlay is a Wrapper for the FancyVideo / FancyImage Component. It can be used to add a text overlay to the video.
- It recives textChildren as normal React nodes
- As children it recives the FancyVideo / FancyImage Component / or what do you fucking want', + }, + }, + }, + + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + + + ), + args: { + position: 'center', + textChildren: ( + <> + Hello World + + ), + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.tsx b/src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.tsx new file mode 100644 index 000000000..7835e87ac --- /dev/null +++ b/src/components/atoms/ImageVideoOverlay/ImageVideoOverlay.tsx @@ -0,0 +1,132 @@ +import React from 'react'; + +import { styled, css } from 'styled-components'; +import { textShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TTheme } from '@/interface/TTheme'; + +// Define the gradient options for the overlay +const gradientOptions = { + start: 'rgba(0,0,0,0.4) 0%', + end: 'rgba(0,0,0,0) 40%', +}; + +// Define a styled wrapper component using styled-components +const Wrapper = styled.div` + position: relative; + overflow: hidden; + line-height: 0; + pointer-events: none; +`; + +// Define a styled overlay component using styled-components +const Overlay = styled.div<{ $position: string }>` + position: absolute; + width: 100%; + height: 100%; + pointer-events: none; + + ${({ $position }) => { + switch ($position) { + case 'top-left': + return css` + background: linear-gradient(135deg, ${gradientOptions.start}, ${gradientOptions.end}); + `; + case 'top-right': + return css` + background: linear-gradient(225deg, ${gradientOptions.start}, ${gradientOptions.end}); + `; + case 'center': + return css` + background: radial-gradient(ellipse 75% 50% at center, ${gradientOptions.start}, ${gradientOptions.end}); + `; + case 'bottom-left': + return css` + background: linear-gradient(45deg, ${gradientOptions.start}, ${gradientOptions.end}); + `; + case 'bottom-right': + return css` + background: linear-gradient(325deg, ${gradientOptions.start}, ${gradientOptions.end}); + `; + default: + return ''; + } + }}; +`; + +// Define a styled text wrapper component using styled-components +const TextWrapper = styled.div<{ $position: string; theme: TTheme }>` + position: absolute; + z-index: 1; + padding: ${({ theme }) => theme.spacing.md}; + pointer-events: none; + ${textShadow.lg} + + ${({ $position }) => { + switch ($position) { + case 'top-left': + return css` + text-align: left; + top: 0; + left: 0; + `; + case 'top-right': + return css` + text-align: right; + top: 0; + right: 0; + `; + case 'center': + return css` + text-align: center; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + `; + case 'bottom-left': + return css` + text-align: left; + bottom: 0; + left: 0; + `; + case 'bottom-right': + return css` + text-align: right; + bottom: 0; + right: 0; + `; + default: + return ''; + } + }}; +`; + +// Define the possible positions for the overlay +export type TPositions = 'top-left' | 'top-right' | 'center' | 'bottom-left' | 'bottom-right'; + +// Define the props for the ImageVideoOverlay component +interface IImageVideoOverlay { + children?: React.ReactNode; + textChildren?: React.ReactNode; + position?: TPositions; +} + +// --------------------------------------------------------------------------- // +// ----------- The Definition for the ImageVideoOverlay Component ------------ // +// --------------------------------------------------------------------------- // +export default function ImageVideoOverlay(props: IImageVideoOverlay) { + const { children, position, textChildren } = { ...defaultProps, ...props }; + + return ( + + + {textChildren} + {/* The children is normaly only a Video or a image, but do with it what d o you fuucking want */} + {children} + + ); +} + +// Define the default props for the ImageVideoOverlay component +const defaultProps = { + position: 'top-right', +}; diff --git a/src/components/atoms/ImageVideoOverlay/index.ts b/src/components/atoms/ImageVideoOverlay/index.ts new file mode 100644 index 000000000..2697e2e51 --- /dev/null +++ b/src/components/atoms/ImageVideoOverlay/index.ts @@ -0,0 +1 @@ +export { default as ImageVideoOverlay } from './ImageVideoOverlay'; diff --git a/src/components/atoms/InputLabel/InputLabel.stories.tsx b/src/components/atoms/InputLabel/InputLabel.stories.tsx new file mode 100644 index 000000000..896ac2e67 --- /dev/null +++ b/src/components/atoms/InputLabel/InputLabel.stories.tsx @@ -0,0 +1,43 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; +import themeStore from '../../../design/theme/themeStore/themeStore'; + +// Import the component to be tested +import InputLabel from './InputLabel'; + +// Define metadata for the story +const meta = { + component: InputLabel, + parameters: { + docs: { + description: { + component: + 'Is simply a label for the input. It is used to describe the input. The Parents are the Aligned and AnimatedInputLabel.
Its only used for grounding the design', + }, + }, + }, + + // Define arguments for the story + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: () => Label, + args: { + theme: themeStore.getState().theme, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/InputLabel/InputLabel.tsx b/src/components/atoms/InputLabel/InputLabel.tsx new file mode 100644 index 000000000..1f597a6a0 --- /dev/null +++ b/src/components/atoms/InputLabel/InputLabel.tsx @@ -0,0 +1,21 @@ +import { styled } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// --------------------------------------------------------------------------- // +// --------------- The main label design for the input elements -------------- // +// --------------------------------------------------------------------------- // +const InputLabel = styled.label<{ theme: TTheme }>` + width: 100%; + color: ${({ theme }) => theme.colors.secondary[1]}; + font-weight: bold; + pointer-events: none; + user-select: none; + transition: 0.3s; + transition-timing-function: cubic-bezier(0.46, 0.03, 0.52, 0.96); + letter-spacing: 0.8px; +`; + +InputLabel.displayName = 'InputLabel'; + +export default InputLabel; diff --git a/src/components/atoms/InputLabel/index.ts b/src/components/atoms/InputLabel/index.ts new file mode 100644 index 000000000..7876ceaac --- /dev/null +++ b/src/components/atoms/InputLabel/index.ts @@ -0,0 +1 @@ +export { default as InputLabel } from './InputLabel'; diff --git a/src/components/atoms/InputUnderline/InputUnderline.stories.tsx b/src/components/atoms/InputUnderline/InputUnderline.stories.tsx new file mode 100644 index 000000000..95caaade5 --- /dev/null +++ b/src/components/atoms/InputUnderline/InputUnderline.stories.tsx @@ -0,0 +1,77 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import InputUnderline from './InputUnderline'; + +// Define metadata for the story +const meta = { + component: InputUnderline, + parameters: { + docs: { + description: { + component: + 'The InputUnderline is a simple underline for the input.
- It has a colorState prop to define the color of the underline
- It has a isActive prop to define if the underline is active
- It has a themeType prop to define the theme of the underline
- It has a layer prop to define the layer of the underline', + }, + }, + }, + + // Define arguments for the story + argTypes: { + colorState: { + description: 'Define the color state of the underline', + control: { + type: 'select', + }, + options: ['error', 'active', 'default'], + }, + isActive: { + description: 'Define if the underline is active', + control: { + type: 'boolean', + }, + }, + themeType: { + description: 'Define the theme of the underline', + control: { + type: 'select', + }, + options: ['primary', 'secondary', 'accent'], + }, + layer: { + description: 'Define the layer of the underline', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + colorState: 'active', + isActive: false, + themeType: 'secondary', + layer: 0, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/InputUnderline/InputUnderline.tsx b/src/components/atoms/InputUnderline/InputUnderline.tsx new file mode 100644 index 000000000..ef27d53d9 --- /dev/null +++ b/src/components/atoms/InputUnderline/InputUnderline.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { styled, css } from 'styled-components'; + +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +// Define the styled component for the underline +type IStyledUnderline = IStyledPrefixAndPicker; +const UnderLine = styled.i` + position: absolute; + left: 0; + bottom: 0; + height: 2px; + border-radius: 5px; + background: ${({ theme, $themeType = 'secondary', $layer }) => getBackgroundColor({ theme, $themeType, $layer })}; + overflow: hidden; + width: 100%; + + // Define the styles for the gradient overlay + &::before { + content: ''; + width: 100%; + border-radius: 5px; + position: absolute; + left: 0; + bottom: 0; + opacity: ${({ $isActive }) => ($isActive ? 1 : 0)}; + height: 100%; + background: ${({ $colorState, theme }) => { + if ($colorState === 'error') return css`linear-gradient(90deg, ${theme.colors.error[1]}, ${theme.colors.error[0]})`; + if ($colorState === 'active') return css`linear-gradient(90deg, ${theme.colors.accent[1]}, ${theme.colors.accent[0]})`; + if ($colorState === 'default') return css`linear-gradient(90deg, ${theme.colors.secondary[0]}, ${theme.colors.secondary[4]})`; + }}; + + // Define the transition styles for the gradient overlay + transition: 0.25s; + transition-timing-function: cubic-bezier(0.46, 0.03, 0.52, 0.96); + } +`; + +// Define the props for the FancyInputUnderline component +interface IFancyUnderline { + colorState?: 'error' | 'active' | 'default'; + isActive?: boolean; + autoWidth?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; +} +// --------------------------------------------------------------------------- // +// --------- The underline for the input components with state style --------- // +// --------------------------------------------------------------------------- // +export default function FancyInputUnderline(props: IFancyUnderline) { + const { colorState = 'default', isActive, autoWidth, layer = 4, themeType } = props; + + // Render the FancyInputUnderline component with the appropriate props + return ; +} diff --git a/src/components/atoms/InputUnderline/index.ts b/src/components/atoms/InputUnderline/index.ts new file mode 100644 index 000000000..bc199604b --- /dev/null +++ b/src/components/atoms/InputUnderline/index.ts @@ -0,0 +1 @@ +export { default as InputUnderline } from './InputUnderline'; diff --git a/src/components/atoms/ListDivider/ListDivider.model.ts b/src/components/atoms/ListDivider/ListDivider.model.ts new file mode 100644 index 000000000..b384e3edc --- /dev/null +++ b/src/components/atoms/ListDivider/ListDivider.model.ts @@ -0,0 +1,22 @@ +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +export type TTextAlign = 'left' | 'center' | 'right'; + +type WithLabel = { + label: string; + textAlignment?: TTextAlign; +}; + +type WithoutLabel = { + label?: never; + textAlignment?: TTextAlign; +}; + +export type IListDivider = { + themeType?: TThemeTypes; + layer?: TLayer; + noLine?: boolean; + bold?: boolean; + icon?: React.ReactNode; +} & (WithLabel | WithoutLabel); diff --git a/src/components/atoms/ListDivider/ListDivider.stories.tsx b/src/components/atoms/ListDivider/ListDivider.stories.tsx new file mode 100644 index 000000000..cee02d42f --- /dev/null +++ b/src/components/atoms/ListDivider/ListDivider.stories.tsx @@ -0,0 +1,60 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import ListDivider from './ListDivider'; + +// Define metadata for the story +const meta = { + component: ListDivider, + parameters: { + docs: { + description: { + component: 'Dumb-Component: Its a Divider that displays only a line or line with informations', + }, + }, + }, + // Define arguments for the story + argTypes: { + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the button hover effect', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '3', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: {}, +}; +export const WithText: Story = { + render: (args) => , + args: { + label: 'Some Text', + }, +}; diff --git a/src/components/atoms/ListDivider/ListDivider.style.tsx b/src/components/atoms/ListDivider/ListDivider.style.tsx new file mode 100644 index 000000000..b1fff9959 --- /dev/null +++ b/src/components/atoms/ListDivider/ListDivider.style.tsx @@ -0,0 +1,67 @@ +import { css, styled } from 'styled-components'; + +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { IListDivider } from './ListDivider.model'; +import { TTheme } from '@/interface/TTheme'; + +// only a sepeation line +type TStyledPrefixAndOmitter = IStyledPrefixAndPicker & { theme: TTheme }; +export const StyledI = styled.div` + height: 1px; + background-color: ${({ theme, $themeType, $layer }) => + getBackgroundColor({ theme, $themeType: $themeType ?? 'secondary', $layer: $layer ?? 2 })}; + margin: 8px 0; +`; + +// the wrapper for the text and the line +type TStyledTextDiv = IStyledPrefixAndPicker & { theme: TTheme }; +export const StyledTextDiv = styled.div` + display: flex; + color: ${({ $themeType, $layer, theme }) => getBackgroundColor({ $themeType: $themeType ?? 'secondary', $layer, theme })}; + + justify-content: ${({ $textAlignment }) => + $textAlignment === 'left' ? 'flex-start' : $textAlignment === 'right' ? 'flex-end' : 'center'}; + align-items: center; + margin: 2px 0; + + > span { + padding: ${({ $textAlignment }) => { + switch ($textAlignment) { + case 'left': + return '0 8px 0 20px'; + case 'right': + return '0 20px 0 8px'; + case 'center': + return '0 8px 0 8px'; + } + }}; + flex-shrink: 0; + } + + /* This aligns the left line */ + ${({ $textAlignment, $themeType, $layer, theme, $noLine }) => + !$noLine && + ($textAlignment === 'right' || $textAlignment === 'center') && + css` + &:before { + content: ' '; + width: 100%; + height: 1px; + background-color: ${getBackgroundColor({ $themeType: $themeType ?? 'secondary', $layer, theme })}; + } + `} + + /* This aligns the right line */ + ${({ $textAlignment, $themeType, $layer, theme, $noLine }) => + !$noLine && + ($textAlignment === 'left' || $textAlignment === 'center') && + css` + &:after { + content: ' '; + width: 100%; + height: 1px; + background-color: ${getBackgroundColor({ $themeType: $themeType ?? 'secondary', $layer, theme })}; + } + `} +`; diff --git a/src/components/atoms/ListDivider/ListDivider.tsx b/src/components/atoms/ListDivider/ListDivider.tsx new file mode 100644 index 000000000..ae9f0cac5 --- /dev/null +++ b/src/components/atoms/ListDivider/ListDivider.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import { StyledI, StyledTextDiv } from './ListDivider.style'; +import FancyContent from '../../molecules/FancyContent/FancyContent'; +import { IListDivider } from './ListDivider.model'; + +// --------------------------------------------------------------------------- // +// ------- A Divider that displays only a line or line with informations ----- // +// --------------------------------------------------------------------------- // +export default function ListDivider(props: IListDivider) { + const { label, textAlignment = 'center', themeType, layer, bold, icon, noLine } = props; + + return ( + <> + {/* Render a Seperator(Divider) with a optional label and icon */} + {label ? ( + + + {label && ( + + {label} + + )} + {icon && {icon}} + + + ) : ( + // Render a Seperator(Divider) only a line + + )} + + ); +} diff --git a/src/components/atoms/ListDivider/index.ts b/src/components/atoms/ListDivider/index.ts new file mode 100644 index 000000000..a871920d7 --- /dev/null +++ b/src/components/atoms/ListDivider/index.ts @@ -0,0 +1 @@ +export { default as ListDivider } from './ListDivider'; diff --git a/src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.stories.tsx b/src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.stories.tsx new file mode 100644 index 000000000..f575f065c --- /dev/null +++ b/src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.stories.tsx @@ -0,0 +1,89 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import LoadingSVGArrows from './LoadingSVGArrows'; + +// Define metadata for the story +const meta = { + component: LoadingSVGArrows, + parameters: { + docs: { + description: { + component: + 'The LoadingSVGArrows is a simple loading animation with SVG arrows.
- It has a isLoading prop to define if the animation is active', + }, + }, + }, + + // Define arguments for the story + argTypes: { + isActive: { + control: { + type: 'boolean', + }, + }, + isLoading: { + control: { + type: 'boolean', + }, + }, + themeType: { + control: { + type: 'select', + }, + options: ['primary', 'secondary', 'accent'], + }, + layer: { + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + size: { + control: { + type: 'select', + }, + }, + errorMessage: { + control: { + type: 'text', + }, + }, + isPassive: { + description: + 'Passive mode is when when the color should adapt from the parent component, otherwise it can used interative with (active, error, colors) ', + control: { + type: 'boolean', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + isLoading: true, + isPassive: false, + themeType: 'accent', + layer: 1, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.tsx b/src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.tsx new file mode 100644 index 000000000..2c8f59917 --- /dev/null +++ b/src/components/atoms/LoadingSVGArrows/LoadingSVGArrows.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import styled, { keyframes } from 'styled-components'; +import SVGLoadingArrows from '../../icons/SVGLoadingArrows/SVGLoadingArrows'; +import { ISVGAtomProps } from '../FancySVGAtom/FancySVGAtom.model'; + +const loadingAnimation = keyframes` + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +`; + +const AnimatedSVGArrwos = styled.span` + animation: ${loadingAnimation} 2s infinite ease-in-out; + line-height: 0; +`; + +interface ILoadingSVGArrowsProps { + isLoading?: boolean; +} + +// --------------------------------------------------------------------------- // +// A loading animation with two arrows thats shown when something is loading // +// --------------------------------------------------------------------------- // +export default function LoadingSVGArrows(props: ILoadingSVGArrowsProps & ISVGAtomProps) { + const { isLoading } = props; + + return isLoading ? ( + + + + ) : null; +} diff --git a/src/components/atoms/MenuItem/MenuItem.stories.tsx b/src/components/atoms/MenuItem/MenuItem.stories.tsx new file mode 100644 index 000000000..c97c6fe1a --- /dev/null +++ b/src/components/atoms/MenuItem/MenuItem.stories.tsx @@ -0,0 +1,60 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import MenueItem from './MenuItem'; + +// Define metadata for the story +const meta = { + component: MenueItem, + parameters: { + docs: { + description: { + component: + 'Dumb-Component: The MenueItem is only a Box with some styles you can use it to build your own MenueList or something else', + }, + }, + }, + // Define arguments for the story + argTypes: { + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the button hover effect', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '3', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + Some Content + {args.children} + + ), + args: {}, +}; diff --git a/src/components/atoms/MenuItem/MenuItem.style.tsx b/src/components/atoms/MenuItem/MenuItem.style.tsx new file mode 100644 index 000000000..fefaa8cc3 --- /dev/null +++ b/src/components/atoms/MenuItem/MenuItem.style.tsx @@ -0,0 +1,29 @@ +import styled from 'styled-components'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; +import { getBackgroundColor, getTextColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; + +type StyledMenuProps = { + $themeType?: TThemeTypes; + $layer?: TLayer; + theme: TTheme; + as?: 'a' | 'button'; +}; + +export const StyledMenuItem = styled.button` + display: flex; + box-sizing: border-box; + padding: 10px 20px; + cursor: pointer; + background-color: transparent; + border: none; + text-decoration: none; + color: ${({ theme, $themeType }) => getTextColor({ theme, $themeType: $themeType ?? 'secondary', $textLayer: 1 })}; + + &:hover { + background-color: ${({ theme, $themeType, $layer }) => + getBackgroundColor({ theme, $themeType: $themeType ?? 'primary', $layer: $layer ?? 3 })}; + } +`; diff --git a/src/components/atoms/MenuItem/MenuItem.tsx b/src/components/atoms/MenuItem/MenuItem.tsx new file mode 100644 index 000000000..b23a31836 --- /dev/null +++ b/src/components/atoms/MenuItem/MenuItem.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { StyledMenuItem } from './MenuItem.style'; + +type HTMLButtonProps = React.ButtonHTMLAttributes & { as?: 'button' }; +type HTMLAnchorProps = React.AnchorHTMLAttributes & { as: 'a' }; + +type MenueItemProps = { + children?: React.ReactNode; + themeType?: TThemeTypes; + layer?: TLayer; +} & (HTMLButtonProps | HTMLAnchorProps); +// --------------------------------------------------------------------------- // +//A Empty Menue Item that only handles the style you can put everythin as childs// +// --------------------------------------------------------------------------- // +export default function MenuItem(props: MenueItemProps) { + const { children, as, themeType, layer, ...HTMLProps } = props; + + return ( + + {children} + + ); +} diff --git a/src/components/atoms/MenuItem/index.ts b/src/components/atoms/MenuItem/index.ts new file mode 100644 index 000000000..e24b0fb81 --- /dev/null +++ b/src/components/atoms/MenuItem/index.ts @@ -0,0 +1 @@ +export { default as MenuItem } from './MenuItem'; diff --git a/src/components/atoms/PageNumberList/PageNumberList.stories.tsx b/src/components/atoms/PageNumberList/PageNumberList.stories.tsx new file mode 100644 index 000000000..3297c0695 --- /dev/null +++ b/src/components/atoms/PageNumberList/PageNumberList.stories.tsx @@ -0,0 +1,63 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import PageNumberList from './PageNumberList'; + +// Define metadata for the story +const meta = { + component: PageNumberList, + parameters: { + docs: { + description: { + component: + 'The PageNumberList is a simple component to display the page numbers.
- It has a totalPages prop to define the total number of pages
- It has a currentPage prop to define the current page
- It has a pageLimits prop to define the page limits
- It has a onClick prop to define the onClick function', + }, + }, + }, + + // Define arguments for the story + argTypes: { + pageLimits: { + control: { + type: 'number', + }, + }, + totalPages: { + control: { + type: 'number', + }, + }, + currentPage: { + control: { + type: 'number', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + totalPages: 10, + currentPage: 1, + pageLimits: 3, + onClick: (page: number) => console.log(page), + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/PageNumberList/PageNumberList.style.tsx b/src/components/atoms/PageNumberList/PageNumberList.style.tsx new file mode 100644 index 000000000..33e91b0bd --- /dev/null +++ b/src/components/atoms/PageNumberList/PageNumberList.style.tsx @@ -0,0 +1,21 @@ +import { styled } from 'styled-components'; +import { TTheme } from '@/interface/TTheme'; +import simpleColorTransition from '../../../design/designFunctions/simpleColorTransition/simpleTransition'; + +// Define a styled component for the button wrapper +export const ButtonWrapper = styled.div<{ $isActive: boolean; theme: TTheme }>` + button { + color: ${({ $isActive, theme }) => $isActive && theme.colors.accent[0]}; + ${simpleColorTransition} + padding: ${({ theme }) => theme.spacing.sm}; + + &:hover { + color: ${({ $isActive, theme }) => $isActive && theme.colors.accent[0]}; + } + } +`; + +export const Wrapper = styled.div<{ theme: TTheme }>` + display: flex; + gap: ${({ theme }) => theme.spacing.md}; +`; diff --git a/src/components/atoms/PageNumberList/PageNumberList.tsx b/src/components/atoms/PageNumberList/PageNumberList.tsx new file mode 100644 index 000000000..d1d1a44a0 --- /dev/null +++ b/src/components/atoms/PageNumberList/PageNumberList.tsx @@ -0,0 +1,77 @@ +import { FancyButton } from '../../organisms/FancyButton'; +import { ButtonWrapper, Wrapper } from './PageNumberList.style'; + +// Define a function to generate an array of page numbers to display +const generateNumbers = (totalPages: number, currentPage: number, pageLimit = 3) => { + const numbers: (number | string)[] = []; + + let startPage = currentPage - Math.floor(pageLimit / 2); + let endPage = currentPage + Math.floor(pageLimit / 2); + + if (pageLimit % 2 === 0) { + startPage += 1; // If pageLimit is even, favor showing one less page before the current page + } + + // Adjust if the range goes outside the total pages + if (startPage < 1) { + endPage += Math.abs(startPage) + 1; + startPage = 1; + } + + if (endPage > totalPages) { + startPage -= endPage - totalPages; + endPage = totalPages; + } + + startPage = Math.max(startPage, 1); + + if (startPage > 1) { + numbers.push('...'); + } + + for (let i = startPage; i <= endPage; i++) { + numbers.push(i); + } + + if (endPage < totalPages) { + numbers.push('...'); + } + + return numbers; +}; + +// Define the props for the PageNumberList component +interface IPageNumberList { + totalPages: number; + currentPage: number; + pageLimits?: number; + onClick?: (page: number) => void; +} +// --------------------------------------------------------------------------- // +// ------- This compoennt generate the Page Numbers and the Spacings --------- // +// --------------------------------------------------------------------------- // +export default function PageNumberList(props: IPageNumberList) { + const { totalPages, currentPage, onClick, pageLimits } = props; + + // Generate an array of page numbers to display + const NumberArray = generateNumbers(totalPages, currentPage, pageLimits); + + // Render the PageNumberList component with the appropriate props + return ( + + {/* Map over the total number of pages and render a FancyButton for each page */} + {NumberArray.map((item, index) => ( + + onClick && onClick(index + 1)} + size="md" + themeType="transparent" + wide={false} + /> + + ))} + + ); +} diff --git a/src/components/atoms/PasswordEye/PasswordEye.stories.tsx b/src/components/atoms/PasswordEye/PasswordEye.stories.tsx new file mode 100644 index 000000000..439f37c76 --- /dev/null +++ b/src/components/atoms/PasswordEye/PasswordEye.stories.tsx @@ -0,0 +1,51 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import PasswordEye from './PasswordEye'; + +// Define metadata for the story +const meta = { + component: PasswordEye, + parameters: { + docs: { + description: { + component: + 'The PasswordEye is a simple component to toggle the password visibility.
- It has a isShow prop to define if the password is shown
- It has a onClick prop to define the onClick function', + }, + }, + }, + // Define arguments for the story + argTypes: { + isShow: { + description: 'Define if the password is shown', + control: { + type: 'boolean', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + isShow: false, + onClick: () => console.log('click'), + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/PasswordEye/PasswordEye.tsx b/src/components/atoms/PasswordEye/PasswordEye.tsx new file mode 100644 index 000000000..ee82aa198 --- /dev/null +++ b/src/components/atoms/PasswordEye/PasswordEye.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import SVGEyeCrossed from '../../icons/SVGEyeCrossed/SVGEyeCrossed'; +import SVGEyeOpen from '../../icons/SVGEyeOpen/SVGEyeOpen'; +import FancySVGAtom from '../FancySVGAtom/FancySVGAtom'; + +interface IPasswordEye { + isShow?: boolean; + onClick?: () => void; +} +export default function PasswordEye({ isShow, onClick }: IPasswordEye) { + const clickHandler = () => { + onClick && onClick(); + }; + + return ( + + {isShow ? ( + // the eye icon for the password type toggle + + {SVGEyeOpen} + + ) : ( + // the crossed out eye icon for the password type toggle + + {SVGEyeCrossed} + + )} + + ); +} diff --git a/src/components/atoms/PasswordEye/index.ts b/src/components/atoms/PasswordEye/index.ts new file mode 100644 index 000000000..8c603ef59 --- /dev/null +++ b/src/components/atoms/PasswordEye/index.ts @@ -0,0 +1 @@ +export { default as PasswordEye } from './PasswordEye'; diff --git a/src/components/atoms/ProgressBar/ProgressBar.stories.tsx b/src/components/atoms/ProgressBar/ProgressBar.stories.tsx new file mode 100644 index 000000000..82d2e054e --- /dev/null +++ b/src/components/atoms/ProgressBar/ProgressBar.stories.tsx @@ -0,0 +1,75 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import ProgressBar from './ProgressBar'; + +// Define metadata for the story +const meta = { + component: ProgressBar, + parameters: { + docs: { + description: { + component: + 'The ProgressBar is a simple component to display the progress.
- It has a maxValue prop to define the max value of the progress bar
- It has a progress prop to define the progress of the progress bar
- It has a themeType prop to define the theme type of the progress bar
- It has a layer prop to define the layer of the progress bar', + }, + }, + }, + // Define arguments for the story + argTypes: { + maxValue: { + description: 'Define the max value of the progress bar', + control: { + type: 'number', + }, + }, + progress: { + description: 'Define the progress of the progress bar', + control: { + type: 'number', + }, + }, + themeType: { + description: 'Define the theme type of the progress bar', + control: { + type: 'select', + }, + options: ['primary', 'secondary', 'accent'], + }, + layer: { + description: 'Define the layer of the progress bar', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + maxValue: 100, + progress: 50, + themeType: 'secondary', + layer: 0, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/ProgressBar/ProgressBar.tsx b/src/components/atoms/ProgressBar/ProgressBar.tsx new file mode 100644 index 000000000..d589e0b9f --- /dev/null +++ b/src/components/atoms/ProgressBar/ProgressBar.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +// Define the styled component for the progress bar container +const ProgressBarContainer = styled.div<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + width: 100%; + height: 4px; + background-color: ${({ theme, $themeType = 'secondary', $layer = 4 }) => getBackgroundColor({ theme, $themeType, $layer })}; + border-radius: 10px; + overflow: hidden; + position: relative; +`; + +// Define the interface for the progress bar fill props +interface IProgressBarFillProps { + width: number; + theme: TTheme; +} +// Define the styled component for the progress bar fill +const ProgressBarFill = styled.div` + height: 100%; + width: ${({ width }) => width}%; + background-color: ${({ theme }) => theme.colors.accent[0]}; + border-radius: ${({ theme }) => theme.borderRadius.complete} 0 0 ${({ theme }) => theme.borderRadius.complete}; + transition: width 0.2s ease-out; +`; + +// Define the interface for the progress bar props +export interface IProgressBar { + progress?: number; + maxValue?: number; + id?: string; + themeType?: TThemeTypes; + layer?: TLayer; +} +// Define the ProgressBar component +export default function ProgressBar({ progress = 0, maxValue = 100, id, themeType, layer }: IProgressBar) { + // Calculate the width of the progress bar fill + const width = (progress / maxValue) * 100; + + // Render the ProgressBar component with the appropriate props + return ( + + + + ); +} diff --git a/src/components/atoms/ProgressBar/index.ts b/src/components/atoms/ProgressBar/index.ts new file mode 100644 index 000000000..e072ff53d --- /dev/null +++ b/src/components/atoms/ProgressBar/index.ts @@ -0,0 +1 @@ +export { default as ProgressBar } from './ProgressBar'; diff --git a/src/components/atoms/RawCheckbox/RawCheckbox.model.ts b/src/components/atoms/RawCheckbox/RawCheckbox.model.ts new file mode 100644 index 000000000..c81b5a83c --- /dev/null +++ b/src/components/atoms/RawCheckbox/RawCheckbox.model.ts @@ -0,0 +1,7 @@ +import { HTMLAttributes } from 'react'; + +// the props that will be passed to the input element +type NativeAttrs = Omit, 'type'>; + +// MAIN INCOMMING PROPS PASSED TO THE COMPONENT +export type IRawCheckboxProps = NativeAttrs; diff --git a/src/components/atoms/RawCheckbox/RawCheckbox.stories.tsx b/src/components/atoms/RawCheckbox/RawCheckbox.stories.tsx new file mode 100644 index 000000000..503c039d0 --- /dev/null +++ b/src/components/atoms/RawCheckbox/RawCheckbox.stories.tsx @@ -0,0 +1,44 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import RawCheckbox from './RawCheckbox'; + +// Define metadata for the story +const meta = { + component: RawCheckbox, + parameters: { + docs: { + description: { + component: + 'The RawCheckbox is a simple component to display the checkbox.
- It has a defaultChecked prop to define the default checked of the checkbox
- It has a onClick prop to define the onClick function', + }, + }, + }, + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + defaultChecked: true, + onClick: () => console.log('click'), + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/RawCheckbox/RawCheckbox.style.tsx b/src/components/atoms/RawCheckbox/RawCheckbox.style.tsx new file mode 100644 index 000000000..e21820986 --- /dev/null +++ b/src/components/atoms/RawCheckbox/RawCheckbox.style.tsx @@ -0,0 +1,71 @@ +import styled, { css } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// a consistant style for the checkbox and the fake checkbox +const checkboxStyles = css` + width: 20px; + height: 20px; + border-radius: 4px; +`; + +// the real chekbox but its hidden and the fake one is shown +export const HidenCheckBox = styled.input` + margin: 0; + position: absolute; + cursor: pointer; + opacity: 1; + background-color: transparent; + z-index: 0; + appearance: none; + outline: none; + ${checkboxStyles} + margin: 1px; + + &:checked { + background-color: transparent; + } + + &:focus-visible { + box-shadow: 0 0 0px 1px white; + } +`; + +// the fake checkbox that is shown for better styling +export const FakeCheckbox = styled.div<{ $checked: boolean; theme: TTheme }>` + position: absolute; + top: 0; + left: 0; + background-color: ${({ theme }) => theme.colors.primary[3]}; + z-index: 1; + ${checkboxStyles} + box-sizing: border-box; + width: 20px; + height: 20px; + + &:focus-visible { + outline: none; /* Remove default outline */ + } + /* the checkmark svg */ + svg { + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + transform: translate(-50%, -50%); + fill: ${({ theme }) => theme.colors.accent[0]}; + display: ${({ $checked }) => ($checked ? 'block' : 'none')}; + } +`; + +// the container for the checkbox and the fake checkbox +export const InputContainer = styled.div` + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; +`; diff --git a/src/components/atoms/RawCheckbox/RawCheckbox.tsx b/src/components/atoms/RawCheckbox/RawCheckbox.tsx new file mode 100644 index 000000000..c48dd0ad0 --- /dev/null +++ b/src/components/atoms/RawCheckbox/RawCheckbox.tsx @@ -0,0 +1,40 @@ +import React, { useState, useEffect } from 'react'; + +import { FakeCheckbox, HidenCheckBox, InputContainer } from './RawCheckbox.style'; +import { IRawCheckboxProps } from './RawCheckbox.model'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// --------------------------------------------------------------------------- // +// -------------------- A simple Checkbox with a Label ----------------------- // +// --------------------------------------------------------------------------- // +export default function RawCheckbox(props: IRawCheckboxProps) { + const { onChange, defaultChecked, id, ...rest } = props; + const [checked, setChecked] = useState(false); + + // the onClick for clicking the fake checkbox to set the state + const handleClick = () => { + setChecked(!checked); + }; + + // the onChange handler to set the state and call the incomming onChange handler + const handleChange = (e: React.ChangeEvent) => { + setChecked(e.target.checked); + if (onChange) onChange(e); + }; + + // Set default incomming checked value + useEffect(() => { + if (defaultChecked) setChecked(defaultChecked); + }, [defaultChecked]); + + return ( + + {/* The Fakecheckbox for the styling */} + + + + {/* The real checkbox but this hidden */} + + + ); +} diff --git a/src/components/atoms/RawCheckbox/index.ts b/src/components/atoms/RawCheckbox/index.ts new file mode 100644 index 000000000..c315b01ae --- /dev/null +++ b/src/components/atoms/RawCheckbox/index.ts @@ -0,0 +1 @@ +export { default as RawCheckbox } from './RawCheckbox'; diff --git a/src/components/atoms/RawInput/RawInput.stories.tsx b/src/components/atoms/RawInput/RawInput.stories.tsx new file mode 100644 index 000000000..a79f61ef9 --- /dev/null +++ b/src/components/atoms/RawInput/RawInput.stories.tsx @@ -0,0 +1,60 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import RawInput from './RawInput'; + +// Define metadata for the story +const meta = { + component: RawInput, + parameters: { + docs: { + description: { + component: '', + }, + }, + }, + // Define arguments for the story + argTypes: { + placeholder: { + control: { + type: 'text', + }, + }, + value: { + control: { + type: 'text', + }, + }, + onChange: { + control: { + type: 'function', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + placeholder: 'Placeholder', + value: '', + onChange: (event: React.ChangeEvent) => console.log(event.target.value), + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/RawInput/RawInput.tsx b/src/components/atoms/RawInput/RawInput.tsx new file mode 100644 index 000000000..dc299d6a1 --- /dev/null +++ b/src/components/atoms/RawInput/RawInput.tsx @@ -0,0 +1,28 @@ +import { styled } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import { TTheme } from '@/interface/TTheme'; + +export type TRawInputAlign = 'left' | 'center'; + +export interface IRawInput { + $align?: TRawInputAlign; + theme?: TTheme; +} +const RawInput = styled.input` + font-weight: 500; + font-size: 16px; + box-sizing: border-box; + width: 100%; + appearance: none; + background-color: transparent; + color: ${({ theme }) => theme.colors.secondary[0]}; + text-align: ${({ $align }) => ($align !== 'center' ? 'left' : 'center')}; + border: none; + outline: none; + box-shadow: none; + font-size: ${fontSize.md}; + padding: 0; +`; + +export default RawInput; diff --git a/src/components/atoms/RawInput/index.ts b/src/components/atoms/RawInput/index.ts new file mode 100644 index 000000000..f99545c82 --- /dev/null +++ b/src/components/atoms/RawInput/index.ts @@ -0,0 +1 @@ +export { default as RawInput } from './RawInput'; diff --git a/src/components/atoms/RawLI/RawLI.tsx b/src/components/atoms/RawLI/RawLI.tsx new file mode 100644 index 000000000..235488438 --- /dev/null +++ b/src/components/atoms/RawLI/RawLI.tsx @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +const RawLI = styled.li` + list-style: none; +`; + +export default RawLI; diff --git a/src/components/atoms/RawLI/index.ts b/src/components/atoms/RawLI/index.ts new file mode 100644 index 000000000..4abcdaa2b --- /dev/null +++ b/src/components/atoms/RawLI/index.ts @@ -0,0 +1 @@ +export { default as RawLI } from './RawLI'; diff --git a/src/components/atoms/RawNav/RawNav.tsx b/src/components/atoms/RawNav/RawNav.tsx new file mode 100644 index 000000000..a9e7b015a --- /dev/null +++ b/src/components/atoms/RawNav/RawNav.tsx @@ -0,0 +1,7 @@ +import { CSSProp, styled } from 'styled-components'; + +const RawNav = styled.nav<{ $externalStyle?: CSSProp }>` + width: 100%; + ${({ $externalStyle }) => $externalStyle}; +`; +export default RawNav; diff --git a/src/components/atoms/RawRadio/RawRadio.stories.tsx b/src/components/atoms/RawRadio/RawRadio.stories.tsx new file mode 100644 index 000000000..7e55de15d --- /dev/null +++ b/src/components/atoms/RawRadio/RawRadio.stories.tsx @@ -0,0 +1,58 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import RawRadio from './RawRadio'; + +// Define metadata for the story +const meta = { + component: RawRadio, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The RawRadio Renders only a Styled Radio Button.', + }, + }, + }, + // Define arguments for the story + argTypes: { + checked: { + description: 'The checked state of the radio button', + control: { + type: 'boolean', + }, + }, + tabIndex: { + description: 'The tabIndex of the radio button', + control: { + type: 'number', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + checked: false, + tabIndex: 0, + name: 'radio', + }, + + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/RawRadio/RawRadio.style.tsx b/src/components/atoms/RawRadio/RawRadio.style.tsx new file mode 100644 index 000000000..2ba17cdb5 --- /dev/null +++ b/src/components/atoms/RawRadio/RawRadio.style.tsx @@ -0,0 +1,55 @@ +import { styled } from 'styled-components'; +import { TTheme } from '../../../interface/TTheme'; + +export const RadioWrapper = styled.div` + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; +`; + +export const HiddenRadio = styled.input.attrs({ type: 'radio' })` + margin: 0; + position: absolute; + cursor: pointer; + opacity: 1; + background-color: transparent; + z-index: 0; + appearance: none; + outline: none; + width: 16px; + height: 16px; +`; + +export const StyledRadio = styled.span<{ checked: boolean; theme: TTheme }>` + width: 16px; + height: 16px; + border: 2px solid; + border-color: ${(props) => (props.checked ? props.theme.colors.accent[2] : props.theme.colors.primary[3])}; + background: ${(props) => (props.checked ? 'transparent' : props.theme.colors.primary[3])}; + border-radius: 50%; + display: inline-block; + position: relative; + flex-shrink: 0; + transition: + background 0.2s ease, + border-color 0.2s ease; + + &:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 10px; + height: 10px; + border-radius: 50%; + background: ${(props) => (props.checked ? props.theme.colors.accent[1] : 'transparent')}; + transition: + background 0.2s ease, + border-color 0.2s ease; + } +`; diff --git a/src/components/atoms/RawRadio/RawRadio.tsx b/src/components/atoms/RawRadio/RawRadio.tsx new file mode 100644 index 000000000..3e4cdea46 --- /dev/null +++ b/src/components/atoms/RawRadio/RawRadio.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { HiddenRadio, RadioWrapper, StyledRadio } from './RawRadio.style'; + +export type TRawRadioProp = { + name: string; + checked: boolean; +} & React.InputHTMLAttributes; +// --------------------------------------------------------------------------- // +// ---------- The Raw Radio is only a styled Radio input -------------------- // +// --------------------------------------------------------------------------- // +export default function RawRadio(props: TRawRadioProp) { + const { name, checked, tabIndex, ...inputProps } = props; + + return ( + + + + + ); +} diff --git a/src/components/atoms/RawRadio/index.ts b/src/components/atoms/RawRadio/index.ts new file mode 100644 index 000000000..a361de5de --- /dev/null +++ b/src/components/atoms/RawRadio/index.ts @@ -0,0 +1 @@ +export { default as RawRadio } from './RawRadio'; diff --git a/src/components/atoms/RawSlider/RawSlider.stories.tsx b/src/components/atoms/RawSlider/RawSlider.stories.tsx new file mode 100644 index 000000000..afa613a7a --- /dev/null +++ b/src/components/atoms/RawSlider/RawSlider.stories.tsx @@ -0,0 +1,79 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import RawSlider from './RawSlider'; + +// Define metadata for the story +const meta = { + component: RawSlider, + parameters: { + docs: { + description: { + component: + 'RawSlider is a simple component to display the slider.
- It has a themeType prop to define the theme type of the slider
- It has a value prop to define the value of the slider
- It has a onChange prop to define the onChange function
- It has a id prop to define the id of the slider
- It has a min prop to define the min of the slider', + }, + }, + }, + // Define arguments for the story + argTypes: { + themeType: { + control: { + type: 'select', + }, + }, + value: { + control: { + type: 'number', + }, + }, + onChange: { + control: { + type: 'function', + }, + }, + id: { + control: { + type: 'text', + }, + }, + min: { + control: { + type: 'number', + }, + }, + activeHandler: { + description: 'Active handler is when the slider focused active or not', + control: { + type: 'function', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + min: 0, + themeType: 'secondary', + value: 0, + activeHandler: (boolean: boolean) => console.log(boolean), + onChange: (event: React.ChangeEvent) => console.log(event.target.value), + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/RawSlider/RawSlider.style.tsx b/src/components/atoms/RawSlider/RawSlider.style.tsx new file mode 100644 index 000000000..4f7a5e587 --- /dev/null +++ b/src/components/atoms/RawSlider/RawSlider.style.tsx @@ -0,0 +1,51 @@ +import { styled, css } from 'styled-components'; + +import { boxShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +// eslint-disable-next-line react-refresh/only-export-components +const DragableThumb = css<{ theme: TTheme }>` + height: 30px; + width: 30px; + border-radius: 50%; + background: ${({ theme }) => theme.colors.accent[0]}; + cursor: ew-resize; + ${boxShadow.sm} + transition: background 0.1s ease-in-out; + border: none; +`; + +export const StyledRawSlider = styled.input<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + -webkit-appearance: none; + width: 100%; + margin: 0; + height: 4px; + margin: 10px 0 12px 0; + background: ${({ theme, $themeType = 'secondary', $layer = 4 }) => getBackgroundColor({ theme, $themeType, $layer })}; + border-radius: 5px; + background-image: ${({ theme }) => `linear-gradient(90deg, ${theme.colors.accent[0]}, ${theme.colors.accent[1]})`}; + background-size: 70% 100%; + background-repeat: no-repeat; + outline: none; + + /* Chrome */ + &::-webkit-slider-thumb { + -webkit-appearance: none; + ${DragableThumb} + } + + /* Firefox */ + &::-moz-range-thumb { + ${DragableThumb} + } + + &::-webkit-slider-runnable-track { + -webkit-appearance: none; + box-shadow: none; + border: none; + background: transparent; + } +`; diff --git a/src/components/atoms/RawSlider/RawSlider.tsx b/src/components/atoms/RawSlider/RawSlider.tsx new file mode 100644 index 000000000..2e699f224 --- /dev/null +++ b/src/components/atoms/RawSlider/RawSlider.tsx @@ -0,0 +1,55 @@ +import React, { InputHTMLAttributes, useRef } from 'react'; +import { StyledRawSlider } from './RawSlider.style'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; + +// --------------------------------------------------------------------------- // +// ------------ Here is createt the Slider Atom (Range Slider) --------------- // +// --------------------------------------------------------------------------- // +export interface IRawSlider extends InputHTMLAttributes { + id?: string; + activeHandler?: (value: boolean) => void; + ref?: React.RefObject; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function RawSlider(props: IRawSlider) { + const { disabled, id, max, min, value, ref, activeHandler, themeType, layer, ...htmlProps } = props; + const inputSlider = useRef(null); + const sliderProgress = value ? Number(value) : 0; + + const focusHandler = (value: boolean) => { + activeHandler && activeHandler(value); + }; + + //initialize the min an max value when get it or not + const minVal = min ? Number(min) : 0; + const maxVal = max ? Number(max) : 100; + + //calc the the progress + const calcBackgorundSize = !isNaN(sliderProgress) ? ((sliderProgress - minVal) * 100) / (maxVal - minVal) + '% 100%' : '0% 100%'; + const calcSliderProgress = !isNaN(sliderProgress) ? sliderProgress : 0; + + return ( + focusHandler(true)} + onBlur={() => focusHandler(false)} + style={{ backgroundSize: calcBackgorundSize }} + id={id} + $themeType={themeType} + $layer={layer} + type="range" + value={calcSliderProgress} + min={minVal} + max={maxVal} + onTouchStart={() => { + focusHandler(true); + inputSlider.current?.focus(); + }} + onTouchEnd={() => setTimeout(() => focusHandler(false), 500)} + {...htmlProps} + /> + ); +} diff --git a/src/components/atoms/RawSlider/index.ts b/src/components/atoms/RawSlider/index.ts new file mode 100644 index 000000000..515481487 --- /dev/null +++ b/src/components/atoms/RawSlider/index.ts @@ -0,0 +1 @@ +export { default as RawSlider } from './RawSlider'; diff --git a/src/components/atoms/RawUL/RawUL.tsx b/src/components/atoms/RawUL/RawUL.tsx new file mode 100644 index 000000000..cac83d1e2 --- /dev/null +++ b/src/components/atoms/RawUL/RawUL.tsx @@ -0,0 +1,14 @@ +import { styled } from 'styled-components'; + +const RawUL = styled.ul` + padding: 0; + margin: 0; + + li { + list-style: none; + padding: 0; + margin: 0; + } +`; + +export default RawUL; diff --git a/src/components/atoms/ScrollableBar/ScrollableBar.stories.tsx b/src/components/atoms/ScrollableBar/ScrollableBar.stories.tsx new file mode 100644 index 000000000..03c5c1586 --- /dev/null +++ b/src/components/atoms/ScrollableBar/ScrollableBar.stories.tsx @@ -0,0 +1,52 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +import SVGEyeCrossed from '../../icons/SVGEyeCrossed/SVGEyeCrossed'; + +// Import the component to be tested +import ScrollableBar from './ScrollableBar'; +import { FancyBottomBarIcon } from '../../templates/FancyBottomBarIcon'; + +// Define metadata for the story +const meta = { + component: ScrollableBar, + parameters: { + docs: { + description: { + component: 'A ScrollableBar component, when the content is too wide to fit in the screen, it will be scrollable', + }, + }, + }, + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + + + + + + + + ), + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/ScrollableBar/ScrollableBar.tsx b/src/components/atoms/ScrollableBar/ScrollableBar.tsx new file mode 100644 index 000000000..0456c1c2f --- /dev/null +++ b/src/components/atoms/ScrollableBar/ScrollableBar.tsx @@ -0,0 +1,29 @@ +import React, { useRef } from 'react'; +import { ScollAbleBar } from './SrollableBar.style'; + +export interface IScrollableBar { + children?: React.ReactNode; +} +// --------------------------------------------------------------------------- // +// -------- Handles the overflow in the scrollbar with mac variation -------- // +// --------------------------------------------------------------------------- // +export default function ScrollableBar(props: IScrollableBar) { + const { children } = props; + + const scrollAbleBar = useRef(null); + + //this function handles the horizontal scrooling with the mouse wheel + const horizontalScrollingHandler = (e: React.WheelEvent) => { + if (/Mac/i.test(navigator.userAgent)) { + return; + } + + const box = scrollAbleBar.current; + box!.scrollLeft += e.deltaY < 0 ? -30 : 30; + }; + return ( + + {children} + + ); +} diff --git a/src/components/atoms/ScrollableBar/SrollableBar.style.tsx b/src/components/atoms/ScrollableBar/SrollableBar.style.tsx new file mode 100644 index 000000000..316a8c412 --- /dev/null +++ b/src/components/atoms/ScrollableBar/SrollableBar.style.tsx @@ -0,0 +1,11 @@ +import { styled } from 'styled-components'; + +export const ScollAbleBar = styled.div` + display: flex; + width: 100%; + overflow-x: scroll; + + &::-webkit-scrollbar { + display: none; + } +`; diff --git a/src/components/atoms/ScrollableBar/index.ts b/src/components/atoms/ScrollableBar/index.ts new file mode 100644 index 000000000..75d4afc07 --- /dev/null +++ b/src/components/atoms/ScrollableBar/index.ts @@ -0,0 +1 @@ +export { default as ScrollableBar } from './ScrollableBar'; diff --git a/src/components/atoms/SimpleDialog/SimpleDialog.stories.tsx b/src/components/atoms/SimpleDialog/SimpleDialog.stories.tsx new file mode 100644 index 000000000..991f7d408 --- /dev/null +++ b/src/components/atoms/SimpleDialog/SimpleDialog.stories.tsx @@ -0,0 +1,58 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import SimpleDialog from './SimpleDialog'; + +// Define metadata for the story +const meta = { + component: SimpleDialog, + parameters: { + docs: { + description: { + component: 'A ScrollableBar component, when the content is too wide to fit in the screen, it will be scrollable', + }, + }, + }, + // Define arguments for the story + argTypes: { + layer: { + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + }, + themeType: { + control: { + type: 'select', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + isOpen: true, + children: 'Test', + layer: 1, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SimpleDialog/SimpleDialog.tsx b/src/components/atoms/SimpleDialog/SimpleDialog.tsx new file mode 100644 index 000000000..ec2a70b01 --- /dev/null +++ b/src/components/atoms/SimpleDialog/SimpleDialog.tsx @@ -0,0 +1,55 @@ +import React, { useEffect, useState } from 'react'; +import { styled } from 'styled-components'; +import { animated, useSpring } from '@react-spring/web'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import getColorsForComponent from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +// Define the styled component for the dialog +const StyledDialog = styled(animated.div)<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: ${({ theme }) => theme.spacing.xl}; + border-radius: ${({ theme }) => theme.borderRadius.lg}; + border: none; + width: 70%; + max-height: 85%; + ${({ theme, $themeType = 'primary', $layer = 1 }) => getColorsForComponent({ theme, $themeType, $layer })} + z-index: 1000; +`; + +// Define the props for the SimpleDialog component +interface ISimpleDialog { + isOpen: boolean; + children: React.ReactNode; + themeType?: TThemeTypes; + layer?: TLayer; +} + +// Define the SimpleDialog component +export default function SimpleDialog({ isOpen, children, themeType, layer }: ISimpleDialog) { + const [shouldRender, setRender] = useState(isOpen); + + // Define the fade animation for the dialog + const fade = useSpring({ + transform: isOpen ? 'translate(-50%, -50%)' : 'translate(-50%, -40%)', + opacity: isOpen ? 1 : 0, + onRest: () => setRender(isOpen), + }); + + // Update the shouldRender state variable when the isOpen prop changes + useEffect(() => { + if (isOpen) setRender(true); + }, [isOpen]); + + // Render the SimpleDialog component with the appropriate props + return shouldRender ? ( + + {children} + + ) : null; +} diff --git a/src/components/atoms/SimpleDialog/index.ts b/src/components/atoms/SimpleDialog/index.ts new file mode 100644 index 000000000..cf6369d74 --- /dev/null +++ b/src/components/atoms/SimpleDialog/index.ts @@ -0,0 +1 @@ +export { default as SimpleDialog } from './SimpleDialog'; diff --git a/src/components/atoms/SingleInputAtom/SingleInputAtom.stories.tsx b/src/components/atoms/SingleInputAtom/SingleInputAtom.stories.tsx new file mode 100644 index 000000000..7baf069cc --- /dev/null +++ b/src/components/atoms/SingleInputAtom/SingleInputAtom.stories.tsx @@ -0,0 +1,79 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import { SingleInputAtom } from './SingleInputAtom'; + +// fix for building ... because storybook is not able to handle the default export +const HelperComponent = (props: React.ComponentProps) => ; +HelperComponent.displayName = 'SingleInputAtom'; + +// Define metadata for the story +const meta = { + component: HelperComponent, + parameters: { + docs: { + description: { + component: 'A ScrollableBar component, when the content is too wide to fit in the screen, it will be scrollable', + }, + }, + }, + // Define arguments for the story + argTypes: { + value: { + description: 'The value of the input, with a maximum length of 1', + control: { + type: 'text', + }, + }, + ariaLabel: { + control: { + type: 'text', + }, + }, + themeType: { + description: 'The theme type of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the input', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: '0', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + value: '1', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SingleInputAtom/SingleInputAtom.tsx b/src/components/atoms/SingleInputAtom/SingleInputAtom.tsx new file mode 100644 index 000000000..e4ef9ee9f --- /dev/null +++ b/src/components/atoms/SingleInputAtom/SingleInputAtom.tsx @@ -0,0 +1,69 @@ +import React, { forwardRef, useState } from 'react'; +import { styled } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; + +// the style for a single input +interface StyledSingleInputProps { + $hasValue: boolean; + $isFocused: boolean; + $themeType?: TThemeTypes; + $layer?: TLayer; +} +const StyledSingleInput = styled.input` + aspect-ratio: 4/5; + width: 1.5ch; + font-size: ${fontSize.xxl}; + text-align: center; + color: ${({ theme }) => theme.colors.secondary[0]}; + border: 1.5px solid + ${({ $hasValue, theme, $themeType = 'secondary', $layer }) => + $hasValue ? theme.colors.accent[0] : getBackgroundColor({ theme, $themeType, $layer })}; + border-radius: 5px; + padding: ${({ theme }) => theme.spacing.xs}; + background-color: transparent; + appearance: none; + outline: none; + box-shadow: ${({ $isFocused, theme }) => ($isFocused ? `0 0 2px 1px${theme.colors.accent[1]}` : 'none')}; +`; + +// --------------------------------------------------------------------------- // +// --------- A Single Letter/NumberInput for a Verification process ---------- // +// --------------------------------------------------------------------------- // +interface ISingleInputAtomProps { + value: string; + ariaLabel?: string; + onKeyDown?: (e: React.KeyboardEvent) => void; + themeType?: TThemeTypes; + layer?: TLayer; +} +export const SingleInputAtom = forwardRef( + ({ value, onKeyDown, ariaLabel, themeType, layer }, ref) => { + const [isFocused, setIsFocused] = useState(false); + + return ( + {}} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + $hasValue={value.length > 0} + $isFocused={isFocused} + /> + ); + } +); + +export default SingleInputAtom; diff --git a/src/components/atoms/SingleInputAtom/index.ts b/src/components/atoms/SingleInputAtom/index.ts new file mode 100644 index 000000000..b6cdf3e5c --- /dev/null +++ b/src/components/atoms/SingleInputAtom/index.ts @@ -0,0 +1 @@ +export { default as SingleInputAtom } from './SingleInputAtom'; diff --git a/src/components/atoms/SliderMarker/SliderMarker.stories.tsx b/src/components/atoms/SliderMarker/SliderMarker.stories.tsx new file mode 100644 index 000000000..d5d4ff320 --- /dev/null +++ b/src/components/atoms/SliderMarker/SliderMarker.stories.tsx @@ -0,0 +1,49 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import SliderMarker from './SliderMarker'; + +// Define metadata for the story +const meta = { + component: SliderMarker, + parameters: { + docs: { + description: { + component: 'A Marker component to display the position on the slider', + }, + }, + }, + // Define arguments for the story + argTypes: { + position: { + description: 'The position of the marker on the slider', + control: { + type: 'text', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + position: '50%', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SliderMarker/SliderMarker.style.tsx b/src/components/atoms/SliderMarker/SliderMarker.style.tsx new file mode 100644 index 000000000..1b48c7bac --- /dev/null +++ b/src/components/atoms/SliderMarker/SliderMarker.style.tsx @@ -0,0 +1,26 @@ +import { styled } from 'styled-components'; + +import { TTheme } from '../../../interface/TTheme'; +import colorTransparencyCalculator from '../../../design/designFunctions/colorTransparencyCalculator/colorTransparencyCalculator'; + +export const WrapperMarker = styled.div` + position: absolute; + box-sizing: border-box; + top: 2px; + bottom: 2px; + margin-left: 0; + margin-right: 0; + width: 5px; + z-index: 2; + transform: translate(-2.5px); +`; + +export const Marker = styled.div<{ theme: TTheme }>` + box-sizing: border-box; + position: relative; + border: ${({ theme }) => `solid 1px ${theme.colors.primary[0]} `}; + width: 100%; + height: 100%; + box-shadow: 0px 0px 1px 1px ${({ theme }) => colorTransparencyCalculator(theme.colors.secondary[0], 0.5)}; + border-radius: ${({ theme }) => theme.borderRadius.sm}; +`; diff --git a/src/components/atoms/SliderMarker/SliderMarker.tsx b/src/components/atoms/SliderMarker/SliderMarker.tsx new file mode 100644 index 000000000..a94d9ab36 --- /dev/null +++ b/src/components/atoms/SliderMarker/SliderMarker.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { Marker, WrapperMarker } from './SliderMarker.style'; + +// --------------------------------------------------------------------------- // +// --- The main Marker Component to display the position on the slider ------ // +// --------------------------------------------------------------------------- // +interface ISliderMarker { + position: string; + children?: React.ReactNode; +} +export default function SliderMarker({ position, children }: ISliderMarker) { + return ( + + + {/* the children can use to display something in the marker like the color indicator that moves with the marker */} + {children && children} + + + ); +} diff --git a/src/components/atoms/SliderMarker/index.ts b/src/components/atoms/SliderMarker/index.ts new file mode 100644 index 000000000..c517066ca --- /dev/null +++ b/src/components/atoms/SliderMarker/index.ts @@ -0,0 +1 @@ +export { default as SliderMarker } from './SliderMarker'; diff --git a/src/components/atoms/Slot/Slot.stories.tsx b/src/components/atoms/Slot/Slot.stories.tsx new file mode 100644 index 000000000..a7d19464f --- /dev/null +++ b/src/components/atoms/Slot/Slot.stories.tsx @@ -0,0 +1,59 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import Slot from './Slot'; +// Define metadata for the story +const meta = { + component: Slot, + parameters: { + docs: { + description: { + component: 'A ScrollableBar component, when the content is too wide to fit in the screen, it will be scrollable', + }, + }, + }, + // Define arguments for the story + argTypes: { + children: { + description: 'The content to be displayed in the slot', + control: { + type: 'text', + }, + }, + align: { + description: 'The alignment of the content in the slot', + control: { + type: 'select', + }, + }, + grow: { + description: 'The Flex grow factor of the slot', + control: { + type: 'number', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + children: 'Iam only a dummy', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/Slot/Slot.tsx b/src/components/atoms/Slot/Slot.tsx new file mode 100644 index 000000000..4e9d4dd6c --- /dev/null +++ b/src/components/atoms/Slot/Slot.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { styled, CSSProp } from 'styled-components'; + +type TAlign = 'center' | 'left' | 'right'; + +const StyledSlot = styled.div<{ $align?: TAlign; $grow?: number; $extenalStyle?: CSSProp }>` + display: flex; + position: relative; + height: 100%; + box-sizing: border-box; + justify-content: ${({ $align }) => { + if ($align === 'center') return 'center'; + if ($align === 'left') return 'flex-start'; + if ($align === 'right') return 'flex-end'; + return 'flex-start'; + }}; + flex-grow: ${({ $grow = 1 }) => $grow}; + align-items: center; + ${({ $extenalStyle }) => ($extenalStyle ? $extenalStyle : '')} +`; + +// --------------------------------------------------------------------------- // +// ------ A simple Slot component wich can use to position a component ------ // +// --------------------------------------------------------------------------- // +export interface ISlot { + children?: React.ReactNode; + align?: TAlign; + grow?: number; + externalStyle?: CSSProp; +} +export default function Slot(props: ISlot) { + const { children, align, grow, externalStyle } = props; + + return ( + + {children} + + ); +} diff --git a/src/components/atoms/Slot/index.ts b/src/components/atoms/Slot/index.ts new file mode 100644 index 000000000..81f1b2267 --- /dev/null +++ b/src/components/atoms/Slot/index.ts @@ -0,0 +1 @@ +export { default as Slot } from './Slot'; diff --git a/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.stories.tsx b/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.stories.tsx new file mode 100644 index 000000000..6737fa06d --- /dev/null +++ b/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.stories.tsx @@ -0,0 +1,67 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// Import the component to be tested +import SpeedDailMenueItem from './SpeedDailMenueItem'; +// Define metadata for the story +const meta = { + title: 'components/atoms/SpeedDailMenueItem', + component: SpeedDailMenueItem, + parameters: { + docs: { + description: { + component: 'A SpeedDailMenueItem or better a Button with a label and an icon', + }, + }, + }, + // Define arguments for the story + argTypes: { + isOpen: { + description: 'Is the menue open or closed', + type: { name: 'boolean' }, + }, + label: { + description: 'The label for the button', + type: { name: 'string' }, + }, + icon: { + description: 'The value for the button', + }, + hideLabel: { + description: 'Hide the label', + type: { name: 'boolean' }, + }, + labelAlign: { + description: 'Align the label left or right', + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + label: 'Iam only a dummy', + icon: , + isOpen: true, + hideLabel: false, + labelAlign: 'left', + index: 0, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.style.tsx b/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.style.tsx new file mode 100644 index 000000000..b8974c356 --- /dev/null +++ b/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.style.tsx @@ -0,0 +1,77 @@ +import styled, { css } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import { boxShadow, textShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TTheme } from '@/interface/TTheme'; + +export const MenueItemContainer = styled.div<{ $isOpen?: boolean; $index: number }>` + position: absolute; + left: 50%; + border-radius: 50%; + bottom: ${({ $index }) => `calc(${($index + 1) * 60}px)`}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + transform: ${({ $isOpen }) => ($isOpen ? 'translateY(0) scale(1) translateX(-50%)' : `translateY(50px) scale(0) translateX(-50%)`)}; + transition: + transform 0.25s ease-in-out, + opacity 0.25s ease-in-out; + transition-delay: ${({ $isOpen, $index }) => ($isOpen ? 0.1 * $index : 0.1 * (2 - $index))}s; + pointer-events: ${({ $isOpen }) => ($isOpen ? 'auto' : 'none')}; + transform: translate(-50%); + height: 40px; + width: 40px; + ${boxShadow.sm}; +`; + +export const MenuItem = styled.button<{ theme: TTheme }>` + width: 100%; + height: 100%; + border-radius: 50%; + padding: 0; + color: ${({ theme }) => theme.colors.secondary[0]}; + cursor: pointer; + font-size: ${fontSize.md}; + border: none; + outline: none; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; + background-color: ${({ theme }) => theme.colors.primary[2]}; + transition: 150ms ease-in-out; + + svg { + width: 50%; + height: 50%; + } + + &:hover { + transform: scale(1.01); + background-color: ${({ theme }) => theme.colors.accent[2]}; + } +`; + +export const Label = css<{ $isOpen: boolean; $labelAlign?: 'left' | 'right'; theme: TTheme }>` + position: absolute; + top: 50%; + transform: translateY(-50%); + color: ${({ theme }) => theme.colors.secondary[0]}; + display: ${({ $isOpen }) => ($isOpen ? 'inline' : 'none')}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + transition: + opacity 0.5s ease-in-out, + right 0.3s ease, + left 0.3s ease; + ${textShadow.md} + white-space: nowrap; + + ${({ $labelAlign }) => + $labelAlign === 'right' + ? css` + left: 100%; + margin-left: 10px; + ` + : css` + right: 100%; + margin-right: 10px; + `} +`; diff --git a/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.tsx b/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.tsx new file mode 100644 index 000000000..494db24e9 --- /dev/null +++ b/src/components/atoms/SpeedDialMenueItem/SpeedDailMenueItem.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +import { Typography } from '../Typography'; +import { Label, MenuItem, MenueItemContainer } from './SpeedDailMenueItem.style'; + +//the props only for the button +export type TMenueButtonProps = { + label?: string; + icon: string | number | JSX.Element; + index?: number; + onClick?: () => void; +}; + +// the props for the complete component +export type TMenueItemProps = { + labelAlign?: 'left' | 'right'; + hideLabel?: boolean; + isOpen?: boolean; +}; + +export type ISpeedDailMenueItem = TMenueButtonProps & TMenueItemProps; + +// --------------------------------------------------------------------------- // +// ---------- Component that handles the Buttonlist and the opening ---------- // +// --------------------------------------------------------------------------- // +export default function SpeedDailMenueItem(props: ISpeedDailMenueItem) { + const { label, icon, hideLabel, isOpen, onClick, index } = props; + + return ( + + + {icon} + + {label && !hideLabel && ( + + {label} + + )} + + ); +} diff --git a/src/components/atoms/SpeedDialMenueItem/index.ts b/src/components/atoms/SpeedDialMenueItem/index.ts new file mode 100644 index 000000000..bd1d74b0e --- /dev/null +++ b/src/components/atoms/SpeedDialMenueItem/index.ts @@ -0,0 +1 @@ +export { default as SpeedDialMenueItem } from './SpeedDailMenueItem'; diff --git a/src/components/atoms/SwipeUpContainer/SwipeUpContainer.stories.tsx b/src/components/atoms/SwipeUpContainer/SwipeUpContainer.stories.tsx new file mode 100644 index 000000000..250ce86a6 --- /dev/null +++ b/src/components/atoms/SwipeUpContainer/SwipeUpContainer.stories.tsx @@ -0,0 +1,71 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import SwipeUpContainer from './SwipeUpContainer'; + +// Define metadata for the story +const meta = { + component: SwipeUpContainer, + parameters: { + docs: { + description: { + component: + 'SwipeUpContainer is only the container for the SwipeUpModal.
- The isScalable and the style prop is for the dynamicly changing height of the container.
- The real hieght of the container is the height of the children. ', + }, + }, + }, + // Define arguments for the story + argTypes: { + style: { + description: 'The style of the container', + }, + isScalable: { + description: 'Is the container scalable', + }, + themeType: { + description: 'The theme type of the container', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the container', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + }, + children: { + description: 'The children of the container', + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + style: { height: '100px' }, + isScalable: true, + themeType: 'primary', + layer: 0, + children:
, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SwipeUpContainer/SwipeUpContainer.tsx b/src/components/atoms/SwipeUpContainer/SwipeUpContainer.tsx new file mode 100644 index 000000000..d82accab2 --- /dev/null +++ b/src/components/atoms/SwipeUpContainer/SwipeUpContainer.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TTheme } from '@/interface/TTheme'; + +interface IStyledSwipeUpContainer { + $giveSpace: boolean; + theme: TTheme; + $themeType?: TThemeTypes; + $layer?: TLayer; +} + +const StyledSwipeUpContainer = styled.div` + width: 100%; + max-height: 90%; + border-radius: ${({ theme }) => theme.borderRadius.xxxl} ${({ theme }) => theme.borderRadius.xxxl} 0 0; + position: sticky; + top: 0; + box-shadow: unset; + display: flex; + flex-direction: column; + align-items: center; + padding-top: ${({ $giveSpace }) => ($giveSpace ? ({ theme }) => theme.spacing.lg : '0')}; + z-index: 101; + backdrop-filter: blur(4px); + background-color: ${({ theme, $themeType = 'primary', $layer = 0 }) => getBackgroundColor({ theme, $themeType, $layer })}; +`; + +// --------------------------------------------------------------------------- // +// ----------- The Modal Atom the displays a simple Mobile Modal ------------- // +// --------------------------------------------------------------------------- // +interface ISwipeUpContainer { + children: React.ReactNode; + style?: { height: string }; + isScalable?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function SwipeUpContainer({ children, isScalable = true, style, themeType, layer }: ISwipeUpContainer) { + return ( + + {children} + + ); +} diff --git a/src/components/atoms/SwipeUpContainer/index.ts b/src/components/atoms/SwipeUpContainer/index.ts new file mode 100644 index 000000000..c58a67d30 --- /dev/null +++ b/src/components/atoms/SwipeUpContainer/index.ts @@ -0,0 +1 @@ +export { default as SwipeUpContainer } from './SwipeUpContainer'; diff --git a/src/components/atoms/SwipeUpDash/SwipeUpDash.stories.tsx b/src/components/atoms/SwipeUpDash/SwipeUpDash.stories.tsx new file mode 100644 index 000000000..85092bfdc --- /dev/null +++ b/src/components/atoms/SwipeUpDash/SwipeUpDash.stories.tsx @@ -0,0 +1,39 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import SwipeUpDash from './SwipeUpDash'; + +// Define metadata for the story +const meta = { + component: SwipeUpDash, + parameters: { + docs: { + description: { + component: 'SwipeUpDash is a simple horizontal line', + }, + }, + }, + // Define arguments for the story + argTypes: {}, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SwipeUpDash/SwipeUpDash.tsx b/src/components/atoms/SwipeUpDash/SwipeUpDash.tsx new file mode 100644 index 000000000..7a04d6155 --- /dev/null +++ b/src/components/atoms/SwipeUpDash/SwipeUpDash.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import simpleColorTransition from '../../../design/designFunctions/simpleColorTransition/simpleTransition'; +import { TTheme } from '@/interface/TTheme'; + +const StyledSwipeUpDash = styled.div<{ theme: TTheme }>` + width: 50px; + height: 3px; + background-color: ${({ theme }) => theme.colors.secondary[0]}; + border-radius: 3px; +`; + +const StyledButton = styled.button` + font-weight: bold; + border: none; + background-color: transparent; + cursor: pointer; + margin-top: 1rem; + margin-bottom: 1rem; + ${simpleColorTransition} +`; + +// --------------------------------------------------------------------------- // +// ---- The SwipeUpDash component is a simple horizontal line as Button ------ // +// --------------------------------------------------------------------------- // +export default function SwipeUpDash() { + return ( + + + + ); +} diff --git a/src/components/atoms/SwipeUpDash/index.ts b/src/components/atoms/SwipeUpDash/index.ts new file mode 100644 index 000000000..98875b136 --- /dev/null +++ b/src/components/atoms/SwipeUpDash/index.ts @@ -0,0 +1 @@ +export { default as SwipeUpDash } from './SwipeUpDash'; diff --git a/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.stories.tsx b/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.stories.tsx new file mode 100644 index 000000000..1b4222049 --- /dev/null +++ b/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.stories.tsx @@ -0,0 +1,112 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import SwitchActiveIndicator from './SwitchActiveIndicator'; + +// Define metadata for the story +const meta = { + component: SwitchActiveIndicator, + parameters: { + docs: { + description: { + component: + 'The SwitchActiveIndicator is a simple horizontal line or a bolb that indicates the active tab in a tab switcher, it is used in the FancyTabSwitch component', + }, + }, + }, + // Define arguments for the story + argTypes: { + $type: { + description: 'The type of the indicator, it can be a bolb or a underline', + control: { + type: 'radio', + }, + options: ['bolb', 'underline'], + defaultValue: { + summary: 'bolb', + }, + }, + $direction: { + description: 'The direction of the indicator where it should be moved horizontal or vertical', + control: { + type: 'radio', + }, + options: ['horizontal', 'vertical'], + defaultValue: { + summary: 'horizontal', + }, + }, + $itemNumber: { + description: 'The itemnumber is to calc the position of the indicator for each item', + control: { + type: 'number', + }, + }, + $rounded: { + description: 'The rounded is to set the border-radius of the indicator', + control: { + type: 'select', + }, + options: ['sm', 'md', 'lg', 'xl', 'xxl', 'xxxl', 'complete'], + defaultValue: { + summary: 'md', + }, + }, + $tabSpacing: { + description: 'The spacing is to calc with the offset the position of the indicator for each item', + control: { + type: 'select', + }, + defaultValue: { + summary: '', + }, + }, + $outlined: { + description: 'The outlined is to set the border of the indicator', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + $themeType: { + description: 'The themeType is to set the color of the indicator', + control: { + type: 'select', + }, + defaultValue: { + summary: 'accent', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + $itemNumber: 1, + $tabSpacing: 'md', + $type: 'bolb', + $rounded: 'md', + $outlined: false, + $direction: 'horizontal', + $themeType: 'accent', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.style.tsx b/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.style.tsx new file mode 100644 index 000000000..130c698c0 --- /dev/null +++ b/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.style.tsx @@ -0,0 +1,78 @@ +import { styled, css } from 'styled-components'; + +import { spacingPx } from '../../../design/theme/designSizes'; +import { IActiveSwitchIndicator } from './SwitchActiveIndicator'; +import { generateBlob } from './utils/generateBlob'; +import { generateTopline, generateUnderline } from './utils/generateLines'; +import { TTheme } from '@/interface/TTheme'; + +// Define the function to calculate the current position of the active indicator +type IClacCurrentPosition = Pick; +const clacCurrentPosition = (props: IClacCurrentPosition) => { + const { $itemNumber, $tabSpacing, $direction } = props; + + // Calculate the position in percent + const itemPosition = ($itemNumber - 1) * 100 + '%'; + + // Calculate the gap between the items + const gapSpacing = $tabSpacing ? ($itemNumber - 1) * parseFloat(spacingPx[$tabSpacing]) : 0; + + // Calculate the current position of the active indicator + const currentPosition = $itemNumber + ? css` + ${$direction === 'vertical' + ? css` + transform: translateY(calc(${itemPosition} + ${gapSpacing + 'px'})); + ` + : css` + transform: translateX(calc(${itemPosition} + ${gapSpacing + 'px'})); + `} + ` + : css` + transform: translateX(0); + `; + + // Return the styled-component CSS for the active indicator + return currentPosition; +}; + +// --------------------------------------------------------------------------- // +// -------- Here is the main Generator Function of the activ indicator ------- // +// --------------------------------------------------------------------------- // +type TActiveSwitchIndicator = Pick< + IActiveSwitchIndicator, + '$layer' | '$rounded' | '$outlined' | '$themeType' | '$type' | '$indicatorWidth' +>; +export const ActiveSwitchIndicator = styled.span` + position: absolute; + width: ${({ $indicatorWidth }) => $indicatorWidth ?? '100%'}; /* Set the width of the active indicator */ + + /* Build a switch case */ + ${(props) => { + switch (props.$type) { + case 'underline': + return generateUnderline({ ...props }); + case 'topline': + return generateTopline({ ...props }); + case 'bolb': + default: + return generateBlob({ ...props }); + } + }}/* Calculate the current position of the active indicator */ +`; + +// the wrapper that handles the position of the active indicator +type IWrapper = Pick; +export const Wrapper = styled.i` + top: 0; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + width: 100%; + height: 100%; + pointer-events: none; + cursor: pointer; + transition: transform 0.2s ease; + ${({ $itemNumber, $tabSpacing, $direction }) => clacCurrentPosition({ $itemNumber, $tabSpacing, $direction })} +`; diff --git a/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.tsx b/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.tsx new file mode 100644 index 000000000..b896e0a80 --- /dev/null +++ b/src/components/atoms/SwitchActiveIndicator/SwitchActiveIndicator.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { ActiveSwitchIndicator, Wrapper } from './SwitchActiveIndicator.style'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; +import { TSpacings } from '@/interface/TSpacings'; + +export interface IActiveSwitchIndicator { + $itemNumber: number; + $themeType?: TThemeTypes; + $layer?: TLayer; + $tabSpacing?: TSpacings; + $type?: 'bolb' | 'underline' | 'topline'; + $rounded?: TBorderRadiusSizes | string; + $outlined?: boolean; + $direction?: 'horizontal' | 'vertical'; + $indicatorWidth?: string; +} +// --------------------------------------------------------------------------- // +// -------- Create a Incator for wich Item in a Switch List ist activ ------- // +// --------------------------------------------------------------------------- // +export default function SwitchActiveIndicator(props: IActiveSwitchIndicator) { + const { $itemNumber, $tabSpacing, $direction, ...switchProps } = props; + return ( + + + + ); +} diff --git a/src/components/atoms/SwitchActiveIndicator/utils/generateBlob.ts b/src/components/atoms/SwitchActiveIndicator/utils/generateBlob.ts new file mode 100644 index 000000000..3254a279e --- /dev/null +++ b/src/components/atoms/SwitchActiveIndicator/utils/generateBlob.ts @@ -0,0 +1,49 @@ +import { css } from 'styled-components'; + +import { getBackgroundColor } from '../../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import colorTransparencyCalculator from '../../../../design/designFunctions/colorTransparencyCalculator/colorTransparencyCalculator'; +import { IActiveSwitchIndicator } from '../SwitchActiveIndicator'; +import themeStore from '@/design/theme/themeStore/themeStore'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; +import { TTheme } from '@/interface/TTheme'; + +// Define the function to generate a blob background for the active indicator +type IGenerateBlob = Pick & { theme: TTheme }; +export const generateBlob = (props: IGenerateBlob) => { + const { $themeType, theme, $rounded, $outlined, $layer } = props; + const borderRadius = themeStore.getState().theme.borderRadius; + let backgroundStyle; + + // Get the background color for the active indicator + const backgroundColor = getBackgroundColor({ theme, $themeType: $themeType ?? 'accent', $layer: $layer ?? 0 }); + + // Check if the provided rounded value is a valid key in the borderRadius object + const isRadiusKey = $rounded ? Object.keys(borderRadius).includes($rounded) : false; + + // If the outlined prop is true, generate a slightly transparent background color and a border + if ($outlined) { + const generateSlightBackgroundColor = colorTransparencyCalculator( + getBackgroundColor({ theme, $themeType: $themeType ?? 'accent', $layer: $layer ?? 3 }), + 0.2 + ); + + backgroundStyle = css` + background-color: ${generateSlightBackgroundColor}; + border: 1px solid ${backgroundColor}; + box-sizing: border-box; + `; + } else { + // If the outlined prop is false, use the background color as the background style + backgroundStyle = css` + background-color: ${backgroundColor}; + `; + } + + // Return the styled-component CSS for the active indicator + return css` + top: 0; + height: 100%; + border-radius: ${isRadiusKey ? borderRadius[$rounded as TBorderRadiusSizes] : $rounded}; + ${backgroundStyle} + `; +}; diff --git a/src/components/atoms/SwitchActiveIndicator/utils/generateLines.ts b/src/components/atoms/SwitchActiveIndicator/utils/generateLines.ts new file mode 100644 index 000000000..3d286788b --- /dev/null +++ b/src/components/atoms/SwitchActiveIndicator/utils/generateLines.ts @@ -0,0 +1,39 @@ +import { css } from 'styled-components'; + +import { getBackgroundColor } from '../../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { IActiveSwitchIndicator } from '../SwitchActiveIndicator'; +import { TTheme } from '@/interface/TTheme'; + +const generateLine = ({ theme, $themeType = 'accent', $layer }: IGenerateLine) => { + const backgroundColor = getBackgroundColor({ theme, $themeType, $layer }); + + return css` + height: 1.5px; + background-color: ${backgroundColor}; + border-radius: 10px; + `; +}; + +// Define the function to generate an underline for the active indicator +type IGenerateLine = Pick & { theme: TTheme }; +export const generateUnderline = ({ theme, $themeType = 'accent', $layer }: IGenerateLine) => { + // Get the background color for the active indicator + const line = generateLine({ theme, $themeType, $layer }); + // Return the styled-component CSS for the active indicator + return css` + ${line} + bottom: 0; + box-sizing: border-box; + `; +}; + +export const generateTopline = ({ theme, $themeType = 'accent', $layer }: IGenerateLine) => { + const line = generateLine({ theme, $themeType, $layer }); + + // Return the styled-component CSS for the active indicator + return css` + ${line} + top: 0; + box-sizing: border-box; + `; +}; diff --git a/src/components/atoms/Typography/Typography.stories.tsx b/src/components/atoms/Typography/Typography.stories.tsx new file mode 100644 index 000000000..d3f9895cd --- /dev/null +++ b/src/components/atoms/Typography/Typography.stories.tsx @@ -0,0 +1,60 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import Typography from './Typography'; + +// Define metadata for the story +const meta = { + component: Typography, + parameters: { + docs: { + description: { + component: 'The Typography component can render differnet elements with different styles', + }, + }, + }, + // Define arguments for the story + argTypes: { + type: { + description: 'The elemnt type of the typography', + control: { + type: 'select', + }, + }, + variant: { + description: 'The variant how the typography should look like', + control: { + type: 'select', + }, + }, + className: { + description: 'The className for the typography', + control: { + type: 'text', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => {args.children}, + args: { + children: 'I need something to eat, i think i will eat a pizza', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/Typography/Typography.tsx b/src/components/atoms/Typography/Typography.tsx new file mode 100644 index 000000000..e62a6a63e --- /dev/null +++ b/src/components/atoms/Typography/Typography.tsx @@ -0,0 +1,46 @@ +import React, { ReactNode } from 'react'; +import { CSSProp, css } from 'styled-components'; + +import { IStyledComponentProps, TypographyList } from './TypographyFontVariations.style'; +import themeStore from '../../../design/theme/themeStore/themeStore'; +import { generateFontVariants } from './TypographyStyleVariants'; + +const generateStyle = (externalStyle: CSSProp, fontWeight: 'normal' | 'bold' | undefined) => { + return css` + font-weight: ${fontWeight}; + ${externalStyle}; + `; +}; + +export type ITypography = { + type?: keyof typeof TypographyList; + variant?: keyof typeof TypographyList; + children?: ReactNode; + weight?: 'normal' | 'bold'; + style?: CSSProp; + htmlFor?: string; +} & Omit, 'style'>; +// --------------------------------------------------------------------------- // +// The Typography component can render differnet elements with different styles// +// ------------- like a "h4 can have the style of a p" ----------------------- // +export default function Typography({ type, variant, children, style, weight, ...htmlProps }: ITypography) { + // get the theme font sizes + const themeFonts = themeStore((state) => state.theme.fontSizes); + + // generate the Typography component based on the type prop; + // const Component = TypographyList[type] || TypographyList.content; + const Component = TypographyList[type || 'content'] as React.FC; + + const mixedStyle = generateStyle(style, weight); + // get the variant style based on the variant prop or the type prop; + const fontVariants = generateFontVariants(themeFonts); + + // get the variant style based on the variant prop or the type prop; + const variantStyle = variant ? fontVariants[variant] : (fontVariants[type || 'content'] as CSSProp); + + return ( + + {children} + + ); +} diff --git a/src/components/atoms/Typography/TypographyFontVariations.style.tsx b/src/components/atoms/Typography/TypographyFontVariations.style.tsx new file mode 100644 index 000000000..326ac8836 --- /dev/null +++ b/src/components/atoms/Typography/TypographyFontVariations.style.tsx @@ -0,0 +1,44 @@ +/* eslint-disable react-refresh/only-export-components */ +import { ReactNode } from 'react'; +import styled, { CSSProp } from 'styled-components'; + +export interface IStyledComponentProps { + children?: ReactNode; + $variant?: CSSProp; + $style?: CSSProp; +} +// Base styled component for common styles +const BaseStyledComponent = styled.span` + ${(props) => props.$variant}; + ${(props) => props.$style}; +`; + +// Styled components for each type of typography +const StyledH1 = styled(BaseStyledComponent).attrs({ as: 'h1' })``; +const StyledH2 = styled(BaseStyledComponent).attrs({ as: 'h2' })``; +const StyledH3 = styled(BaseStyledComponent).attrs({ as: 'h3' })``; +const StyledH4 = styled(BaseStyledComponent).attrs({ as: 'h4' })``; +const StyledH5 = styled(BaseStyledComponent).attrs({ as: 'h5' })``; +const StyledH6 = styled(BaseStyledComponent).attrs({ as: 'h6' })``; +const StyledLabel = styled(BaseStyledComponent).attrs({ as: 'label' })``; +const StyledButton = styled(BaseStyledComponent).attrs({ as: 'span' })``; // Assuming you want a span for a button +const StyledInlineElement = styled(BaseStyledComponent).attrs({ as: 'span' })``; +const StyledContent = styled(BaseStyledComponent).attrs({ as: 'p' })``; +const StyledSmText = styled(BaseStyledComponent).attrs({ as: 'p' })``; +const StyledVerySmText = styled(BaseStyledComponent).attrs({ as: 'span' })``; + +// Export the list of typography components +export const TypographyList = { + h1: StyledH1, + h2: StyledH2, + h3: StyledH3, + h4: StyledH4, + h5: StyledH5, + h6: StyledH6, + label: StyledLabel, + button: StyledButton, + inlineElement: StyledInlineElement, + content: StyledContent, + smText: StyledSmText, + verysmText: StyledVerySmText, +}; diff --git a/src/components/atoms/Typography/TypographyStyleVariants.tsx b/src/components/atoms/Typography/TypographyStyleVariants.tsx new file mode 100644 index 000000000..ce85125c1 --- /dev/null +++ b/src/components/atoms/Typography/TypographyStyleVariants.tsx @@ -0,0 +1,88 @@ +import { css } from 'styled-components'; + +import { TFontSizes } from '@/interface/IFontSizes'; +import { TTheme } from '@/interface/TTheme'; + +const nullifyStyle = css` + margin: 0; +`; + +// Hilfsfunktion fΓΌr den Medien-Query +const responsiveFontSize = (mobileSize: string, desktopSize: string) => css<{ theme?: TTheme }>` + font-size: ${mobileSize}; + + @media (min-width: ${({ theme }) => theme.breakpoints.md}) { + font-size: ${desktopSize}; + } +`; + +export const generateFontVariants = (themeFonts: TFontSizes) => { + const { desktop, mobile } = themeFonts; + + return { + h1: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.headings.h1, desktop.headings.h1)} + line-height: 1.2; + `, + h2: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.headings.h2, desktop.headings.h2)} + line-height: 1.2; + `, + h3: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.headings.h3, desktop.headings.h3)} + line-height: 1.2; + `, + h4: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.headings.h4, desktop.headings.h4)} + line-height: 1.2; + `, + h5: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.headings.h5, desktop.headings.h5)} + line-height: 1.2; + `, + h6: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.headings.h6, desktop.headings.h6)} + line-height: 1.2; + `, + button: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.textElements.button, desktop.textElements.button)} + font-weight: bold; + letter-spacing: 0.4px; + `, + label: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.textElements.label, desktop.textElements.label)} + font-weight: 400; + `, + inlineElement: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.textElements.caption, desktop.textElements.caption)} + font-weight: 400; + `, + content: css` + ${nullifyStyle}; + ${responsiveFontSize(mobile.textElements.caption, desktop.textElements.caption)} + line-height: 1.4; + font-weight: 400; + `, + smText: css` + line-height: 1.1; + ${nullifyStyle}; + ${responsiveFontSize(mobile.textElements.smText, desktop.textElements.smText)} + font-weight: 400; + `, + verysmText: css` + line-height: 1; + ${nullifyStyle}; + ${responsiveFontSize(mobile.textElements.verysmText, desktop.textElements.verysmText)} + font-weight: 400; + `, + }; +}; diff --git a/src/components/atoms/Typography/index.ts b/src/components/atoms/Typography/index.ts new file mode 100644 index 000000000..1b67c1953 --- /dev/null +++ b/src/components/atoms/Typography/index.ts @@ -0,0 +1 @@ +export { default as Typography } from './Typography'; diff --git a/src/components/atoms/WeekDays/WeekDays.stories.tsx b/src/components/atoms/WeekDays/WeekDays.stories.tsx new file mode 100644 index 000000000..c023077dd --- /dev/null +++ b/src/components/atoms/WeekDays/WeekDays.stories.tsx @@ -0,0 +1,62 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import WeekDays from './WeekDays'; + +// Define metadata for the story +const meta = { + component: WeekDays, + parameters: { + docs: { + description: { + component: + 'The WeekDays component that only shows the week days from Mo - Su as label and automatically adjusts to the language of the browser', + }, + }, + }, + // Define arguments for the story + argTypes: { + themeType: { + description: 'The Color of the week days', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the week days', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 0, + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: {}, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/WeekDays/WeekDays.tsx b/src/components/atoms/WeekDays/WeekDays.tsx new file mode 100644 index 000000000..6f4b964f9 --- /dev/null +++ b/src/components/atoms/WeekDays/WeekDays.tsx @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from 'react'; +import { styled } from 'styled-components'; + +import Typography from '../Typography/Typography'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +const WeekdaysConatiner = styled.div<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + display: grid; + grid-template-columns: repeat(7, 1fr); + width: 100%; + margin-bottom: ${({ theme }) => theme.spacing.xs}; + color: ${({ theme, $themeType, $layer }) => getBackgroundColor({ theme, $themeType: $themeType ?? 'secondary', $layer: $layer ?? 0 })}; + + & > * { + display: flex; + justify-content: center; + align-items: center; + } +`; +// --------------------------------------------------------------------------- // +// ---------- This Atom creates a List of all Weekdas from Mo - Su ----------- // +// --------------------------------------------------------------------------- // +interface IWeekDays { + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function WeekDays({ themeType, layer }: IWeekDays) { + const [weekdays, setWeekdays] = useState([]); + + useEffect(() => { + const days = []; + for (let i = 5; i <= 11; i++) { + const day = new Date(1970, 0, i); + days.push(day.toLocaleString(navigator.language, { weekday: 'short' }).slice(0, 2)); + } + setWeekdays(days); + }, []); + + return ( + + {weekdays.map((day) => ( + + {day} + + ))} + + ); +} diff --git a/src/components/atoms/WeekDays/index.ts b/src/components/atoms/WeekDays/index.ts new file mode 100644 index 000000000..e55f3e037 --- /dev/null +++ b/src/components/atoms/WeekDays/index.ts @@ -0,0 +1 @@ +export { default as WeekDays } from './WeekDays'; diff --git a/src/components/atoms/YearSelector/YearSelector.stories.tsx b/src/components/atoms/YearSelector/YearSelector.stories.tsx new file mode 100644 index 000000000..625453891 --- /dev/null +++ b/src/components/atoms/YearSelector/YearSelector.stories.tsx @@ -0,0 +1,71 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import YearSelector from './YearSelector'; +// Define metadata for the story +const meta = { + component: YearSelector, + parameters: { + docs: { + description: { + component: 'the year selector is a simple component to select a year', + }, + }, + }, + // Define arguments for the story + argTypes: { + selectedYear: { + description: 'The year to be displayed', + control: { + type: 'number', + }, + }, + themeType: { + description: 'The theme type of the year selector', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the year selector', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + handler: { + description: 'The handler for the year selector', + control: { + type: 'function', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + selectedYear: 2021, + handler: (year: number) => console.log(year), + themeType: 'secondary', + layer: 0, + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/atoms/YearSelector/YearSelector.style.tsx b/src/components/atoms/YearSelector/YearSelector.style.tsx new file mode 100644 index 000000000..90bc2fe56 --- /dev/null +++ b/src/components/atoms/YearSelector/YearSelector.style.tsx @@ -0,0 +1,34 @@ +import { css, styled } from 'styled-components'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +export const StyledYearSelector = styled.div<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + color: ${({ theme, $layer, $themeType }) => ($themeType ? theme.colors[$themeType][$layer ?? 0] : theme.colors.secondary[0])}; +`; + +export const StyledButton = styled.button<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + color: ${({ theme, $layer, $themeType = 'secondary' }) => getBackgroundColor({ theme, $themeType, $layer })}; + border: none; + padding: 0; + cursor: pointer; +`; + +export const SVGDesignCSS = css<{ theme: TTheme }>` + svg { + path: { + stroke: ${({ theme }) => theme.colors.secondary[0]}; + stroke-width: 2px; + } + } +`; diff --git a/src/components/atoms/YearSelector/YearSelector.tsx b/src/components/atoms/YearSelector/YearSelector.tsx new file mode 100644 index 000000000..2e97db5cb --- /dev/null +++ b/src/components/atoms/YearSelector/YearSelector.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import SVGChevronLeft from '../../icons/SVGChevronLeft/SVGChevronLeft'; +import SVGChevronRight from '../../icons/SVGChevronRight/SVGChevronRight'; +import { SVGDesignCSS, StyledButton, StyledYearSelector } from './YearSelector.style'; +import Typography from '../Typography/Typography'; +import { FancySVGAtom } from '../FancySVGAtom'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +// --------------------------------------------------------------------------- // +// ---------- Here are the design variants for sizing and alignment ---------- // +// --------------------------------------------------------------------------- // +interface IYearSelector { + selectedYear: number; + themeType?: TThemeTypes; + layer?: TLayer; + handler?: (change: number) => void; +} +export default function YearSelector({ selectedYear, handler, themeType, layer }: IYearSelector) { + // update the year and call the handler if the year changes + const handleYearChange = (change: number) => { + const calcCurrentYear = selectedYear + change; + handler && handler(calcCurrentYear); + }; + + return ( + + handleYearChange(-1)} $themeType={themeType} $layer={layer}> + + {SVGChevronLeft} + + + + {selectedYear} + + handleYearChange(1)} $themeType={themeType} $layer={layer}> + + {SVGChevronRight} + + + + ); +} diff --git a/src/components/atoms/YearSelector/index.ts b/src/components/atoms/YearSelector/index.ts new file mode 100644 index 000000000..54a59e5c6 --- /dev/null +++ b/src/components/atoms/YearSelector/index.ts @@ -0,0 +1 @@ +export { default as YearSelector } from './YearSelector'; diff --git a/src/components/icons/SVGCheckMark/SVGCheckMark.tsx b/src/components/icons/SVGCheckMark/SVGCheckMark.tsx new file mode 100644 index 000000000..98a92cc6b --- /dev/null +++ b/src/components/icons/SVGCheckMark/SVGCheckMark.tsx @@ -0,0 +1,7 @@ +const SVGCheckMark = () => ( + + + +); + +export default SVGCheckMark; diff --git a/src/components/icons/SVGCheckMark/index.ts b/src/components/icons/SVGCheckMark/index.ts new file mode 100644 index 000000000..2957ab2e7 --- /dev/null +++ b/src/components/icons/SVGCheckMark/index.ts @@ -0,0 +1 @@ +export { default as SVGCheckMark } from './SVGCheckMark'; diff --git a/src/components/icons/SVGChevronLeft/SVGChevronLeft.tsx b/src/components/icons/SVGChevronLeft/SVGChevronLeft.tsx new file mode 100644 index 000000000..afee637be --- /dev/null +++ b/src/components/icons/SVGChevronLeft/SVGChevronLeft.tsx @@ -0,0 +1,12 @@ +const SVGChevronLeft = ( + + + +); + +export default SVGChevronLeft; diff --git a/src/components/icons/SVGChevronLeft/index.ts b/src/components/icons/SVGChevronLeft/index.ts new file mode 100644 index 000000000..65771eb88 --- /dev/null +++ b/src/components/icons/SVGChevronLeft/index.ts @@ -0,0 +1 @@ +export { default as SVGChevronLeft } from './SVGChevronLeft'; diff --git a/src/components/icons/SVGChevronRight/SVGChevronRight.tsx b/src/components/icons/SVGChevronRight/SVGChevronRight.tsx new file mode 100644 index 000000000..1ba247604 --- /dev/null +++ b/src/components/icons/SVGChevronRight/SVGChevronRight.tsx @@ -0,0 +1,12 @@ +const SVGChevronRight = ( + + + +); + +export default SVGChevronRight; diff --git a/src/components/icons/SVGChevronRight/index.ts b/src/components/icons/SVGChevronRight/index.ts new file mode 100644 index 000000000..a91ca91d5 --- /dev/null +++ b/src/components/icons/SVGChevronRight/index.ts @@ -0,0 +1 @@ +export { default as SVGChevronRight } from './SVGChevronRight'; diff --git a/src/components/icons/SVGClipBoardIcon/SVGClipBoardIcon.tsx b/src/components/icons/SVGClipBoardIcon/SVGClipBoardIcon.tsx new file mode 100644 index 000000000..489b1e61f --- /dev/null +++ b/src/components/icons/SVGClipBoardIcon/SVGClipBoardIcon.tsx @@ -0,0 +1,9 @@ +const ClipBoardIcon = () => ( + + + + + +); + +export default ClipBoardIcon; diff --git a/src/components/icons/SVGClipBoardIcon/index.ts b/src/components/icons/SVGClipBoardIcon/index.ts new file mode 100644 index 000000000..20e3b1319 --- /dev/null +++ b/src/components/icons/SVGClipBoardIcon/index.ts @@ -0,0 +1 @@ +export { default as SVGClipBoardIcon } from './SVGClipBoardIcon'; diff --git a/src/components/icons/SVGClipBoardIconChecked/SVGClipBoardIconChecked.tsx b/src/components/icons/SVGClipBoardIconChecked/SVGClipBoardIconChecked.tsx new file mode 100644 index 000000000..6a0038d91 --- /dev/null +++ b/src/components/icons/SVGClipBoardIconChecked/SVGClipBoardIconChecked.tsx @@ -0,0 +1,9 @@ +const ClipBoardIconCheck = () => ( + + + + + +); + +export default ClipBoardIconCheck; diff --git a/src/components/icons/SVGClipBoardIconChecked/index.ts b/src/components/icons/SVGClipBoardIconChecked/index.ts new file mode 100644 index 000000000..a82d8088e --- /dev/null +++ b/src/components/icons/SVGClipBoardIconChecked/index.ts @@ -0,0 +1 @@ +export { default as SVGClipBoardIconChecked } from './SVGClipBoardIconChecked'; diff --git a/src/components/icons/SVGEyeCrossed/SVGEyeCrossed.tsx b/src/components/icons/SVGEyeCrossed/SVGEyeCrossed.tsx new file mode 100644 index 000000000..bd3e904ea --- /dev/null +++ b/src/components/icons/SVGEyeCrossed/SVGEyeCrossed.tsx @@ -0,0 +1,9 @@ +const SVGEyeCrossed = ( + + + + + +); + +export default SVGEyeCrossed; diff --git a/src/components/icons/SVGEyeCrossed/index.ts b/src/components/icons/SVGEyeCrossed/index.ts new file mode 100644 index 000000000..431e07dd6 --- /dev/null +++ b/src/components/icons/SVGEyeCrossed/index.ts @@ -0,0 +1 @@ +export { default } from './SVGEyeCrossed'; diff --git a/src/components/icons/SVGEyeOpen/SVGEyeOpen.tsx b/src/components/icons/SVGEyeOpen/SVGEyeOpen.tsx new file mode 100644 index 000000000..10d8627d3 --- /dev/null +++ b/src/components/icons/SVGEyeOpen/SVGEyeOpen.tsx @@ -0,0 +1,9 @@ +const SVGEyeOpen = ( + // the eye icon for the password type toggle + + + + +); + +export default SVGEyeOpen; diff --git a/src/components/icons/SVGEyeOpen/index.ts b/src/components/icons/SVGEyeOpen/index.ts new file mode 100644 index 000000000..5114433ea --- /dev/null +++ b/src/components/icons/SVGEyeOpen/index.ts @@ -0,0 +1 @@ +export { default as SVGEyeOpen } from './SVGEyeOpen'; diff --git a/src/components/icons/SVGLoadingArrows/SVGLoadingArrows.tsx b/src/components/icons/SVGLoadingArrows/SVGLoadingArrows.tsx new file mode 100644 index 000000000..1c5a3da7e --- /dev/null +++ b/src/components/icons/SVGLoadingArrows/SVGLoadingArrows.tsx @@ -0,0 +1,11 @@ +const SVGLoadingArrows = () => ( + + + + +); + +export default SVGLoadingArrows; diff --git a/src/components/icons/SVGLoadingArrows/index.ts b/src/components/icons/SVGLoadingArrows/index.ts new file mode 100644 index 000000000..971c82d46 --- /dev/null +++ b/src/components/icons/SVGLoadingArrows/index.ts @@ -0,0 +1 @@ +export { default as SVGLoadingArrows } from './SVGLoadingArrows'; diff --git a/src/components/icons/SVGPlus/SVGPlus.tsx b/src/components/icons/SVGPlus/SVGPlus.tsx new file mode 100644 index 000000000..a2c26a1f7 --- /dev/null +++ b/src/components/icons/SVGPlus/SVGPlus.tsx @@ -0,0 +1,7 @@ +const SVGPlus = ( + + + +); + +export default SVGPlus; diff --git a/src/components/icons/SVGPlus/index.ts b/src/components/icons/SVGPlus/index.ts new file mode 100644 index 000000000..fa3ece124 --- /dev/null +++ b/src/components/icons/SVGPlus/index.ts @@ -0,0 +1 @@ +export { default as SVGPlus } from './SVGPlus'; diff --git a/src/components/icons/SVGSearch/SVGSearch.tsx b/src/components/icons/SVGSearch/SVGSearch.tsx new file mode 100644 index 000000000..cd8d212fd --- /dev/null +++ b/src/components/icons/SVGSearch/SVGSearch.tsx @@ -0,0 +1,7 @@ +const SVGSearch = ( + + + +); + +export default SVGSearch; diff --git a/src/components/icons/SVGSearch/index.ts b/src/components/icons/SVGSearch/index.ts new file mode 100644 index 000000000..223c0f73f --- /dev/null +++ b/src/components/icons/SVGSearch/index.ts @@ -0,0 +1 @@ +export { default as SVGSearch } from './SVGSearch'; diff --git a/src/components/icons/SVGXCircle/SVGXCircle.tsx b/src/components/icons/SVGXCircle/SVGXCircle.tsx new file mode 100644 index 000000000..d06302399 --- /dev/null +++ b/src/components/icons/SVGXCircle/SVGXCircle.tsx @@ -0,0 +1,8 @@ +const SVGXCircle = () => ( + + + + +); + +export default SVGXCircle; diff --git a/src/components/icons/SVGXCircle/index.ts b/src/components/icons/SVGXCircle/index.ts new file mode 100644 index 000000000..bed12525d --- /dev/null +++ b/src/components/icons/SVGXCircle/index.ts @@ -0,0 +1 @@ +export { default as SVGXCircle } from './SVGXCircle'; diff --git a/src/components/molecules/BottomBar/BottomBar.stories.tsx b/src/components/molecules/BottomBar/BottomBar.stories.tsx new file mode 100644 index 000000000..892e62d47 --- /dev/null +++ b/src/components/molecules/BottomBar/BottomBar.stories.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyBottomBarStatic from './BottomBar'; + +const meta = { + component: FancyBottomBarStatic, + parameters: { + docs: { + description: { + component: + 'Smart-Comonent: This component shows a bar with icons and labels.
It is handled by a store to show and hide the bar and to (set/get) the active button.', + }, + }, + }, + argTypes: { + themeType: { + description: 'This prop will change the color of the bar', + control: { + type: 'select', + }, + }, + isVisible: { + description: 'This prop will show or hide the bar / props musst be received before the component is rendered', + control: { + type: 'boolean', + }, + }, + layer: { + description: 'This prop will change the layer of the bar', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: {}, +}; diff --git a/src/components/molecules/BottomBar/BottomBar.style.tsx b/src/components/molecules/BottomBar/BottomBar.style.tsx new file mode 100644 index 000000000..50664be3a --- /dev/null +++ b/src/components/molecules/BottomBar/BottomBar.style.tsx @@ -0,0 +1,9 @@ +import { css } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +export const fancyBarStyle = css<{ theme?: TTheme }>` + display: flex; + padding: ${({ theme }) => theme.spacing.sm + ' ' + theme.spacing.sm + ' ' + theme.spacing.sm + ' 0'}; + width: 100%; +`; diff --git a/src/components/molecules/BottomBar/BottomBar.tsx b/src/components/molecules/BottomBar/BottomBar.tsx new file mode 100644 index 000000000..0785e729a --- /dev/null +++ b/src/components/molecules/BottomBar/BottomBar.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { FancyBox } from '../../atoms/FancyBox'; +import { CSSProp, css } from 'styled-components'; +import { fancyBarStyle } from './BottomBar.style'; + +// --------------------------------------------------------------------------- // +// ------------------ The Bottom Bar for the mobile navigation --------------- // +// -------------- use the store to controle the bar from outside ------------ // +type TBottomBar = { + isVisible?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; + externalStyle?: CSSProp; + children?: React.ReactNode; +} & React.ComponentProps; +export default function BottomBar(props: TBottomBar) { + const { isVisible, externalStyle, children, ...bottomBarProps } = { ...defaultProps, ...props }; + + return ( + <> + {isVisible && ( + + {children} + + )} + + ); +} + +const defaultProps: TBottomBar = { + isVisible: true, +}; diff --git a/src/components/molecules/BottomBar/index.ts b/src/components/molecules/BottomBar/index.ts new file mode 100644 index 000000000..2f56dfbe7 --- /dev/null +++ b/src/components/molecules/BottomBar/index.ts @@ -0,0 +1 @@ +export { default as BottomBar } from './BottomBar'; diff --git a/src/components/molecules/BottomBarIcon/BottomBarIcon.stories.tsx b/src/components/molecules/BottomBarIcon/BottomBarIcon.stories.tsx new file mode 100644 index 000000000..b17b9fa0e --- /dev/null +++ b/src/components/molecules/BottomBarIcon/BottomBarIcon.stories.tsx @@ -0,0 +1,73 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; +// Import the component to be tested +import BottomBarIcon from './BottomBarIcon'; +// Define metadata for the story +const meta = { + component: BottomBarIcon, + parameters: { + docs: { + description: { + component: 'The BottomBarIcon component is for displaying a bottom bar icon like.', + }, + }, + }, + // Define arguments for the story + argTypes: { + disabled: { + control: { type: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + label: { + control: { type: 'text' }, + defaultValue: { + summary: 'Label', + }, + }, + icon: { + control: { type: 'object' }, + }, + isActive: { + control: { type: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + themeType: { + control: { type: 'select' }, + defaultValue: { + summary: 'secondary', + }, + }, + }, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + label: 'Label', + disabled: false, + themeType: 'secondary', + layer: 0, + icon: , + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/molecules/BottomBarIcon/BottomBarIcon.style.tsx b/src/components/molecules/BottomBarIcon/BottomBarIcon.style.tsx new file mode 100644 index 000000000..eb36742f8 --- /dev/null +++ b/src/components/molecules/BottomBarIcon/BottomBarIcon.style.tsx @@ -0,0 +1,42 @@ +import { styled } from 'styled-components'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { disabledStyle } from '../../../design/designFunctions/disabledStyle/disableStyle'; +import { TTheme } from '@/interface/TTheme'; + +interface IContentWrapper { + $isActive?: boolean; + $disabled?: boolean; + theme: TTheme; + $themeType?: TThemeTypes; + $layer?: TLayer; +} + +export const ContentWrapper = styled.div` + position: relative; + display: flex; + flex-direction: column; + align-items: center; + width: 78px; + min-height: 32px; + background-color: transparent; + outline: none; + border: none; + cursor: pointer; + margin: 0 auto; + color: ${({ $isActive, theme, $layer = 0, $themeType = 'secondary' }) => + $isActive ? theme.colors.accent[0] : getBackgroundColor({ theme, $themeType, $layer })}; + transition: all 0.3s ease-in-out; + ${({ $disabled }) => $disabled && disabledStyle} + padding-bottom: ${({ theme }) => theme.spacing.xs}; + text-decoration: none; + + /* This is a media query that tests if the primary input mechanism of the device (e.g., mouse or touch screen) is capable of hovering */ + @media (hover: hover) { + &:hover { + color: ${({ $disabled, theme }) => !$disabled && theme.colors.accent[0]}; + } + } +`; diff --git a/src/components/molecules/BottomBarIcon/BottomBarIcon.tsx b/src/components/molecules/BottomBarIcon/BottomBarIcon.tsx new file mode 100644 index 000000000..1146bc4e0 --- /dev/null +++ b/src/components/molecules/BottomBarIcon/BottomBarIcon.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import FancyContent from '../FancyContent/FancyContent'; +import { ContentWrapper } from './BottomBarIcon.style'; + +interface IBottomBarIconProps { + icon: React.ReactNode; + label: string; + hideLabel?: boolean; + isActive?: boolean; + disabled?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; + id?: string; +} +// --------------------------------------------------------------------------- // +// -------------------- Only a Bottombar Icon with Text --------------------- // +// --------------------------------------------------------------------------- // +export default function BottomBarIcon(props: IBottomBarIconProps) { + const { icon, label, isActive, disabled, themeType, layer, hideLabel, id } = props; + + return ( + + + + {icon} + + {!hideLabel && {label}} + + + ); +} diff --git a/src/components/molecules/Button/Button.stories.tsx b/src/components/molecules/Button/Button.stories.tsx new file mode 100644 index 000000000..254d816ff --- /dev/null +++ b/src/components/molecules/Button/Button.stories.tsx @@ -0,0 +1,94 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import Button from './Button'; + +const meta = { + component: Button, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: This component is a button with a lot of props to change the style.', + }, + }, + }, + argTypes: { + themeType: { + description: 'This prop will change the color of the bar', + control: { + type: 'select', + }, + }, + layer: { + description: 'This prop will change the layer of the bar', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + outlined: { + description: 'This prop will change the Nutton to outlined', + control: { + type: 'boolean', + }, + }, + textColor: { + description: 'This prop will change the color of the text', + control: { + type: 'select', + }, + }, + hoverColor: { + description: 'This prop will change the color of the hover', + control: { + type: 'select', + }, + }, + wide: { + description: 'This prop will change the width of the button', + control: { + type: 'boolean', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => + + + + ); +} + +const defaultProps: ISpeedail = { + labelAlign: 'right', +}; diff --git a/src/components/molecules/FancySpeedDailButton/SpeedDailButton.style.tsx b/src/components/molecules/FancySpeedDailButton/SpeedDailButton.style.tsx new file mode 100644 index 000000000..961ed633b --- /dev/null +++ b/src/components/molecules/FancySpeedDailButton/SpeedDailButton.style.tsx @@ -0,0 +1,96 @@ +import { styled } from 'styled-components'; + +import { boxShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TTheme } from '@/interface/TTheme'; + +// eslint-disable-next-line react-refresh/only-export-components +const SpeedDailButtonSize = 60; + +export const Wrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: ${SpeedDailButtonSize + 12 + 'px'}; + height: ${SpeedDailButtonSize + 12 + 'px'}; +`; + +export const SpeedDialContainer = styled.div` + position: relative; + height: ${SpeedDailButtonSize + 'px'}; + width: ${SpeedDailButtonSize + 'px'}; +`; + +export const Button = styled.button<{ $isOpen: boolean; theme?: TTheme }>` + position: relative; + background-color: ${({ theme }) => theme.colors.accent[0]}; + color: ${({ theme }) => theme.colors.secondary[0]}; + border: none; + border-radius: 50%; + cursor: pointer; + height: 100%; + width: 100%; + outline: none; + display: flex; + align-items: center; + justify-content: center; + z-index: 50; + ${boxShadow.sm} + + svg { + position: absolute; + top: 50%; + left: 50%; + width: 60%; + height: 60%; + font-size: 24px; + transform-origin: 50% 50%; + transform: ${({ $isOpen }) => ($isOpen ? 'translate(-50%, -50%) rotate(45deg)' : 'translate(-50%, -50%) rotate(0)')}; + transition: transform 0.3s ease; + } +`; + +export const Ring = styled.div<{ $isOpen: boolean; theme?: TTheme }>` + position: absolute; + height: ${SpeedDailButtonSize + 'px'}; + width: ${SpeedDailButtonSize + 'px'}; + border-bottom: 1.5px solid ${({ theme }) => theme.colors.accent[0]}; + border-top: 1.5px solid ${({ theme }) => theme.colors.accent[0]}; + border-left: solid transparent; + border-right: solid transparent; + border-radius: 50%; + z-index: 49; + top: 50%; + right: 50%; + transform: ${({ $isOpen }) => + $isOpen ? 'translate(50%, -50%) scale(1.12) rotate(125deg)' : 'translate(50%, -50%) scale(0.9) rotate(20deg)'}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + transition: + opacity 0.3s ease-in-out, + transform 0.3s ease-in-out; + pointer-events: none; +`; + +export const MenueItemWrapper = styled.div<{ theme: TTheme }>` + position: absolute; + left: 50%; + bottom: ${({ theme }) => theme.spacing.lg}; + transform: translateX(-50%); +`; + +export const MenueItemContainer = styled.div<{ $isOpen: boolean; $index: number }>` + position: absolute; + left: 50%; + border-radius: 50%; + bottom: ${({ $index }) => `calc(${($index + 1) * 60}px)`}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + transform: ${({ $isOpen }) => ($isOpen ? 'translateY(0) scale(1) translateX(-50%)' : `translateY(50px) scale(0) translateX(-50%)`)}; + transition: + transform 0.25s ease-in-out, + opacity 0.25s ease-in-out; + transition-delay: ${({ $isOpen, $index }) => ($isOpen ? 0.1 * $index : 0.1 * (2 - $index))}s; + pointer-events: ${({ $isOpen }) => ($isOpen ? 'auto' : 'none')}; + transform: translate(-50%); + height: 40px; + width: 40px; + ${boxShadow.sm}; +`; diff --git a/src/components/molecules/FancySpeedDailButton/index.ts b/src/components/molecules/FancySpeedDailButton/index.ts new file mode 100644 index 000000000..699b3cda5 --- /dev/null +++ b/src/components/molecules/FancySpeedDailButton/index.ts @@ -0,0 +1 @@ +export { default as FancySpeedDailButton } from './FancySpeedDailButton'; diff --git a/src/components/molecules/FancyTabSwitch/FancyTabSwitch.stories.tsx b/src/components/molecules/FancyTabSwitch/FancyTabSwitch.stories.tsx new file mode 100644 index 000000000..48a3efcb8 --- /dev/null +++ b/src/components/molecules/FancyTabSwitch/FancyTabSwitch.stories.tsx @@ -0,0 +1,105 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyTabSwitch from './FancyTabSwitch'; + +const meta = { + component: FancyTabSwitch, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'Dumb-Comonent: a Button that opens a SpeedDialMenue with a list of Buttons.', + }, + }, + }, + argTypes: { + wide: { + description: 'If true, the component will take up the full width of its container.', + type: { name: 'boolean' }, + }, + disabled: { + description: 'If true, the component will be disabled.', + type: { name: 'boolean' }, + }, + size: { + description: 'The size of the component.', + control: { + type: 'select', + }, + }, + iconAlign: { + description: 'The icon alignment.', + control: { + type: 'radio', + }, + }, + textColor: { + description: 'The color of the component.', + control: { + type: 'select', + }, + }, + themeType: { + description: 'The color of the component.', + control: { + type: 'select', + }, + }, + currentSelect: { + description: 'The currently selected tab.', + }, + layer: { + description: 'The layer of the component.', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + activeColor: { + description: 'The active color of the component.', + control: { + type: 'select', + }, + }, + outlined: { + description: 'If true, the component will be outlined.', + type: { name: 'boolean' }, + }, + rounded: { + description: 'The rounded of the component.', + control: { + type: 'select', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + values: [ + { itemKey: '1', label: 'Tab 1' }, + { itemKey: '2', label: 'Tab 2' }, + { itemKey: '3', label: 'Tab 3' }, + ], + + label: 'FancyTabSwitch', + currentSelect: '2', + handler: () => { + console.log('onChange'); + }, + themeType: 'primary', + wide: true, + layer: 4, + disabled: false, + }, +}; diff --git a/src/components/molecules/FancyTabSwitch/FancyTabSwitch.style.tsx b/src/components/molecules/FancyTabSwitch/FancyTabSwitch.style.tsx new file mode 100644 index 000000000..7b86fe683 --- /dev/null +++ b/src/components/molecules/FancyTabSwitch/FancyTabSwitch.style.tsx @@ -0,0 +1,55 @@ +import { styled } from 'styled-components'; + +import { borderRadius, spacingPx } from '../../../design/theme/designSizes'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { tabSwitchSizes } from '../TabSwitch/TabSwitch.style'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; +import { TSpacings } from '../../../interface/TSpacings'; + +// Define the interface for the styled-component +export interface IFancyTabSwitchStyle { + $transparent?: boolean; + $wide?: boolean; + $outlinedBackgroundStrength?: number; + $tabSpacing?: TSpacings; + $direction?: 'horizontal' | 'vertical'; + $rounded?: TBorderRadiusSizes; + $padding?: keyof typeof tabSwitchSizes; + $outlined?: boolean; + theme: TTheme; + $layer?: TLayer; + $themeType?: TThemeTypes; +} + +// ----------------------------------------------------------- // +// ---------- The main UL element for the component ---------- // +// ----------------------------------------------------------- // +// Define the styled-component for the unordered list of the tab switch +export const ULButtonSwitchList = styled.ul` + display: ${({ $wide }) => ($wide ? 'grid' : 'inline-grid')}; + grid-auto-flow: ${({ $direction }) => ($direction === 'vertical' ? 'row' : 'column')}; + grid-auto-rows: 1fr; + grid-auto-columns: 1fr; + gap: ${({ $tabSpacing }) => ($tabSpacing ? spacingPx[$tabSpacing] : '0')}; + border-radius: ${({ $rounded }) => ($rounded ? borderRadius[$rounded] : '0')}; + ${({ $wide }) => $wide && `justify-content: space-between`}; + align-items: center; + margin: 0; + padding: 0; + + // Generate the disabled style for the tab switch +`; + +// ----------------------------------- // +// ---------- Other styled ---------- // +// ----------------------------------- // +// Define the styled-component for the list item wrapper +export const ItemWrapper = styled.li` + position: relative; + height: 100%; + width: 100%; + flex: 1 0; + list-style: none; +`; diff --git a/src/components/molecules/FancyTabSwitch/FancyTabSwitch.tsx b/src/components/molecules/FancyTabSwitch/FancyTabSwitch.tsx new file mode 100644 index 000000000..27d1adc30 --- /dev/null +++ b/src/components/molecules/FancyTabSwitch/FancyTabSwitch.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { css } from 'styled-components'; + +import { borderRadius } from '../../../design/theme/designSizes'; +import Fieldset from '../Fieldset/Fieldset'; +import FancyBar from '../../atoms/FancyBox/FancyBox'; +import TabSwitch from '../TabSwitch/TabSwitch'; +import { tabSwitchSizes } from '../TabSwitch/TabSwitch.style'; +import { ITabSwitchProps } from '../TabSwitch/TabSwitch.model'; + +// Define the main FancyTabSwitch component +export default function FancyTabSwitch(props: ITabSwitchProps & { label?: string }) { + const { values, label, layer, themeType, size, disabled, outlined, rounded, wide, ...tabSwitchProps } = props; + + const clacPadding = outlined + ? parseInt(tabSwitchSizes[size || 'sm'].paddingComponent) - 1.5 + 'px' + : size + ? tabSwitchSizes[size].paddingComponent + : '0'; + + /* Generate the unordered list for the tab switch */ + return ( +
+ + + +
+ ); +} diff --git a/src/components/molecules/FancyTabSwitch/index.ts b/src/components/molecules/FancyTabSwitch/index.ts new file mode 100644 index 000000000..a9054e779 --- /dev/null +++ b/src/components/molecules/FancyTabSwitch/index.ts @@ -0,0 +1 @@ +export { default as FancyTabSwitch } from './FancyTabSwitch'; diff --git a/src/components/molecules/FancyTabSwitch/utils/generateColorDesign.ts b/src/components/molecules/FancyTabSwitch/utils/generateColorDesign.ts new file mode 100644 index 000000000..a6782aef1 --- /dev/null +++ b/src/components/molecules/FancyTabSwitch/utils/generateColorDesign.ts @@ -0,0 +1,76 @@ +import { css } from 'styled-components'; + +import { getBackgroundColor } from '../../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { tabSwitchSizes } from '../../TabSwitch/TabSwitch.style'; +import { IFancyTabSwitchStyle } from '../FancyTabSwitch.style'; +import colorTransparencyCalculator from '../../../../design/designFunctions/colorTransparencyCalculator/colorTransparencyCalculator'; +import { borderRadius } from '../../../../design/theme/designSizes'; + +const generateTransparentStyle = (props: TGenerateOutlineStyle) => { + const { $padding, theme, $rounded } = props; + + const getPaddings = $padding ? parseInt(tabSwitchSizes[$padding].paddingComponent) : 0; + const calcPadding = Math.max(0, getPaddings - 1.5) + 'px ' + (getPaddings + parseInt(borderRadius[$rounded ?? 'sm'])) + 'px'; + + return css` + overflow: hidden; + border: 1.5px solid ${getBackgroundColor({ theme, $themeType: 'primary', $layer: 4 })}; + + ${$rounded && + css` + padding: ${calcPadding}; + `} + `; +}; + +// Define the function to generate the outline style for the tab switch +type TGenerateOutlineStyle = Pick< + IFancyTabSwitchStyle, + '$outlined' | '$padding' | '$themeType' | '$rounded' | 'theme' | '$layer' | '$outlinedBackgroundStrength' +>; +const generateOutlineStyle = (props: TGenerateOutlineStyle) => { + const { $padding, $themeType, theme, $rounded, $layer = 3, $outlinedBackgroundStrength = 0.5 } = props; + + // if the theme type istransparent, generate the transparent style and return it + if ($themeType === 'transparent') return generateTransparentStyle({ $padding, $themeType, theme, $rounded }); + // get theme background color + const backgroundColor = getBackgroundColor({ theme, $themeType: $themeType ?? 'primary', $layer: $layer ?? 3 }); + + // generate the background color with a transparency of the background color + const generateSlightBackgroundColor = colorTransparencyCalculator( + getBackgroundColor({ theme, $themeType: $themeType ?? 'primary', $layer: Math.max(1, $layer - 3) }), + $outlinedBackgroundStrength + ); + + return css` + box-sizing: border-box; + background-color: ${generateSlightBackgroundColor}; + border: 1.5px solid ${backgroundColor}; + padding: ${$padding ? parseInt(tabSwitchSizes[$padding].paddingComponent) - 1.5 + 'px' : '0'}; // 1.5px is the border width + `; +}; + +// --------------------------------------------------------------------------- // +// ----------- The Main generator function to create a the square ---------- // +// --------------------------------------------------------------------------- // +type TGenerateColorDesign = Pick< + IFancyTabSwitchStyle, + '$themeType' | 'theme' | '$outlined' | '$padding' | '$rounded' | '$layer' | '$outlinedBackgroundStrength' +>; +export default function generateColorDesign(props: TGenerateColorDesign) { + const { $themeType, theme, $outlined, $padding, $rounded, $layer, $outlinedBackgroundStrength } = props; + let outlinedStyle, backgroundColor; + + // generate the outlined style if the outlined prop is true else generate only the background color + if ($outlined) { + outlinedStyle = generateOutlineStyle({ $outlined, $padding, $themeType, theme, $rounded, $layer, $outlinedBackgroundStrength }); + } else { + backgroundColor = getBackgroundColor({ theme, $themeType: $themeType ?? 'primary', $layer: $layer ?? 3 }); + } + + return css` + padding: ${$padding && $themeType !== 'transparent' ? tabSwitchSizes[$padding].paddingComponent : '0'}; + background-color: ${$themeType !== 'transparent' && backgroundColor}; + ${outlinedStyle} + `; +} diff --git a/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.model.ts b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.model.ts new file mode 100644 index 000000000..3430a5243 --- /dev/null +++ b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.model.ts @@ -0,0 +1,25 @@ +import { TThemeTypes } from '@/interface/TUiColors'; +import { tabSwitchItemSizes } from './FancyTabSwitchButton.style'; + +export interface ITabSwitchDetailsLabelIcon { + itemKey: string; + label?: string; + icon?: JSX.Element; +} + +export interface ITabSwitchDetailsChildren { + itemKey: string; + children?: React.ReactNode; +} + +export interface ITabSwitchButton { + disabled?: boolean; + selected: boolean; + onClick: (key: string) => void; + wide?: boolean; + themeType?: TThemeTypes; + iconAlign?: 'left' | 'right'; + size?: keyof typeof tabSwitchItemSizes; +} + +export type ITabSwitchButtonProps = ITabSwitchButton & ITabSwitchDetailsChildren & ITabSwitchDetailsLabelIcon; diff --git a/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.stories.tsx b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.stories.tsx new file mode 100644 index 000000000..132e5b0d7 --- /dev/null +++ b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.stories.tsx @@ -0,0 +1,95 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyTabSwitchButton from './FancyTabSwitchButton'; + +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// fix for building ... because storybook is not able to handle the default export +const HelperComponent = (props: React.ComponentProps) => ; +HelperComponent.displayName = 'FancyTabSwitchButton'; + +// Define metadata for the story +const meta = { + component: HelperComponent, + parameters: { + docs: { + description: { + component: 'SwipeUpDash is a simple horizontal line', + }, + }, + }, + // Define arguments for the story + argTypes: { + disabled: { + description: 'If true, the button will be disabled.', + type: { name: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + label: { + description: 'The text to be displayed in the button.', + type: { name: 'string' }, + }, + icon: { + description: 'The icon to be displayed in the button.', + type: { name: 'string' }, + }, + children: { + description: 'The children of the button.', + type: { name: 'string' }, + }, + itemKey: { + description: 'The key of the button.', + type: { name: 'string' }, + }, + selected: { + description: 'If true, the button will be selected.', + type: { name: 'boolean' }, + }, + onClick: { + description: 'A function that is called when the button is clicked.', + type: { name: 'function' }, + }, + wide: { + description: 'If true, the button will be wide.', + type: { name: 'boolean' }, + }, + themeType: { + description: 'The text color of the button.', + control: { type: 'select' }, + }, + iconAlign: { + description: 'The alignment of the icon.', + control: { type: 'radio' }, + }, + size: { + description: 'The size of the button.', + control: { type: 'select' }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + itemKey: '1', + label: 'label', + icon: , + selected: false, + wide: false, + themeType: 'secondary', + iconAlign: 'left', + size: 'sm', + }, +}; diff --git a/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.style.tsx b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.style.tsx new file mode 100644 index 000000000..1c573e792 --- /dev/null +++ b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.style.tsx @@ -0,0 +1,130 @@ +import { styled, css } from 'styled-components'; + +import themeStore from '../../../design/theme/themeStore/themeStore'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTypography } from '@/interface/TTypography'; +import { TTheme } from '@/interface/TTheme'; + +const getSpacingFromTheme = themeStore.getState().theme.spacing; +export const tabSwitchItemSizes = { + sm: { + fontSize: 'smText' as TTypography, + padding: getSpacingFromTheme.xs, + }, + md: { + fontSize: 'content' as TTypography, + padding: getSpacingFromTheme.sm, + }, + lg: { + fontSize: 'button' as TTypography, + padding: getSpacingFromTheme.md, + }, +}; + +// ------------------------------------------------------------------ // +// ----------- the helperfunctions for the style generate ----------- // +// ------------------------------------------------------------------ // +//generates the style from the dynamic values of the tab +interface IListButtonStyle { + $wide?: boolean; + $textColor?: TThemeTypes; + $iconAlign?: 'left' | 'right'; + theme: TTheme; + $themeType?: TThemeTypes; + $layer?: TLayer; + $size?: keyof typeof tabSwitchItemSizes; + $hasLabel?: boolean; + $hasIcon?: boolean; +} + +type TGenerateDynamicTabStyle = Pick; +const generateDynamicTabStyle = (props: TGenerateDynamicTabStyle) => { + const { $textColor = 'secondary', theme, $themeType, $layer = 0 } = props; + const darkTheme = themeStore.getState().isDarkTheme; + + //if the background not transparent give him a background/text color + if ($themeType !== 'transparent') { + return css` + color: ${darkTheme + ? getBackgroundColor({ theme, $themeType: $textColor, $layer }) + : getBackgroundColor({ theme, $themeType: $textColor, $layer })}; + `; + } else { + //when the it is transparent style it with underline + return css` + color: ${getBackgroundColor({ theme, $themeType: $textColor, $layer })}; + `; + } +}; + +//this functions hold litle childs for the label +const generateIconAlignment = (props: Pick) => { + const { $iconAlign } = props; + + const getAlignment = () => { + switch ($iconAlign) { + case 'right': + return css` + gap: ${getSpacingFromTheme.xs}; + order: 1; + `; + default: + case 'left': + return css` + gap: ${getSpacingFromTheme.xs}; + `; + } + }; + + return css` + ${getAlignment()} + i { + display: flex; + justify-content: center; + } + `; +}; + +// ------------------------------------------------------------------ // +// ------------ the main style generator for the li item ------------ // +// ------------------------------------------------------------------ // +const generateButtonStyle = (props: IListButtonStyle) => { + const { $wide, $textColor, theme, $layer, $themeType, $iconAlign, $size, $hasIcon, $hasLabel } = props; + + return css` + list-style: none; + height: 100%; + width: 100%; + display: flex; + justify-content: center; + + label { + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; + box-sizing: border-box; + align-items: center; + text-align: center; + cursor: pointer; + user-select: none; + padding: ${$wide + ? `${tabSwitchItemSizes[$size || 'sm'].padding} ${getSpacingFromTheme.md}` + : `${tabSwitchItemSizes[$size || 'sm'].padding} ${parseInt(tabSwitchItemSizes[$size || 'sm'].padding) + 7 + 'px'}`}; + //handles the dynamic values + ${generateDynamicTabStyle({ $themeType, $textColor, theme, $layer })} + // generates underlying childs in this element + ${$hasIcon && $hasLabel && generateIconAlignment({ $iconAlign })} + } + + input { + display: none; + } + `; +}; + +export const SwitchButtonStyle = styled.div` + ${generateButtonStyle} +`; diff --git a/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.tsx b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.tsx new file mode 100644 index 000000000..0697f9f07 --- /dev/null +++ b/src/components/molecules/FancyTabSwitchButton/FancyTabSwitchButton.tsx @@ -0,0 +1,53 @@ +import React, { HTMLAttributes, useId } from 'react'; + +import Typography from '../../atoms/Typography/Typography'; +import FancyContent from '../FancyContent/FancyContent'; +import { ITabSwitchButtonProps } from './FancyTabSwitchButton.model'; +import { SwitchButtonStyle } from './FancyTabSwitchButton.style'; +import { leftRightToFlex } from '../../utils/functions/leftRightToFlex'; + +// ------------------------------------------------------------------ // +// ------------- main component for the tab (li item) --------------- // +// ------------------------------------------------------------------ // +type IFancyTabSwitchButton = ITabSwitchButtonProps & HTMLAttributes; +const FancyTabSwitchButton = React.forwardRef((props, ref) => { + const { disabled, selected, onClick, wide, themeType, iconAlign, size, itemKey, label, icon, children, ...HTMLProps } = props; + + const id = useId(); + + return ( + + onClick(itemKey)} + /> + + {icon || + (label && ( + + {icon && {icon}} + {label && {label}} + + ))} + {children} + + + ); +}); + +export default FancyTabSwitchButton; diff --git a/src/components/molecules/FancyTabSwitchButton/index.ts b/src/components/molecules/FancyTabSwitchButton/index.ts new file mode 100644 index 000000000..4d9b968c3 --- /dev/null +++ b/src/components/molecules/FancyTabSwitchButton/index.ts @@ -0,0 +1 @@ +export { default as TabSwitchItem } from './FancyTabSwitchButton'; diff --git a/src/components/molecules/FancyVideoText/FancyVideoText.stories.tsx b/src/components/molecules/FancyVideoText/FancyVideoText.stories.tsx new file mode 100644 index 000000000..7b8d71645 --- /dev/null +++ b/src/components/molecules/FancyVideoText/FancyVideoText.stories.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyVideoText from './FancyVideoText'; + +const meta = { + component: FancyVideoText, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'Dumb-Comonent: A FancyVideoText component.
It is a video with text on top.', + }, + }, + }, + argTypes: { + aspectRatio: { + description: 'The aspect ratio of the component.', + }, + position: { + description: 'The position of the component.', + control: { + type: 'select', + }, + }, + src: { + description: 'The video source.', + }, + autoPlay: { + description: 'If true, the video will start playing automatically.', + type: { name: 'boolean' }, + }, + muted: { + description: 'If true, the video will be muted.', + type: { name: 'boolean' }, + }, + loop: { + description: 'If true, the video will loop.', + type: { name: 'boolean' }, + }, + controls: { + description: 'If true, the video will have controls.', + type: { name: 'boolean' }, + }, + poster: { + description: 'The video poster.', + type: { name: 'string' }, + }, + darken: { + description: 'If true, the image will be darkened.', + type: { name: 'boolean' }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + src: 'https://www.w3schools.com/html/mov_bbb.mp4', + autoPlay: true, + aspectRatio: '16/9', + position: 'top-left', + children: [

Top Right

], + }, +}; diff --git a/src/components/molecules/FancyVideoText/FancyVideoText.tsx b/src/components/molecules/FancyVideoText/FancyVideoText.tsx new file mode 100644 index 000000000..859f20d1b --- /dev/null +++ b/src/components/molecules/FancyVideoText/FancyVideoText.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import VideoAtom, { IFancyVideo } from '../../atoms/FancyVideo/FancyVideo'; +import ImageVideoOverlay, { TPositions } from '../../atoms/ImageVideoOverlay/ImageVideoOverlay'; + +// Define the props for the FancyVideoText component +interface IFancyVideoTextProps extends IFancyVideo { + position: TPositions; + children?: React.ReactNode; +} +// --------------------------------------------------------------------------- // +// ----------- The Definition for the FancyVideoText Component --------------- // +// --------------------------------------------------------------------------- // +export default function FancyVideoText(props: IFancyVideoTextProps) { + const { position, ...restProps } = props; + + return ( + // Render the ImageVideoOverlay component with the specified position and textChildren props + + {/* Render the VideoAtom component with the remaining props */} + + + ); +} diff --git a/src/components/molecules/FancyVideoText/index.ts b/src/components/molecules/FancyVideoText/index.ts new file mode 100644 index 000000000..5ee330255 --- /dev/null +++ b/src/components/molecules/FancyVideoText/index.ts @@ -0,0 +1 @@ +export { default as FancyVideoText } from './FancyVideoText'; diff --git a/src/components/molecules/Fieldset/Fieldset.stories.tsx b/src/components/molecules/Fieldset/Fieldset.stories.tsx new file mode 100644 index 000000000..172867b1a --- /dev/null +++ b/src/components/molecules/Fieldset/Fieldset.stories.tsx @@ -0,0 +1,50 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import Fieldset from './Fieldset'; + +// Define metadata for the story +const meta = { + component: Fieldset, + parameters: { + docs: { + description: { + component: 'Fieldset is only a wrapper for a group of elements. It is used to group elements in a form.', + }, + }, + }, + + // Define arguments for the story + argTypes: {}, + + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( +
+

Some text

+

Iam the Content of the Fieldset

+
+ ), + args: { + alignLabel: 'left', + label: 'This is a label', + fontVariantLegend: 'h3', + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/molecules/Fieldset/Fieldset.style.tsx b/src/components/molecules/Fieldset/Fieldset.style.tsx new file mode 100644 index 000000000..b7f770cce --- /dev/null +++ b/src/components/molecules/Fieldset/Fieldset.style.tsx @@ -0,0 +1,16 @@ +import { styled } from 'styled-components'; +import { TalignLabel } from './Fieldset'; +import { disabledStyle } from '../../../design/designFunctions/disabledStyle/disableStyle'; +import { TTheme } from '@/interface/TTheme'; + +export const StyledFieldset = styled.fieldset<{ $disabled?: boolean }>` + width: 100%; + border: none; + padding: 0; + margin: 0; + ${({ $disabled }) => $disabled && disabledStyle} +`; + +export const StyledLegend = styled.legend<{ alignLabel?: TalignLabel; theme: TTheme }>` + margin: 0 ${({ alignLabel }) => (alignLabel === 'center' ? 'auto' : '0')} ${({ theme }) => theme.spacing.xs}; +`; diff --git a/src/components/molecules/Fieldset/Fieldset.tsx b/src/components/molecules/Fieldset/Fieldset.tsx new file mode 100644 index 000000000..232e3824f --- /dev/null +++ b/src/components/molecules/Fieldset/Fieldset.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { StyledFieldset, StyledLegend } from './Fieldset.style'; +import { TTypography } from '@/interface/TTypography'; +import { Typography } from '@/components/atoms/Typography'; + +export type TalignLabel = 'left' | 'center'; + +interface IFieldset { + children?: React.ReactNode; + label?: string; + alignLabel?: TalignLabel; + fontVariantLegend?: TTypography; + disabled?: boolean; +} +export default function Fieldset(props: IFieldset) { + const { children, label, alignLabel, fontVariantLegend, disabled } = props; + + return ( + + {label && ( + + + {label} + + + )} + {children} + + ); +} diff --git a/src/components/molecules/Fieldset/index.ts b/src/components/molecules/Fieldset/index.ts new file mode 100644 index 000000000..f8cac46b5 --- /dev/null +++ b/src/components/molecules/Fieldset/index.ts @@ -0,0 +1 @@ +export { default as Fieldset } from './Fieldset'; diff --git a/src/components/molecules/Header/Header.stories.tsx b/src/components/molecules/Header/Header.stories.tsx new file mode 100644 index 000000000..e0c470f1a --- /dev/null +++ b/src/components/molecules/Header/Header.stories.tsx @@ -0,0 +1,62 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import Header from './Header'; +import { HeaderTitleWithLogo } from '../HeaderTitleWithLogo'; +import { FancySearchBar } from '../../organisms/FancySearchBar'; +import { FancyMiniProfile } from '../FancyMiniProfile'; +import { css } from 'styled-components'; + +const meta = { + component: Header, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A simple header that can be self designd with the help of the theme.', + }, + }, + }, + argTypes: {}, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const headerStyle = css` + position: fixed; + top: 10px; + width: 80%; + padding: 12px 18px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 50px; +`; + +export const Primary: Story = { + render: (args) => ( +
+
+ +
+ +
+ +
+
+ ), + args: { + themeType: 'primary', + outlined: false, + layer: 1, + outlinedBackgroundStrength: 0.5, + children: 'Header', + }, +}; diff --git a/src/components/molecules/Header/Header.style.tsx b/src/components/molecules/Header/Header.style.tsx new file mode 100644 index 000000000..87132a30b --- /dev/null +++ b/src/components/molecules/Header/Header.style.tsx @@ -0,0 +1,14 @@ +import { CSSProp, styled } from 'styled-components'; + +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import generateThemeForCard, { IgenerateThemeForCard } from '../../../design/designFunctions/generateThemeForCard/generateThemeForCard'; +import { TTheme } from '@/interface/TTheme'; + +type TStyledHeader = IStyledPrefixAndPicker & { theme: TTheme; $externalStyle?: CSSProp }; +export const StyledHeader = styled.header` + box-sizing: border-box; + display: flex; + ${({ $themeType, theme, $layer, $outlined, $outlinedBackgroundStrength }) => + generateThemeForCard({ $themeType, theme, $outlined, $layer, $outlinedBackgroundStrength })} + ${({ $externalStyle }) => $externalStyle} +`; diff --git a/src/components/molecules/Header/Header.tsx b/src/components/molecules/Header/Header.tsx new file mode 100644 index 000000000..503dc5573 --- /dev/null +++ b/src/components/molecules/Header/Header.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { CSSProp } from 'styled-components'; + +import { IgenerateThemeForCard } from '../../../design/designFunctions/generateThemeForCard/generateThemeForCard'; +import { StyledHeader } from './Header.style'; + +type THeaderProps = IgenerateThemeForCard & { + children?: React.ReactNode; + externalStyle?: CSSProp; +} & React.HTMLAttributes; + +export default function Header(props: THeaderProps) { + const { themeType, layer, outlined, outlinedBackgroundStrength, children, externalStyle } = props; + return ( + + {children} + + ); +} diff --git a/src/components/molecules/Header/index.ts b/src/components/molecules/Header/index.ts new file mode 100644 index 000000000..5653319de --- /dev/null +++ b/src/components/molecules/Header/index.ts @@ -0,0 +1 @@ +export { default as Header } from './Header'; diff --git a/src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.stories.tsx b/src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.stories.tsx new file mode 100644 index 000000000..b6197ffd4 --- /dev/null +++ b/src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.stories.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import HeaderTitleWithLogo from './HeaderTitleWithLogo'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +const meta = { + component: HeaderTitleWithLogo, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A simple HeaderTitleWithLogo component that can be used to provied a title and a Logo.', + }, + }, + }, + argTypes: { + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + linkTo: { + description: 'The link that redirects to a page', + control: { + type: 'text', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + title: 'Title', + logo: , + }, +}; diff --git a/src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.tsx b/src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.tsx new file mode 100644 index 000000000..2f896b73a --- /dev/null +++ b/src/components/molecules/HeaderTitleWithLogo/HeaderTitleWithLogo.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { styled } from 'styled-components'; + +import Typography from '../../atoms/Typography/Typography'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getTextColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +const Wrapper = styled.div` + position: relative; + display: flex; + align-items: center; + height: 100%; + width: 100%; + box-sizing: border-box; +`; + +const LogoWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +const StyledHeading = styled.a<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + display: flex; + gap: ${({ theme }) => theme.spacing.sm}; + align-items: center; + word-break: keep-all; + text-decoration: none; + color: ${({ theme, $themeType = 'secondary', $layer }) => getTextColor({ theme, $themeType, $textLayer: $layer })}; +`; + +interface IHeaderTitleWithLogo { + title?: React.ReactNode | string; + logo?: React.ReactNode; + linkTo?: string; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function HeaderTitleWithLogo(props: IHeaderTitleWithLogo) { + const { title, logo, linkTo, themeType, layer } = { ...defaultProps, ...props }; + + return ( + + + {logo && {logo}} + {title && ( + + {title} + + )} + + + ); +} + +const defaultProps: IHeaderTitleWithLogo = { + title: '', + linkTo: '/', +}; diff --git a/src/components/molecules/HeaderTitleWithLogo/index.ts b/src/components/molecules/HeaderTitleWithLogo/index.ts new file mode 100644 index 000000000..1129d0ae2 --- /dev/null +++ b/src/components/molecules/HeaderTitleWithLogo/index.ts @@ -0,0 +1 @@ +export { default as HeaderTitleWithLogo } from './HeaderTitleWithLogo'; diff --git a/src/components/molecules/InfoCard/InfoCard.stories.tsx b/src/components/molecules/InfoCard/InfoCard.stories.tsx new file mode 100644 index 000000000..80c0a7822 --- /dev/null +++ b/src/components/molecules/InfoCard/InfoCard.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import InfoCard from './InfoCard'; + +const meta = { + component: InfoCard, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: Thats only the Style of the InfoCard, it accepts all kind of children.', + }, + }, + }, + argTypes: { + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + outlined: { + description: 'The component has a outline style', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + outlinedBackgroundStrength: { + description: 'The background strength of the outline', + control: { + type: 'range', + min: 0, + max: 1, + step: 0.01, + }, + defaultValue: { + summary: 10, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + children: 'This is a InfoCard', + themeType: 'primary', + layer: 1, + }, +}; diff --git a/src/components/molecules/InfoCard/InfoCard.tsx b/src/components/molecules/InfoCard/InfoCard.tsx new file mode 100644 index 000000000..dbabce193 --- /dev/null +++ b/src/components/molecules/InfoCard/InfoCard.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import FancyBox from '../../atoms/FancyBox/FancyBox'; +import { generateInfoCardStyle } from './InfoCrad.style'; + +type TInfoCardProps = React.ComponentProps & { + size?: 'sm' | 'md' | 'lg'; +}; + +export default function InfoCard(props: TInfoCardProps) { + const { size, children, ...fancyBoxProps } = props; + + const BoxStyle = generateInfoCardStyle(size || 'md'); + + return ( + + {children} + + ); +} diff --git a/src/components/molecules/InfoCard/InfoCrad.style.tsx b/src/components/molecules/InfoCard/InfoCrad.style.tsx new file mode 100644 index 000000000..b41dd9bf7 --- /dev/null +++ b/src/components/molecules/InfoCard/InfoCrad.style.tsx @@ -0,0 +1,28 @@ +import { css } from 'styled-components'; + +import { TSizes } from '../../../interface/TComponentSizes'; +import themeStore from '@/design/theme/themeStore/themeStore'; + +const getTheme = themeStore.getState().theme; +const sizes = { + sm: { + borderRadius: getTheme.borderRadius.sm, + padding: getTheme.spacing.sm, + }, + md: { + borderRadius: getTheme.borderRadius.md, + padding: getTheme.spacing.md, + }, + lg: { + borderRadius: getTheme.borderRadius.lg, + padding: getTheme.spacing.lg, + }, +}; + +export const generateInfoCardStyle = (size: TSizes) => { + return css` + width: 100%; + border-radius: ${sizes[size].borderRadius}; + padding: ${sizes[size].padding}; + `; +}; diff --git a/src/components/molecules/InfoCard/index.ts b/src/components/molecules/InfoCard/index.ts new file mode 100644 index 000000000..d2d967091 --- /dev/null +++ b/src/components/molecules/InfoCard/index.ts @@ -0,0 +1 @@ +export { default as InfoCard } from './InfoCard'; diff --git a/src/components/molecules/InputWrapper/InputWrapper.stories.tsx b/src/components/molecules/InputWrapper/InputWrapper.stories.tsx new file mode 100644 index 000000000..8e2fe23b6 --- /dev/null +++ b/src/components/molecules/InputWrapper/InputWrapper.stories.tsx @@ -0,0 +1,103 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import InputWrapper from './InputWrapper'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +const meta = { + component: InputWrapper, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The InputWrapper component that can be used to wrap a input component via the InputElement.
', + }, + }, + }, + argTypes: { + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + isActive: { + description: 'The state of the component', + control: { + type: 'boolean', + }, + }, + disabled: { + description: 'The state of the component', + control: { + type: 'boolean', + }, + }, + align: { + description: 'The align of the component', + control: { + type: 'select', + }, + }, + label: { + description: 'The label of the component', + control: { + type: 'text', + }, + }, + errorMessage: { + description: 'The errorMessage of the component', + control: { + type: 'text', + }, + }, + value: { + description: 'The value of the component', + control: { + type: 'text', + }, + }, + autoWidth: { + description: 'The autoWidth of the component', + control: { + type: 'boolean', + }, + }, + underline: { + description: 'The underline of the component', + control: { + type: 'boolean', + }, + }, + placeholder: { + description: 'The placeholder of the component', + control: { + type: 'text', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + id: 'id', + label: 'Label', + align: 'left', + icon: , + }, +}; diff --git a/src/components/molecules/InputWrapper/InputWrapper.style.tsx b/src/components/molecules/InputWrapper/InputWrapper.style.tsx new file mode 100644 index 000000000..7119f6920 --- /dev/null +++ b/src/components/molecules/InputWrapper/InputWrapper.style.tsx @@ -0,0 +1,49 @@ +import { styled, css } from 'styled-components'; + +import { disabledStyle } from '../../../design/designFunctions/disabledStyle/disableStyle'; +import { fontSize } from '../../../design/theme/designSizes'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +export const StyledInputWrapper = styled.div<{ disabled?: boolean; $autoWidth?: boolean }>` + box-sizing: border-box; + position: relative; + display: grid; + grid-template-columns: auto 1fr auto; + grid-template-rows: 1fr auto; + width: ${({ $autoWidth }) => ($autoWidth ? 'auto' : '100%')}; + + ${({ disabled }) => (disabled ? disabledStyle : '')}; +`; + +//the style for the error message +export const ErrorMessage = styled.p<{ theme: TTheme }>` + grid-column: 2; + grid-row: 2; + margin: 0; + margin-top: ${({ theme }) => theme.spacing.xxs}; + font-size: ${fontSize.extrasm}; + color: ${({ theme }) => theme.colors.error[0]}; +`; + +//the input/label/underline are all wrapped in thid container +export const InputContainer = styled.div<{ $givePadding: boolean; theme: TTheme; $themeType: TThemeTypes; $layer: TLayer }>` + width: 100%; + grid-column: 2/3; + ${({ $givePadding, theme }) => + $givePadding && + css` + padding-top: ${parseFloat(theme.spacing.lg) + 2 + 'px'}; + `}; + position: relative; + + input { + padding: 0px 0px ${({ theme }) => parseFloat(theme.spacing.xs) + 2 + 'px'}; + } + + svg { + color: ${({ theme, $themeType, $layer }) => getBackgroundColor({ theme, $themeType, $layer })}; + } +`; diff --git a/src/components/molecules/InputWrapper/InputWrapper.tsx b/src/components/molecules/InputWrapper/InputWrapper.tsx new file mode 100644 index 000000000..6d7a9e14d --- /dev/null +++ b/src/components/molecules/InputWrapper/InputWrapper.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import { css } from 'styled-components'; + +import { ErrorMessage, StyledInputWrapper, InputContainer } from './InputWrapper.style'; +import FancyInputUnderline from '../../atoms/InputUnderline/InputUnderline'; +import FancySVGAtom from '../../atoms/FancySVGAtom/FancySVGAtom'; +import { AnimatedInputLabel } from '../../atoms/AnimatedLabel/AnimatedInputLabel'; +import { TRawInputAlign } from '../../atoms/RawInput/RawInput'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import calcColorState from '../../../design/designFunctions/calcColorState/calcColorState'; +import themeStore from '../../../design/theme/themeStore/themeStore'; + +// Define the styles for the icon +const iconStyle = css` + margin-right: ${parseFloat(themeStore.getState().theme.spacing.xs) + 2 + 'px'}; + margin-bottom: 2px; + transition: 0.5s; + align-self: flex-end; +`; + +// Define the interface for the color state + +// Define the props for the InputWrapper component +export type IInputWrapperUserInputProps = Omit; +export interface IInputWrapper { + id: string; + isActive?: boolean; + label?: string; + disabled?: boolean; + InputElement?: React.ReactNode; + errorMessage?: string; + icon?: JSX.Element; + value?: string | number | readonly string[] | undefined; + themeType?: TThemeTypes; + layer?: TLayer; + align?: TRawInputAlign; + autoWidth?: boolean; + underline?: boolean; + placeholder?: string; +} +// --------------------------------------------------------------------------- // +// ------ The Wrapper for the inputs that give him some extra features ------ // +// ------------------ like a Label icon errormessage ------------------------- // +export default function InputWrapper(props: IInputWrapper) { + const { + id, + value, + isActive, + disabled, + InputElement, + errorMessage, + icon, + label, + align, + underline = true, + autoWidth, + placeholder, + layer = 4, + themeType = 'secondary', + } = props; + const [isInitial, setIsInitial] = useState(false); + + // Calculate the color state for the label and underline + const colorStateLabel = calcColorState({ type: 'label', isActive, errorMessage, value, placeholder }); + const colorStateUnderline = calcColorState({ type: 'underline', isActive, errorMessage, value, placeholder }); + + // Set the initial state of the input field + useEffect(() => { + if (isActive) setIsInitial(true); + }, [isActive]); + + // Render the InputWrapper component with the appropriate props + return ( + + {icon && ( + + {icon} + + )} + + {InputElement} + {/* Render the label for the input field if a label prop exists */} + {label && ( + + {label} + + )} + {/* Render the underline for the input field if the underline prop is true */} + {underline && ( + + )} + + {/* Render the error message if an errorMessage prop exists */} + {errorMessage && {errorMessage}} + + ); +} diff --git a/src/components/molecules/InputWrapper/index.ts b/src/components/molecules/InputWrapper/index.ts new file mode 100644 index 000000000..7298a6056 --- /dev/null +++ b/src/components/molecules/InputWrapper/index.ts @@ -0,0 +1 @@ +export { default as InputWrapper } from './InputWrapper'; diff --git a/src/components/molecules/MenuList/MenuList.stories.tsx b/src/components/molecules/MenuList/MenuList.stories.tsx new file mode 100644 index 000000000..5144e0961 --- /dev/null +++ b/src/components/molecules/MenuList/MenuList.stories.tsx @@ -0,0 +1,60 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import MenuList from './MenuList'; + +// Define metadata for the story +const meta = { + title: 'components/atoms/MenuList', + component: MenuList, + parameters: { + docs: { + description: { + component: 'Template: The FancyMenueItem is a template for a finished Item it used by the FancyMenuList', + }, + }, + }, + // Define arguments for the story + argTypes: { + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the button hover effect', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '3', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + +

Some Content

+ {args.children} +
+ ), + args: {}, +}; diff --git a/src/components/molecules/MenuList/MenuList.style.tsx b/src/components/molecules/MenuList/MenuList.style.tsx new file mode 100644 index 000000000..7e0e6b6ad --- /dev/null +++ b/src/components/molecules/MenuList/MenuList.style.tsx @@ -0,0 +1,18 @@ +import { styled } from 'styled-components'; + +import { MenuListProps } from './MenuList'; +import IStyledPrefixAndOmitter from '../../../interface/IStyledPrefixAndOmiter.model'; +import generateThemeForCard from '../../../design/designFunctions/generateThemeForCard/generateThemeForCard'; +import { TTheme } from '@/interface/TTheme'; + +type StyledMenuProps = IStyledPrefixAndOmitter & { theme: TTheme }; + +export const MenuContainer = styled.div` + display: flex; + flex-direction: column; + padding: 8px 0; + ${({ theme, $themeType, $layer, $outlined }) => + generateThemeForCard({ theme, $themeType: $themeType ?? 'primary', $layer: $layer ?? 2, $outlined, $outlinedBackgroundStrength: 1 })} + box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); + border-radius: ${({ theme }) => theme.borderRadius.md}; +`; diff --git a/src/components/molecules/MenuList/MenuList.tsx b/src/components/molecules/MenuList/MenuList.tsx new file mode 100644 index 000000000..a98fa36bd --- /dev/null +++ b/src/components/molecules/MenuList/MenuList.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { MenuContainer } from './MenuList.style'; + +export interface MenuListProps { + children?: React.ReactNode; + themeType?: TThemeTypes; + layer?: TLayer; + outlined?: boolean; +} +// --------------------------------------------------------------------------- // +// ---------------- A simple MenuList that can have any childs --------------- // +// --------------------------------------------------------------------------- // +export default function MenuList(props: MenuListProps) { + const { children, themeType, layer, outlined } = props; + return ( + + {children} + + ); +} diff --git a/src/components/molecules/Modal/Modal.stories.tsx b/src/components/molecules/Modal/Modal.stories.tsx new file mode 100644 index 000000000..bdcb853dc --- /dev/null +++ b/src/components/molecules/Modal/Modal.stories.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import Modal from './Modal'; + +const meta = { + component: Modal, + parameters: { + docs: { + description: { + component: 'Smart-Comonent: A Modal component to show a Dialog with a Backdrop', + }, + }, + }, + argTypes: { + id: { + description: 'The id of the component', + control: { + type: 'text', + }, + }, + isOpen: { + description: 'The status of the component', + control: { + type: 'select', + }, + }, + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + + onClose: { + description: 'The closeModal of the component', + control: { + type: 'function', + }, + }, + backDrop: { + description: 'Is the backdrop visible', + control: { + type: 'boolean', + }, + }, + isCloseable: { + description: 'The modal is closeable', + control: { + type: 'boolean', + }, + defaultValue: { + summary: true, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + isOpen: true, + themeType: 'primary', + layer: 0, + id: 'Modal', + onClose: () => { + console.log('closeModal'); + }, + }, +}; diff --git a/src/components/molecules/Modal/Modal.style.tsx b/src/components/molecules/Modal/Modal.style.tsx new file mode 100644 index 000000000..3e7ff8f92 --- /dev/null +++ b/src/components/molecules/Modal/Modal.style.tsx @@ -0,0 +1,15 @@ +import { styled } from 'styled-components'; + +export const HeadLine = styled.div` + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; +`; + +export const CloseButtonWrapper = styled.div` + position: absolute; + top: 2px; + right: 0; +`; diff --git a/src/components/molecules/Modal/Modal.tsx b/src/components/molecules/Modal/Modal.tsx new file mode 100644 index 000000000..bdca5587c --- /dev/null +++ b/src/components/molecules/Modal/Modal.tsx @@ -0,0 +1,46 @@ +import React, { ReactNode, useEffect, useState } from 'react'; + +import SimpleDialog from '../../atoms/SimpleDialog/SimpleDialog'; +import BackDrop from '../../atoms/BackDrop/BackDrop'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +// --------------------------------------------------------------------------- // +// ------ The main Modal Component to comstomize the Head/Bottomline ------- // +// --------------------------------------------------------------------------- // +export interface IModal { + id?: string; + children?: ReactNode; + isOpen: boolean; + onClose?: (id: string) => void; + isCloseable?: boolean; + backDrop?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function Modal(props: IModal) { + const { id, children, isOpen, onClose, isCloseable, themeType, layer, backDrop = true } = props; + const [modalVisible, setModalVisible] = useState(false); + + // close the modal when the user clicks on the backdrop + const closeModalHanlder = () => { + if (isCloseable === false) return; + //if a components needs controle from outside + if (onClose && id) onClose(id); + setModalVisible(false); + }; + + useEffect(() => { + if (isOpen) setModalVisible(true); + else setModalVisible(false); + }, [isOpen]); + + return ( + <> + + {children} + + {backDrop && } + + ); +} diff --git a/src/components/molecules/Modal/index.ts b/src/components/molecules/Modal/index.ts new file mode 100644 index 000000000..c6b35681c --- /dev/null +++ b/src/components/molecules/Modal/index.ts @@ -0,0 +1 @@ +export { default as Modal } from './Modal'; diff --git a/src/components/molecules/MonthWithDays/IDisableDateSettings.model.ts b/src/components/molecules/MonthWithDays/IDisableDateSettings.model.ts new file mode 100644 index 000000000..1b6152d26 --- /dev/null +++ b/src/components/molecules/MonthWithDays/IDisableDateSettings.model.ts @@ -0,0 +1,7 @@ +export type IWeekDays = 0 | 1 | 2 | 3 | 4 | 5 | 6; + +export interface IDisabledDateSettings { + disablePastDates?: boolean; + disableWeekends?: boolean; + disabledWeekdays?: IWeekDays[]; +} diff --git a/src/components/molecules/MonthWithDays/IExternalMonthWithDays.model.ts b/src/components/molecules/MonthWithDays/IExternalMonthWithDays.model.ts new file mode 100644 index 000000000..77387d4ee --- /dev/null +++ b/src/components/molecules/MonthWithDays/IExternalMonthWithDays.model.ts @@ -0,0 +1,17 @@ +import { IAvailableDot } from '../../atoms/AvilableDot/AvailableDot'; + +export type IDateWithExternalState = { + date: number; + isAvilable: IAvailableDot; +}; + +export interface IExternalMonthWithDays { + monthIdx: number; + dates?: IDateWithExternalState[]; +} + +interface IExternalYearWithMonths { + [key: number]: IExternalMonthWithDays[]; +} + +export default IExternalYearWithMonths; diff --git a/src/components/molecules/MonthWithDays/MonthWithDays.stories.tsx b/src/components/molecules/MonthWithDays/MonthWithDays.stories.tsx new file mode 100644 index 000000000..14b68c1bd --- /dev/null +++ b/src/components/molecules/MonthWithDays/MonthWithDays.stories.tsx @@ -0,0 +1,111 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import MonthWithDays from './MonthWithDays'; + +const meta = { + component: MonthWithDays, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: Generate a month with days, which can be used in a calendar.
', + }, + }, + }, + argTypes: { + themeType: { + description: 'The themeType for the Modal', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer for the Modal', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + monthIdx: { + description: 'The month index', + control: { + type: 'number', + }, + }, + year: { + description: 'The year', + control: { + type: 'number', + }, + }, + selectedDates: { + description: 'The selected dates', + control: { + type: 'object', + }, + }, + isRangePicking: { + description: 'The range picking', + control: { + type: 'boolean', + }, + }, + disabledDateSetting: { + description: 'The disabled date setting', + control: { + type: 'object', + }, + }, + externalMonthWithDays: { + description: 'The external month with days setting', + control: { + type: 'object', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + year: 2021, + monthIdx: 0, + isRangePicking: false, + themeType: 'primary', + layer: 1, + selectedDates: [new Date(2021, 0, 1), undefined], + handleDateClick: () => { + console.log('handleDateClick'); + }, + disabledDateSetting: { + disabledWeekdays: [1, 6], + disableWeekends: true, + disablePastDates: false, + }, + externalMonthWithDays: { + monthIdx: 0, + dates: [ + { + date: 0, + isAvilable: 'partially', + }, + { + date: 5, + isAvilable: 'not', + }, + { + date: 7, + isAvilable: 'transparent', + }, + ], + }, + }, +}; diff --git a/src/components/molecules/MonthWithDays/MonthWithDays.style.tsx b/src/components/molecules/MonthWithDays/MonthWithDays.style.tsx new file mode 100644 index 000000000..2adc6ddff --- /dev/null +++ b/src/components/molecules/MonthWithDays/MonthWithDays.style.tsx @@ -0,0 +1,12 @@ +import { styled } from 'styled-components'; + +export const DaysContainer = styled.div` + display: grid; + grid-template-columns: repeat(7, 1fr); +`; + +export const DateNumber = styled.span` + text-align: right; + cursor: pointer; + box-sizing: border-box; +`; diff --git a/src/components/molecules/MonthWithDays/MonthWithDays.tsx b/src/components/molecules/MonthWithDays/MonthWithDays.tsx new file mode 100644 index 000000000..27c1e22fc --- /dev/null +++ b/src/components/molecules/MonthWithDays/MonthWithDays.tsx @@ -0,0 +1,97 @@ +import React, { useEffect, useMemo, useState } from 'react'; + +import DateNumberWithStatus from '../DateNumberWithStatus/DateNumberWithStatus'; +import { DateNumber, DaysContainer } from './MonthWithDays.style'; +import { IDateArray } from '../RangeCalendar/IDateArray.model'; +import { IDisabledDateSettings } from './IDisableDateSettings.model'; +import Typography from '../../atoms/Typography/Typography'; +import Day from './day.model'; +import createDaysOfMonth from './helperFunctions/createDaysOfMonth'; +import { IDateWithExternalState, IExternalMonthWithDays } from './IExternalMonthWithDays.model'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +const getFirstDayOfMonth = (month: number, year: number): number => { + return new Date(year, month - 1, 1).getDay() || 7; +}; + +const getDaysInMonth = (month: number, year: number): number => { + return new Date(year, month, 0).getDate(); +}; + +interface IMonthWithDays { + monthIdx: number; + year: number; + handleDateClick?: (day: Day, monthIdx: number) => void; + isRangePicking?: boolean; + selectedDates: IDateArray; + externalMonthWithDays?: IExternalMonthWithDays; + disabledDateSetting?: IDisabledDateSettings; + themeType?: TThemeTypes; + layer?: TLayer; +} +// --------------------------------------------------------------------------- // +// --------- This Component generates a single month with the dates ---------- // +// --------------------------------------------------------------------------- // +export default function MonthWithDays(props: IMonthWithDays) { + const { monthIdx, year, handleDateClick, selectedDates, isRangePicking, disabledDateSetting, externalMonthWithDays, layer, themeType } = + props; + const [monthDays, setMonthDays] = useState([]); + + // Create the days of the month and memoize them + const month = useMemo(() => { + const month = { + name: new Date(0, monthIdx + 1, 0).toLocaleString('default', { month: 'long' }), + days: createDaysOfMonth({ + monthIdx, + year, + selectedDates, + disabledDateSetting, + isRangePicking, + externalMonthWithDays: monthDays, + }), + }; + return month; + }, [monthIdx, selectedDates, isRangePicking, monthDays, year, disabledDateSetting]); + + useEffect(() => { + if (externalMonthWithDays) { + // create a array for each month of a year and fill it with the external state + const daysOfMonth = Array.from({ length: getDaysInMonth(monthIdx + 1, year) }); + externalMonthWithDays?.dates?.forEach((date) => { + daysOfMonth[date.date] = date; + }); + + setMonthDays(daysOfMonth as IDateWithExternalState[]); + } + }, [externalMonthWithDays, year, monthIdx]); + + return ( +
+ + {month.name} + + + {/* Generate the empty spaces for the start of the month */} + {Array.from({ length: getFirstDayOfMonth(monthIdx + 1, year) - 1 }, (_, i) => ( + + ))} + {/* Generate the days of the month */} + {month.days.map((day) => ( + handleDateClick && handleDateClick(day, monthIdx)} + /> + ))} + +
+ ); +} diff --git a/src/components/molecules/MonthWithDays/day.model.ts b/src/components/molecules/MonthWithDays/day.model.ts new file mode 100644 index 000000000..fb91439f6 --- /dev/null +++ b/src/components/molecules/MonthWithDays/day.model.ts @@ -0,0 +1,12 @@ +import { IRange } from '../../atoms/DateNumberAtom/DateNumberAtom'; +import { IAvailableDot } from '../../atoms/AvilableDot/AvailableDot'; + +interface Day { + number: number; + isSelected: boolean; + range: IRange; + disabled: boolean; + isAvilable?: IAvailableDot; +} + +export default Day; diff --git a/src/components/molecules/MonthWithDays/helperFunctions/createDayFunction.ts b/src/components/molecules/MonthWithDays/helperFunctions/createDayFunction.ts new file mode 100644 index 000000000..3530df52d --- /dev/null +++ b/src/components/molecules/MonthWithDays/helperFunctions/createDayFunction.ts @@ -0,0 +1,65 @@ +import { IDisabledDateSettings, IWeekDays } from '../IDisableDateSettings.model'; +import { IDateWithExternalState } from '../IExternalMonthWithDays.model'; +import Day from '../day.model'; +import { IDateArray } from '../../RangeCalendar/IDateArray.model'; + +// 0 - Sunday, 1 - Monday, 2 - Tuesday, 3 - Wednesday, 4 - Thursday, 5 - Friday, 6 - Saturday +// this function disables dates based on the following parameters: +const disableDate = (date: Date, disabledDateSetting?: IDisabledDateSettings): boolean => { + const { disablePastDates = false, disableWeekends = false, disabledWeekdays = [] } = disabledDateSetting || {}; + + const dateDay = date.getDay() as IWeekDays; + + const isWeekend = date.getDay() === 0 || date.getDay() === 6; + const isPast = date.getTime() < Date.now(); + const isDisabledDay = disabledWeekdays?.includes(dateDay); + const disableSpecificDates = + (disablePastDates ? isPast : false) || (disableWeekends ? isWeekend : false) || (disabledWeekdays ? !!isDisabledDay : false); + + return disableSpecificDates; +}; + +// --------------------------------------------------------------------------- // +// ----this function creates a day object based on the following parameters--- // +// --------------------------------------------------------------------------- // +interface ICreateDay { + dayNumber: number; + month: number; + year: number; + isRangePicking?: boolean; + selectedDates: IDateArray; + disabledDateSetting?: IDisabledDateSettings; + externalDate?: IDateWithExternalState; +} +const createDay = (props: ICreateDay): Day => { + const { dayNumber, month, year, selectedDates, disabledDateSetting, isRangePicking, externalDate } = props; + + const date = new Date(year, month, dayNumber); + let isStart, isEnd, isInRange; + + // if isRangePicking is true, pick a date range (start and end date) + if (isRangePicking && Array.isArray(selectedDates)) { + const validRange = selectedDates[0] && selectedDates[1] ? true : false; + const isRangeStart = selectedDates[0] ? true : false; + const isRangeEnd = selectedDates[1] ? true : false; + + isInRange = validRange && selectedDates[0]! <= date && date <= selectedDates[1]!; + isStart = isRangeStart && selectedDates[0]!.getTime() === date.getTime(); + isEnd = isRangeEnd && selectedDates[1]!.getTime() === date.getTime(); + } + + // this function disables the date + const isDateDisabled = disableDate(date, disabledDateSetting); + + return { + number: dayNumber, + disabled: isDateDisabled, + isAvilable: externalDate?.isAvilable, + isSelected: Array.isArray(selectedDates) + ? selectedDates.some((selectedDate) => selectedDate && selectedDate.getTime() === date.getTime()) + : selectedDates.getTime() === date.getTime(), + range: { start: isStart, end: isEnd, inRange: isInRange }, + }; +}; + +export default createDay; diff --git a/src/components/molecules/MonthWithDays/helperFunctions/createDaysOfMonth.ts b/src/components/molecules/MonthWithDays/helperFunctions/createDaysOfMonth.ts new file mode 100644 index 000000000..8ed526254 --- /dev/null +++ b/src/components/molecules/MonthWithDays/helperFunctions/createDaysOfMonth.ts @@ -0,0 +1,40 @@ +import createDay from './createDayFunction'; +import Day from '../day.model'; +import { IDateArray } from '../../RangeCalendar/IDateArray.model'; +import { IDisabledDateSettings } from '../IDisableDateSettings.model'; +import { IDateWithExternalState } from '../IExternalMonthWithDays.model'; + +const getDaysInMonth = (month: number, year: number): number => { + return new Date(year, month, 0).getDate(); +}; + +// --------------------------------------------------------------------------- // +// --------- This function creates the days for the specific month ----------- // +// --------------------------------------------------------------------------- // + +interface ICreateDaysOfMonth { + monthIdx: number; + year: number; + selectedDates: IDateArray; + disabledDateSetting?: IDisabledDateSettings; + isRangePicking?: boolean; + externalMonthWithDays?: IDateWithExternalState[]; +} +const createDaysOfMonth = (props: ICreateDaysOfMonth): Day[] => { + const { monthIdx, year, selectedDates, disabledDateSetting, isRangePicking, externalMonthWithDays } = props; + + const MonthDays = Array.from({ length: getDaysInMonth(monthIdx + 1, year) }, (_, j) => + createDay({ + dayNumber: j + 1, + month: monthIdx, + year: year, + selectedDates, + disabledDateSetting, + isRangePicking, + externalDate: externalMonthWithDays![j], + }) + ); + return MonthDays; +}; + +export default createDaysOfMonth; diff --git a/src/components/molecules/MonthWithDays/index.ts b/src/components/molecules/MonthWithDays/index.ts new file mode 100644 index 000000000..49e8c2365 --- /dev/null +++ b/src/components/molecules/MonthWithDays/index.ts @@ -0,0 +1 @@ +export { default as MonthWithDays } from './MonthWithDays'; diff --git a/src/components/molecules/NumberInput/NumberInput.stories.tsx b/src/components/molecules/NumberInput/NumberInput.stories.tsx new file mode 100644 index 000000000..b3da31aed --- /dev/null +++ b/src/components/molecules/NumberInput/NumberInput.stories.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import NumberInput from './NumberInput'; + +const meta = { + component: NumberInput, + parameters: { + docs: { + description: { + component: 'Smart-Comonent: A Modal component to show a Dialog with a Backdrop', + }, + }, + }, + argTypes: { + autoWidth: { + description: 'If true, the width of the input will be automatically adjusted to fit the content.', + type: { name: 'boolean' }, + }, + active: { + description: 'If true, the input will be highlighted.', + type: { name: 'boolean' }, + }, + align: { + description: 'The alignment of the text.', + type: { name: 'string' }, + }, + step: { + description: 'The step to increment or decrement the value.', + type: { name: 'number' }, + }, + activeHandler: { + description: 'A function to handle the active state of the input.', + type: { name: 'function' }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + autoWidth: false, + active: false, + align: 'left', + step: 1, + }, +}; diff --git a/src/components/molecules/NumberInput/NumberInput.styled.tsx b/src/components/molecules/NumberInput/NumberInput.styled.tsx new file mode 100644 index 000000000..847902dfb --- /dev/null +++ b/src/components/molecules/NumberInput/NumberInput.styled.tsx @@ -0,0 +1,27 @@ +import { styled } from 'styled-components'; +import RawInput from '../../atoms/RawInput/RawInput'; +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { INumberInput } from './NumberInput'; + +type IStyledNumberInput = IStyledPrefixAndPicker; +const StyledNumberInput = styled(RawInput)` + box-sizing: border-box; + border-radius: 0; + width: ${({ $width }) => ($width ? $width : '2ch')}; + background-color: transparent; + border: none; + transition: 0.3s; + transition-timing-function: cubic-bezier(0.46, 0.03, 0.52, 0.96); + outline: none; + //the focus animation for the underline + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + // hide the arrows on the input for firefox + -moz-appearance: textfield; +`; + +export default StyledNumberInput; diff --git a/src/components/molecules/NumberInput/NumberInput.tsx b/src/components/molecules/NumberInput/NumberInput.tsx new file mode 100644 index 000000000..4373561c9 --- /dev/null +++ b/src/components/molecules/NumberInput/NumberInput.tsx @@ -0,0 +1,89 @@ +import React, { InputHTMLAttributes, useState, useEffect, KeyboardEvent, ChangeEvent } from 'react'; +import StyledNumberInput from './NumberInput.styled'; +import { TRawInputAlign } from '../../atoms/RawInput/RawInput'; + +export interface INumberInput extends InputHTMLAttributes { + autoWidth?: boolean; + active?: boolean; + align?: TRawInputAlign; + step?: number; + activeHandler?: (value: boolean) => void; +} + +export default function NumberInput(props: INumberInput) { + const { value, onChange, activeHandler, align, id, autoWidth, max, min, step = 1, ...moreHTMLProps } = props; + const [inputValue, setInputValue] = useState(value ? value.toString() : null); + + // Set the initial value + useEffect(() => { + if (value !== undefined) { + setInputValue(value.toString()); + } + }, [value]); + + // Handle the change of the input value + const handleChange = (e: ChangeEvent) => { + const newValue = e.target.value; + // Remove all non-numeric characters + const sanitizedValue = newValue.replace(/[^\d]/g, ''); + updateValue(sanitizedValue, e); + }; + + // Handle the arrow up and down keys to increase or decrease the value + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + + const currentValue = Number(inputValue) || 0; + let newValue = e.key === 'ArrowUp' ? currentValue + step : currentValue - step; + + // Round to the number of decimal places in `step` + const numDecimalPlaces = step.toString().split('.')[1]?.length || 0; + newValue = Number(newValue.toFixed(numDecimalPlaces)); + + // Update the fake event object to pass to the onChange handler + const fakeEvent = { + target: { + value: newValue.toString(), + }, + } as ChangeEvent; + + updateValue(newValue.toString(), fakeEvent); + } + }; + + // Update the value and call the onChange handler + const updateValue = (newValue: string, e?: ChangeEvent) => { + let adjustedValue = newValue; + // Check if the value is within the min and max range + if (max !== undefined && Number(newValue) > Number(max)) { + adjustedValue = max.toString(); + } else if (min !== undefined && Number(newValue) < Number(min)) { + adjustedValue = min.toString(); + } + + // Update the state + setInputValue(adjustedValue); + + // Call the onChange handler if provided + if (onChange && e) { + e.target.value = adjustedValue; + onChange(e); + } + }; + + return ( + activeHandler && activeHandler(true)} + onBlur={() => activeHandler && activeHandler(false)} + $width={autoWidth ? (inputValue ? inputValue.length + 1 + 'ch' : '2ch') : '100%'} + $align={align} + {...moreHTMLProps} + /> + ); +} diff --git a/src/components/molecules/NumberInput/index.ts b/src/components/molecules/NumberInput/index.ts new file mode 100644 index 000000000..4778444af --- /dev/null +++ b/src/components/molecules/NumberInput/index.ts @@ -0,0 +1 @@ +export { default as NumberInput } from './NumberInput'; diff --git a/src/components/molecules/Paginator/Paginator.stories.tsx b/src/components/molecules/Paginator/Paginator.stories.tsx new file mode 100644 index 000000000..267d20b40 --- /dev/null +++ b/src/components/molecules/Paginator/Paginator.stories.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import Paginator from './Paginator'; + +const meta = { + component: Paginator, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A Paginator component to switch pages', + }, + }, + }, + argTypes: { + currentPage: { + description: 'The current page which is slected.', + type: { name: 'number' }, + defaultValue: { summary: 1 }, + }, + totalPages: { + description: 'The total pages that should counted to.', + type: { name: 'number' }, + }, + themeType: { + description: 'The theme type of the paginator.', + control: { + type: 'select', + }, + defaultValue: { summary: 'accent' }, + }, + outlinedButton: { + description: 'If true, the buttons will be outlined.', + type: { name: 'boolean' }, + defaultValue: { summary: false }, + }, + pageLimits: { + description: 'The limit of the pages Numbers.', + type: { name: 'number' }, + defaultValue: { summary: 3 }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + totalPages: 10, + currentPage: 1, + outlinedButton: false, + pageLimits: 3, + }, +}; diff --git a/src/components/molecules/Paginator/Paginator.style.tsx b/src/components/molecules/Paginator/Paginator.style.tsx new file mode 100644 index 000000000..18ee77ca5 --- /dev/null +++ b/src/components/molecules/Paginator/Paginator.style.tsx @@ -0,0 +1,41 @@ +import { styled, css } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// The Paginator Wrapper that wraps the hole components +export const StyledPaginator = styled.div<{ theme: TTheme }>` + display: flex; + justify-content: space-around; + align-items: center; + gap: ${({ theme }) => theme.spacing.xs}; + margin: 0 12px; +`; + +// The Number Wrapper that wraps the buttons for the page numbers +export const NumberList = styled.div` + display: flex; + width: 100%; + justify-content: space-around; + align-items: center; +`; + +// A Icon Wraper that press thes spacing more to the outside (Look more centerd) +interface IIconWrapper { + $align: 'left' | 'right'; + theme: TTheme; +} + +export const IconWrapper = styled.div` + display: flex; + flex-shrink: 0; + aspect-ratio: 1/1; + + ${({ $align, theme }) => + $align === 'right' + ? css` + margin-left: ${parseFloat(theme.spacing.xs) - 1 + 'px'}; + ` + : css` + margin-right: ${parseFloat(theme.spacing.xs) - 1 + 'px'}; + `} +`; diff --git a/src/components/molecules/Paginator/Paginator.tsx b/src/components/molecules/Paginator/Paginator.tsx new file mode 100644 index 000000000..73df6b850 --- /dev/null +++ b/src/components/molecules/Paginator/Paginator.tsx @@ -0,0 +1,72 @@ +import React, { useMemo } from 'react'; + +import SVGChevronLeft from '../../icons/SVGChevronLeft/SVGChevronLeft'; +import SVGChevronRight from '../../icons/SVGChevronRight/SVGChevronRight'; + +import FancyButton from '../../organisms/FancyButton/FancyButton'; +import { IconWrapper, NumberList, StyledPaginator } from './Paginator.style'; +import PageNumberList from '../../atoms/PageNumberList/PageNumberList'; +import { TThemeTypes } from '@/interface/TUiColors'; + +// Define the props for the Paginator component +interface IPaginator { + currentPage?: number; + totalPages: number; + themeType?: TThemeTypes; + outlinedButton?: boolean; + onPageChange: (page: number) => void; + pageLimits?: number; +} + +// --------------------------------------------------------------------------- // +// ---------------- The Paginator for a List to siwtch pages ----------------- // +// --------------------------------------------------------------------------- // +export default function Paginator(props: IPaginator) { + const { currentPage, totalPages, onPageChange, outlinedButton, themeType, pageLimits } = { ...defaultProps, ...props }; + + // Define a function to handle page changes + const pageHandler = (page: number) => { + onPageChange(page); + }; + + // Memoize the PageNumberList component to avoid unnecessary re-renders + const PageList = useMemo( + () => PageNumberList({ totalPages, currentPage, onClick: pageHandler, pageLimits }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [totalPages, currentPage, onPageChange, pageLimits] + ); + + // Render the Paginator component with the appropriate props + return ( + + {/* The left button for the Page Switch */} + {SVGChevronLeft}} + onClick={() => onPageChange(currentPage - 1)} + disabled={currentPage === 1} + /> + {/* The numberlist */} + {PageList} + {/* The right button for the Page Switch */} + {SVGChevronRight}} + size="md" + onClick={() => onPageChange(currentPage + 1)} + disabled={currentPage === totalPages} + /> + + ); +} + +// Define the default props for the Paginator component +const defaultProps = { + currentPage: 1 as number, + totalPages: 1, +}; diff --git a/src/components/molecules/Paginator/index.ts b/src/components/molecules/Paginator/index.ts new file mode 100644 index 000000000..756b0c61c --- /dev/null +++ b/src/components/molecules/Paginator/index.ts @@ -0,0 +1 @@ +export { default as Paginator } from './Paginator'; diff --git a/src/components/molecules/PasswordInput/PasswordInput.stories.tsx b/src/components/molecules/PasswordInput/PasswordInput.stories.tsx new file mode 100644 index 000000000..740dde614 --- /dev/null +++ b/src/components/molecules/PasswordInput/PasswordInput.stories.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import PasswordInput from './PasswordInput'; + +const meta = { + component: PasswordInput, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A PasswordInput component to input password', + }, + }, + }, + argTypes: { + value: { + description: 'The value of the input.', + type: { name: 'string' }, + }, + align: { + description: 'The align of the input.', + control: { + type: 'radio', + }, + defaultValue: { summary: 'left' }, + }, + activeHandler: { + description: 'The handler gives back is the input activ or not.', + }, + themeType: { + description: 'The theme type of the input.', + control: { + type: 'select', + }, + defaultValue: { summary: 'accent' }, + }, + layer: { + description: 'The layer of the input.', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { summary: 4 }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + value: '', + align: 'left', + themeType: 'secondary', + layer: 4, + }, +}; diff --git a/src/components/molecules/PasswordInput/PasswordInput.tsx b/src/components/molecules/PasswordInput/PasswordInput.tsx new file mode 100644 index 000000000..8624eb731 --- /dev/null +++ b/src/components/molecules/PasswordInput/PasswordInput.tsx @@ -0,0 +1,56 @@ +import React, { InputHTMLAttributes, useState } from 'react'; +import { styled } from 'styled-components'; + +import RawInput, { TRawInputAlign } from '../../atoms/RawInput/RawInput'; +import PasswordEye from '../../atoms/PasswordEye/PasswordEye'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +const WrapperEye = styled.div<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + position: absolute; + bottom: 6px; + right: 4px; + + svg { + color: ${({ theme, $themeType = 'secondary', $layer = 4 }) => getBackgroundColor({ theme, $themeType, $layer })}; + } +`; + +export interface IPasswordInputProps extends Omit, 'type'> { + value?: string; + align?: TRawInputAlign; + activeHandler?: (value: boolean) => void; + themeType?: TThemeTypes; + layer?: TLayer; +} +// --------------------------------------------------------------------------- // +// --------------- The passwordInputcomponent for only the input ------------- // +// --------------------------------------------------------------------------- // +export default function PasswordInput(props: IPasswordInputProps) { + const { value, onChange, activeHandler, align, themeType, layer, ...HTMLInputProps } = props; + const [isShowPassword, setIsShowPassword] = useState(false); + + // this handler is for the eye icon to show the password + const showPasswordHandler = () => { + setIsShowPassword(!isShowPassword); + }; + + return ( + <> + + + + activeHandler && activeHandler(true)} + onBlur={() => activeHandler && activeHandler(false)} + $align={align} + {...HTMLInputProps} + /> + + ); +} diff --git a/src/components/molecules/PasswordInput/index.ts b/src/components/molecules/PasswordInput/index.ts new file mode 100644 index 000000000..f117efa1b --- /dev/null +++ b/src/components/molecules/PasswordInput/index.ts @@ -0,0 +1 @@ +export { default as PasswordInput } from './PasswordInput'; diff --git a/src/components/molecules/RangeCalendar/IDateArray.model.ts b/src/components/molecules/RangeCalendar/IDateArray.model.ts new file mode 100644 index 000000000..4fb8c7809 --- /dev/null +++ b/src/components/molecules/RangeCalendar/IDateArray.model.ts @@ -0,0 +1,2 @@ +export type IDateArray = [Date | undefined, Date | undefined] | Date; +export type IDateOnlyArray = [Date | undefined, Date | undefined]; diff --git a/src/components/molecules/RangeCalendar/RangeCalendar.stories.tsx b/src/components/molecules/RangeCalendar/RangeCalendar.stories.tsx new file mode 100644 index 000000000..142ead3c8 --- /dev/null +++ b/src/components/molecules/RangeCalendar/RangeCalendar.stories.tsx @@ -0,0 +1,114 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import RangeCalendar from './RangeCalendar'; + +const meta = { + component: RangeCalendar, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A RangeCalendar component to select a date range. or a single date.', + }, + }, + }, + argTypes: { + selectedYear: { + description: 'The selected year.', + type: { name: 'number' }, + defaultValue: { summary: 'currentYear' }, + }, + rangeCalendar: { + description: 'If the Calendar a Rangepicker or SingelDate.', + type: { name: 'boolean' }, + defaultValue: { summary: false }, + }, + handler: { + description: 'The handler gives back the selected date.', + }, + selectFromTo: { + description: 'If the Calendar a Rangepicker that you can select set wich date is currently selected the from - to.', + control: { + type: 'radio', + }, + defaultValue: { summary: 'from' }, + }, + themeType: { + description: 'The theme type of the input.', + control: { + type: 'select', + }, + defaultValue: { summary: 'secondary' }, + }, + layer: { + description: 'The layer of the input.', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { summary: 4 }, + }, + externalMonthsWithDays: { + description: 'The external months with days.', + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + selectedYear: new Date().getFullYear(), + rangeCalendar: false, + selectFromTo: 'from', + themeType: 'secondary', + externalMonthsWithDays: [ + { + monthIdx: 0, + dates: [ + { + date: 1, + isAvilable: 'partially', + }, + { + date: 2, + isAvilable: 'not', + }, + { + date: 3, + isAvilable: 'transparent', + }, + ], + }, + { + monthIdx: 11, + dates: [ + { + date: 5, + isAvilable: 'partially', + }, + { + date: 7, + isAvilable: 'not', + }, + { + date: 2, + isAvilable: 'transparent', + }, + ], + }, + ], + disabledDateSetting: { + disabledWeekdays: [1, 6], + disableWeekends: true, + disablePastDates: false, + }, + }, +}; diff --git a/src/components/molecules/RangeCalendar/RangeCalendar.style.tsx b/src/components/molecules/RangeCalendar/RangeCalendar.style.tsx new file mode 100644 index 000000000..0b3811fde --- /dev/null +++ b/src/components/molecules/RangeCalendar/RangeCalendar.style.tsx @@ -0,0 +1,36 @@ +import { styled } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +export const MonthContainer = styled.div<{ theme: TTheme }>` + margin-top: ${({ theme }) => theme.spacing.md}; + + h2 { + margin: ${({ theme }) => theme.spacing.sm}; + margin-left: ${({ theme }) => theme.spacing.sm}; + } +`; + +export const StyledCalendar = styled.div` + width: 100%; + height: 300px; + overflow-y: auto; + &::-webkit-scrollbar { + display: none; + -ms-overflow-style: none; + scrollbar-width: none; + } +`; + +export const DateContainer = styled.div` + display: grid; + grid-template-columns: repeat(7, 1fr); +`; + +// this span is for the empty days +export const DateNumber = styled.span` + width: 14.28%; /* 100 / 7 days */ + text-align: right; + cursor: pointer; + box-sizing: border-box; +`; diff --git a/src/components/molecules/RangeCalendar/RangeCalendar.tsx b/src/components/molecules/RangeCalendar/RangeCalendar.tsx new file mode 100644 index 000000000..cd3968fe3 --- /dev/null +++ b/src/components/molecules/RangeCalendar/RangeCalendar.tsx @@ -0,0 +1,106 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { MonthContainer, StyledCalendar } from './RangeCalendar.style'; + +import MonthWithDays from '../MonthWithDays/MonthWithDays'; +import useSelectedDates from './helperFunctions/useSelectedDates'; +import useVisibleMonths from './helperFunctions/useVisibleMonths'; + +import { IDisabledDateSettings } from '../MonthWithDays/IDisableDateSettings.model'; +import { IExternalMonthWithDays } from '../MonthWithDays/IExternalMonthWithDays.model'; +import { IDateArray } from './IDateArray.model'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +// --------------------------------------------------------------------------- // +// -------- The main calenader wich can select a date, or date range ---------- // +// --------------------------------------------------------------------------- // +interface ICalendar { + selectedYear?: number; + rangeCalendar: boolean; + handler?: (date: IDateArray) => void; + selectFromTo?: 'from' | 'to' | undefined; + handleSwitchFromTo?: (change: 'from' | 'to') => void; + disabledDateSetting?: IDisabledDateSettings; + externalMonthsWithDays?: IExternalMonthWithDays[]; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function RangeCalendar(props: ICalendar) { + const { + selectedYear = new Date().getFullYear(), + handler, + selectFromTo, + handleSwitchFromTo, + disabledDateSetting, + externalMonthsWithDays, + rangeCalendar = false, + themeType, + layer, + } = props; + const monthRefs = useRef<(HTMLDivElement | null)[]>([]); + const [isScrolled, setIsScrolled] = useState(false); + const [externalMonthsData, setExternalMonthsData] = useState([]); + // hooks for selected dates and visible months + const { visibleMonths, firstMonthRef, lastMonthRef } = useVisibleMonths(isScrolled); + const { selectedDates, handleDateClick } = useSelectedDates(selectedYear, selectFromTo, handleSwitchFromTo, handler, rangeCalendar); + + // Scroll to current month on mount and set isScrolled to true + useEffect(() => { + const currentMonth = new Date().getMonth(); + monthRefs.current[currentMonth]?.scrollIntoView(); + setTimeout(() => { + setIsScrolled(true); + }, 300); + }, []); + + useEffect(() => { + // compare the year of the external state with the selected year + if (externalMonthsWithDays) { + // create a array for each month of a year and fill it with the external state + const monthsOfYearExternal = new Array(12).fill({}); + externalMonthsWithDays.forEach((month) => { + monthsOfYearExternal[month.monthIdx] = month; + }); + setExternalMonthsData(monthsOfYearExternal); + } else { + setExternalMonthsData([]); + } + }, [selectedYear, externalMonthsWithDays]); + + return ( + + {visibleMonths.map((MonthIdx, idx) => { + return ( + { + monthRefs.current[MonthIdx] = ref; + if (idx === 0) { + firstMonthRef.current = ref; + } + if (idx === visibleMonths.length - 1) { + lastMonthRef.current = ref; + } + }} + > + + + ); + })} + + ); +} + +RangeCalendar.defaultProps = { + rangeCalendar: false, +}; diff --git a/src/components/molecules/RangeCalendar/helperFunctions/selectDayFunction.ts b/src/components/molecules/RangeCalendar/helperFunctions/selectDayFunction.ts new file mode 100644 index 000000000..53752cc2d --- /dev/null +++ b/src/components/molecules/RangeCalendar/helperFunctions/selectDayFunction.ts @@ -0,0 +1,64 @@ +import Day from '../../MonthWithDays/day.model'; + +import { IDateOnlyArray } from '../IDateArray.model'; +type DateArray = Date[] | (Date | undefined)[]; + +const checkForValidDatesEnterd = (dates: DateArray) => { + return dates[0] && dates[1] ? true : false; +}; + +const checkDateIsSame = (dates: Date[]) => { + // Check if date is the same + return dates[0].getTime() === dates[1].getTime(); +}; + +interface ISelectDayFunction { + day: Day; + monthIndex: number; + selectedYear: number; + selectedDates: IDateOnlyArray; + selectFromTo: 'from' | 'to' | undefined; +} + +// inputs: selectFromTo +const selectDayFunction = (props: ISelectDayFunction) => { + const { day, monthIndex, selectedYear, selectedDates, selectFromTo } = props; + + // create day with the selected date + const newDate = new Date(selectedYear, monthIndex, day.number); + let newSelectedDates: DateArray = [...selectedDates]; + + // When no dates are selected or when "from" date is selected + if (selectFromTo === 'from' || selectFromTo === undefined) { + newSelectedDates = [newDate, selectedDates[1]]; + + if (newSelectedDates[1] && newDate > newSelectedDates[1]) { + newSelectedDates[1] = undefined; // Swap dates if "from" date is later than "to" date + } + + //handleSwitchFromTo && handleSwitchFromTo('to'); + } else if (selectFromTo === 'to') { + newSelectedDates = [selectedDates[0], newDate]; + + if (newSelectedDates[0] && newDate < newSelectedDates[0]) { + newSelectedDates[0] = undefined; // Swap dates if "to" date is earlier than "from" date + } + } + + //When Valid dates are entered + if (checkForValidDatesEnterd(newSelectedDates)) { + if (checkDateIsSame(newSelectedDates as Date[])) { + const identifySecondAsDate = newSelectedDates[1] as Date; + identifySecondAsDate.setDate(identifySecondAsDate.getDate() + 1); + newSelectedDates = [newSelectedDates[0], identifySecondAsDate]; + } + // else if when selection 1 is after selection 2 then set selection 2 to undefined + else if (newSelectedDates[0]!.getTime() > newSelectedDates[1]!.getTime()) { + newSelectedDates[1] = undefined; + } + } + + return newSelectedDates as IDateOnlyArray; +}; + +export default selectDayFunction; diff --git a/src/components/molecules/RangeCalendar/helperFunctions/useSelectedDates.ts b/src/components/molecules/RangeCalendar/helperFunctions/useSelectedDates.ts new file mode 100644 index 000000000..6061ce913 --- /dev/null +++ b/src/components/molecules/RangeCalendar/helperFunctions/useSelectedDates.ts @@ -0,0 +1,37 @@ +// useSelectedDates.ts +import { useState, useCallback } from 'react'; +import Day from '../../MonthWithDays/day.model'; +import selectDayFunction from './selectDayFunction'; +import { IDateArray, IDateOnlyArray } from '../IDateArray.model'; + +// the hook for handle the selected dates +export default function useSelectedDates( + selectedYear: number, + selectFromTo: 'from' | 'to' | undefined, + handleSwitchFromTo: ((change: 'from' | 'to') => void) | undefined, + handler: ((date: IDateArray) => void) | undefined, + rangeCalendar: boolean +) { + const [selectedDates, setSelectedDates] = useState([undefined, undefined]); + const handleDateClick = useCallback( + (day: Day, monthIndex: number) => { + // when a range is slected you can select two dates + if (rangeCalendar) { + const pickedDays = selectedDates as IDateOnlyArray; + const selectedDays = selectDayFunction({ day, monthIndex, selectedDates: pickedDays, selectedYear, selectFromTo }); + if (selectFromTo === 'from') handleSwitchFromTo && handleSwitchFromTo('to'); + if (selectFromTo === 'to') handleSwitchFromTo && handleSwitchFromTo('from'); + setSelectedDates(selectedDays); + handler && handler(selectedDays); + // when a single date is selected + } else { + const newDate = new Date(selectedYear, monthIndex, day.number); + setSelectedDates(newDate); + handler && handler(newDate); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [selectedDates, selectedYear, selectFromTo] + ); + return { selectedDates, handleDateClick }; +} diff --git a/src/components/molecules/RangeCalendar/helperFunctions/useVisibleMonths.ts b/src/components/molecules/RangeCalendar/helperFunctions/useVisibleMonths.ts new file mode 100644 index 000000000..d136f8c2f --- /dev/null +++ b/src/components/molecules/RangeCalendar/helperFunctions/useVisibleMonths.ts @@ -0,0 +1,44 @@ +import { useState, useEffect } from 'react'; +import useIntersectionObserver from '../../../utils/hooks/useIntersectionObserver/useIntersectionObserver'; + +// the hook for handle the visible months and add the next and previous months +export default function useVisibleMonths(isScrolled: boolean) { + const [lastMonthRef, isInView] = useIntersectionObserver(); + const [firstMonthRef, isFirstInView] = useIntersectionObserver(); + + // calulate the visible months based on the current month + const [visibleMonths, setVisibleMonths] = useState(() => { + const currentMonth = new Date().getMonth(); + const startMonth = Math.max(currentMonth - 2, 0); + const endMonth = Math.min(currentMonth + 2, 11); + return Array.from({ length: endMonth - startMonth + 1 }, (_, i) => i + startMonth); + }); + + // When the last visible month enters the viewport, add the next two months + useEffect(() => { + if (isInView) { + setVisibleMonths((prevMonths) => { + const nextMonth1 = prevMonths[prevMonths.length - 1] + 1; + const nextMonth2 = prevMonths[prevMonths.length - 1] + 2; + if (nextMonth1 < 12) prevMonths.push(nextMonth1); + if (nextMonth2 < 12) prevMonths.push(nextMonth2); + return [...prevMonths]; + }); + } + }, [isInView]); + + // When the first visible month enters the viewport, add the previous two months + useEffect(() => { + if (isFirstInView && isScrolled) { + setVisibleMonths((prevMonths) => { + const prevMonth1 = prevMonths[0] - 1; + const prevMonth2 = prevMonths[0] - 2; + if (prevMonth1 >= 0) prevMonths.unshift(prevMonth1); + if (prevMonth2 >= 0) prevMonths.unshift(prevMonth2); + return [...prevMonths]; + }); + } + }, [isFirstInView, isScrolled]); + + return { visibleMonths, firstMonthRef, lastMonthRef }; +} diff --git a/src/components/molecules/RangeCalendar/index.ts b/src/components/molecules/RangeCalendar/index.ts new file mode 100644 index 000000000..006645fdd --- /dev/null +++ b/src/components/molecules/RangeCalendar/index.ts @@ -0,0 +1 @@ +export { default as RangeCalendar } from './RangeCalendar'; diff --git a/src/components/molecules/ScalingSection/ScalingSection.stories.tsx b/src/components/molecules/ScalingSection/ScalingSection.stories.tsx new file mode 100644 index 000000000..cf430c31b --- /dev/null +++ b/src/components/molecules/ScalingSection/ScalingSection.stories.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import ScalingSection from './ScalingSection'; + +const meta = { + component: ScalingSection, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: This is the top of a swipeupmodal that is used to control the swipe up modal.', + }, + }, + }, + argTypes: { + touchStart: { + description: 'A function to handle the touchStart event.', + type: { name: 'function' }, + }, + touchMove: { + description: 'A function to handle the touchMove event.', + type: { name: 'function' }, + }, + touchEnd: { + description: 'A function to handle the touchEnd event.', + type: { name: 'function' }, + }, + click: { + description: 'A function to handle the click event.', + type: { name: 'function' }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: {}, +}; diff --git a/src/components/molecules/ScalingSection/ScalingSection.tsx b/src/components/molecules/ScalingSection/ScalingSection.tsx new file mode 100644 index 000000000..9170b4b2f --- /dev/null +++ b/src/components/molecules/ScalingSection/ScalingSection.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { styled } from 'styled-components'; +import SwipeUpDash from '../../atoms/SwipeUpDash/SwipeUpDash'; + +const SytledScalingSection = styled.div` + position: sticky; + height: 30px; + top: 0; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +`; + +// --------------------------------------------------------------------------- // +//the ScalingSection is for conroling events on the swipe up dash for better UX // +// --------------------------------------------------------------------------- // +interface IScalingSection { + touchStart?: (e: React.TouchEvent) => void; + touchMove: (e: React.TouchEvent) => void; + touchEnd: (e: React.TouchEvent) => void; + click: (e: React.MouseEvent) => void; +} +export default function ScalingSection({ touchStart, touchMove, touchEnd, click }: IScalingSection) { + return ( + + + + ); +} diff --git a/src/components/molecules/ScalingSection/index.ts b/src/components/molecules/ScalingSection/index.ts new file mode 100644 index 000000000..38e2d3229 --- /dev/null +++ b/src/components/molecules/ScalingSection/index.ts @@ -0,0 +1 @@ +export { default as ScalingSection } from './ScalingSection'; diff --git a/src/components/molecules/SearchBar/SearchBar.stories.tsx b/src/components/molecules/SearchBar/SearchBar.stories.tsx new file mode 100644 index 000000000..7a2de759f --- /dev/null +++ b/src/components/molecules/SearchBar/SearchBar.stories.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import SearchBar from './SearchBar'; + +const meta = { + component: SearchBar, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The Searchbar for the header.', + }, + }, + }, + argTypes: { + searchValue: { + description: 'The value of the search bar.', + type: { name: 'string' }, + }, + align: { + description: 'The alignment of the text.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'center', + }, + }, + themeType: { + description: 'The theme type of the search bar.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the search bar.', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 4, + }, + }, + placerholder: { + description: 'The placeholder of the search bar.', + type: { name: 'string' }, + defaultValue: { + summary: 'Search', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + searchValue: '', + }, +}; diff --git a/src/components/molecules/SearchBar/SearchBar.tsx b/src/components/molecules/SearchBar/SearchBar.tsx new file mode 100644 index 000000000..d234bdf51 --- /dev/null +++ b/src/components/molecules/SearchBar/SearchBar.tsx @@ -0,0 +1,81 @@ +import React, { ChangeEvent, useState } from 'react'; +import { styled, css } from 'styled-components'; +import FancyTextInput from '../../organisms/FancyTextInput/FancyTextInput'; + +import SVGSearch from '../../icons/SVGSearch/SVGSearch'; +import FancySVGAtom from '../../atoms/FancySVGAtom/FancySVGAtom'; +import { TRawInputAlign } from '../../atoms/RawInput/RawInput'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; + +// Styled component for the search bar +const StyledSearchBar = styled.div<{ $isActive?: boolean; theme: TTheme }>` + display: flex; + align-items: center; + border-radius: ${({ $isActive, theme }) => + $isActive + ? `${theme.borderRadius.lg} ${theme.borderRadius.lg} 0px 0px` + : theme.borderRadius.lg}; // Set the border radius based on whether the search bar list is active + gap: ${({ theme }) => theme.spacing.sm}; + z-index: 1; + + input { + padding: 0 0 4px; + } +`; + +// Props for the SearchBar component +interface ISearchBar { + searchValue?: string; + align?: TRawInputAlign; + activeHandler?: (isActive: boolean) => void; + handler?: (value: string) => void; + themeType?: TThemeTypes; + layer?: TLayer; + placerholder?: string; +} +// The SearchBar component +export default function SearchBar(props: ISearchBar) { + const { activeHandler, handler, searchValue, align, themeType, layer } = { ...defaultProps, ...props }; + const [isActive, setIsActive] = useState(false); // The state for the isActive state of the search bar + // Function to handle changes to the isActive state + const focusHandler = (isFocused: boolean) => { + activeHandler && activeHandler(isFocused); + setIsActive(isFocused); + }; + + // Function to handle changes to the search value + const onChangeValueHandler = (e?: ChangeEvent) => { + handler && handler(e!.target.value); + }; + + return ( + + {/* The search icon */} + + {SVGSearch} + + {/* The search input */} + + + ); +} + +const defaultProps = { + align: 'center', +}; diff --git a/src/components/molecules/SearchBar/index.ts b/src/components/molecules/SearchBar/index.ts new file mode 100644 index 000000000..8580b56b7 --- /dev/null +++ b/src/components/molecules/SearchBar/index.ts @@ -0,0 +1 @@ +export { default as SearchBar } from './SearchBar'; diff --git a/src/components/molecules/SearchBarList/SearchBarList.stories.tsx b/src/components/molecules/SearchBarList/SearchBarList.stories.tsx new file mode 100644 index 000000000..cdc8868a4 --- /dev/null +++ b/src/components/molecules/SearchBarList/SearchBarList.stories.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import SearchBarList from './SearchBarList'; + +const meta = { + component: SearchBarList, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A SearchBarList component to display a list of items as children', + }, + }, + }, + argTypes: { + isActive: { + description: 'If true, the list will be displayed.', + type: { name: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + themeType: { + description: 'The theme type of the search bar list.', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the search bar list.', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + isActive: true, + }, +}; diff --git a/src/components/molecules/SearchBarList/SearchBarList.style.tsx b/src/components/molecules/SearchBarList/SearchBarList.style.tsx new file mode 100644 index 000000000..2127f4292 --- /dev/null +++ b/src/components/molecules/SearchBarList/SearchBarList.style.tsx @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +// Styled component for the search bar list +export const StyledSearchBarList = styled.div<{ theme: TTheme }>` + border-radius: ${({ theme }) => theme.borderRadius.xl}; + z-index: 100; +`; + +// Styled component for the inner card of the search bar list +export const InnerCard = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + box-sizing: border-box; + align-items: center; + justify-content: center; +`; diff --git a/src/components/molecules/SearchBarList/SearchBarList.tsx b/src/components/molecules/SearchBarList/SearchBarList.tsx new file mode 100644 index 000000000..98525afd6 --- /dev/null +++ b/src/components/molecules/SearchBarList/SearchBarList.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import FancyCard from '../../atoms/FancyCard/FancyCard'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { InnerCard, StyledSearchBarList } from './SearchBarList.style'; + +// Props for the SearchBarList component +interface ISearchBarList { + isActive?: boolean; + children?: React.ReactNode; + themeType?: TThemeTypes; + layer?: TLayer; +} +// The SearchBarList component +export default function SearchBarList(props: ISearchBarList) { + const { isActive, children, themeType, layer } = props; + + return ( + + {/* If the search bar list is active, display the list */} + {isActive && ( + + + {/* If there are items to display, display them */} + {children &&
{children}
} +
+
+ )} +
+ ); +} diff --git a/src/components/molecules/SearchBarList/index.ts b/src/components/molecules/SearchBarList/index.ts new file mode 100644 index 000000000..138a56344 --- /dev/null +++ b/src/components/molecules/SearchBarList/index.ts @@ -0,0 +1 @@ +export { default as SearchBarList } from './SearchBarList'; diff --git a/src/components/molecules/SingleInputs/SingleInputs.stories.tsx b/src/components/molecules/SingleInputs/SingleInputs.stories.tsx new file mode 100644 index 000000000..34e6e39e8 --- /dev/null +++ b/src/components/molecules/SingleInputs/SingleInputs.stories.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import SingleInputs from './SingleInputs'; + +const meta = { + component: SingleInputs, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The Singele input component is for single input like a verification code.', + }, + }, + }, + + argTypes: { + length: { + description: 'How many inputs are Displayed / how long is the verification code.', + control: { + type: 'number', + }, + defaultValue: { + summary: 6, + }, + }, + automaticCase: { + description: 'If the input should be upper or lower case', + control: { + type: 'select', + options: ['upper', 'lower', undefined], + }, + defaultValue: { + summary: undefined, + }, + }, + status: { + description: 'The status of the input', + control: { + type: 'object', + }, + defaultValue: { + summary: `isError: false, + isSucceed: false,`, + }, + }, + themeType: { + description: 'The theme type of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the input', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: '0', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + length: 6, + status: { + isError: false, + isSucceed: false, + }, + }, +}; diff --git a/src/components/molecules/SingleInputs/SingleInputs.style.tsx b/src/components/molecules/SingleInputs/SingleInputs.style.tsx new file mode 100644 index 000000000..32d74dbed --- /dev/null +++ b/src/components/molecules/SingleInputs/SingleInputs.style.tsx @@ -0,0 +1,22 @@ +import { styled } from 'styled-components'; + +import IStatus from '../../../interface/IStatus'; +import { TTheme } from '@/interface/TTheme'; + +interface IInputWrapper { + $status?: Omit; + theme: TTheme; +} +export const InputWrapper = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: center; + width: 100%; + gap: ${({ theme }) => theme.spacing.md}; + + input { + ${({ $status, theme }) => + $status?.isError ? `border-color: ${theme.colors.error[0]}` : $status?.isSucceed ? `border-color: ${theme.colors.success[0]};` : ''}; + transition: border-color 0.3s ease-in-out; + } +`; diff --git a/src/components/molecules/SingleInputs/SingleInputs.tsx b/src/components/molecules/SingleInputs/SingleInputs.tsx new file mode 100644 index 000000000..97ce9d064 --- /dev/null +++ b/src/components/molecules/SingleInputs/SingleInputs.tsx @@ -0,0 +1,148 @@ +import React, { useState, createRef, useEffect } from 'react'; + +import SingleInputAtom from '../../atoms/SingleInputAtom/SingleInputAtom'; +import IStatus from '../../../interface/IStatus'; +import { InputWrapper } from './SingleInputs.style'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +interface IFancySingleInputsProps { + length?: number; + handler?: (value: string) => void; + status?: Pick; + automaticCase?: 'upper' | 'lower'; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function SingleInputs(props: IFancySingleInputsProps) { + const { length = 6, handler, status, automaticCase, themeType, layer } = props; + + const [values, setValues] = useState(Array(length).fill('')); + const refs = Array.from({ length }, () => createRef()); + + //when the values are filled, call the handler + useEffect(() => { + if (values.every((value) => value !== '')) { + handler && handler(values.join('')); + } + }, [values, handler]); + + // this function is to handle the paste event + const processClipboardData = (paste: string) => { + const newValues = paste.split('').slice(0, length); + + setValues((prev) => { + const copy = [...prev]; + for (let i = 0; i < newValues.length; i++) { + copy[i] = newValues[i]; + } + return copy; + }); + }; + + const handlePaste = (event: React.ClipboardEvent) => { + event.preventDefault(); + const paste = event.clipboardData.getData('text'); + processClipboardData(paste); + (event.target as HTMLInputElement).blur(); + }; + + // this function is to handle the character keys + const handleCharacterInput = (event: React.KeyboardEvent, index: number) => { + event.preventDefault(); + + // if the automatic case is upper, change the character to upper case + if (automaticCase === 'upper') { + event.key = event.key.toUpperCase(); + } + if (automaticCase === 'lower') { + event.key = event.key.toLowerCase(); + } + + setValues((prev) => { + const copy = [...prev]; + copy[index] = event.key; + return copy; + }); + + // if the next input exists, focus it + if (refs[index + 1]) { + moveRightToTheNextInputRef(index); + } else { + // if there is no next input, blur the current input + event.currentTarget.blur(); + } + }; + + // this function is to handle the backspace key + const handleBackspaceKey = (index: number) => { + if (values[index] === '') { + moveLeftToTheNextInputRef(index); + } else { + setValues((prev) => { + const copy = [...prev]; + copy[index] = ''; + return copy; + }); + } + }; + + // this function is to jump to the next input + const moveRightToTheNextInputRef = (index: number) => { + // if the next input exists, focus it + if (refs[index + 1]) { + refs[index + 1].current?.focus(); + } + }; + + // this function is to jumpback to the previous input + const moveLeftToTheNextInputRef = (index: number) => { + // if the previous input exists, focus it + if (refs[index - 1]) { + refs[index - 1].current?.focus(); + } + }; + + const handleKeyDownPaste = async () => { + const clipText = await navigator.clipboard.readText(); + moveRightToTheNextInputRef(clipText.length); + processClipboardData(clipText); + }; + + const handleKeyDown = (event: React.KeyboardEvent, index: number) => { + if (event.key === 'v' && (event.ctrlKey || event.metaKey)) { + void handleKeyDownPaste(); + event.currentTarget.blur(); + } + // Handle character keys + else if (event.key.length === 1) { + handleCharacterInput(event, index); + } + // Handle backspace key + else if (event.key === 'Backspace') { + handleBackspaceKey(index); + } + // Handle arrow keys + else if (event.key === 'ArrowRight') { + moveRightToTheNextInputRef(index); + } else if (event.key === 'ArrowLeft') { + moveLeftToTheNextInputRef(index); + } + }; + + return ( + + {values.map((value, index) => ( + handleKeyDown(e, index)} + themeType={themeType} + layer={layer} + /> + ))} + + ); +} diff --git a/src/components/molecules/SingleInputs/index.ts b/src/components/molecules/SingleInputs/index.ts new file mode 100644 index 000000000..05ea385d0 --- /dev/null +++ b/src/components/molecules/SingleInputs/index.ts @@ -0,0 +1 @@ +export { default as SingleInputs } from './SingleInputs'; diff --git a/src/components/molecules/SingleToastMessage/IToastMessage.model.tsx b/src/components/molecules/SingleToastMessage/IToastMessage.model.tsx new file mode 100644 index 000000000..6de198d49 --- /dev/null +++ b/src/components/molecules/SingleToastMessage/IToastMessage.model.tsx @@ -0,0 +1,13 @@ +import { TSystemMessageProps } from '../../../interface/TSystemMessageProps'; +import { TLayer } from '@/interface/TLayer'; + +type IToastMessage = { + id: number; + title: string; + message: string; + time?: number; + themeType: TSystemMessageProps; + layer?: TLayer; +}; + +export default IToastMessage; diff --git a/src/components/molecules/SingleToastMessage/SingleToastMessage.mdx b/src/components/molecules/SingleToastMessage/SingleToastMessage.mdx new file mode 100644 index 000000000..520a55f66 --- /dev/null +++ b/src/components/molecules/SingleToastMessage/SingleToastMessage.mdx @@ -0,0 +1,58 @@ +## SingleToastMessage Documentation + +### Overview + +`SingleToastMessage` is a React component tailored to display individual toast notifications with style and interactivity. It handles various message types such as success, warning, error, and information. Each toast is equipped with a headline, a message body, a customizable timer line indicating its duration, and a close button for users to dismiss the toast manually. + +### Component Props + +The component accepts the following properties: + +- **toast**: This is the main object that contains all the data required for the toast. It follows the `IToastMessage` type, which encompasses: + + - `id`: A unique identifier for the toast. + - `title`: The headline or main point of the toast. + - `message`: Detailed message or information. + - `time`: Optional. The duration the toast will be displayed (in milliseconds). + - `themeType`: Determines the style of the toast based on its type. Accepts values 'success', 'warning', 'error', or 'info'. + - `layer`: Optional. Sets the depth or layer of the toast's appearance. + +- **remove**: A function to be called to dismiss the toast. It takes the toast's `id` as an argument. + +### Component Behavior + +On mounting, the `SingleToastMessage` component starts a timer based on the `time` prop its only for displaying the countdown. However, users also have the flexibility to manually dismiss the toast using the `FancyXButton`. + +### Styling + +Different message types (`themeType`) influence the toast's appearance, ensuring users can quickly distinguish the notification's nature. For instance, an 'error' might display in red, while 'success' might show in green. The `$messageType` and `$time` props are also leveraged for stylized components like `Container` and `TimerLine`. + +### Example Usage + +```React +import SingleToastMessage from './SingleToastMessage'; + +const MyToastComponent = () => { + const handleRemove = (id: number) => { + console.log(`Toast with ID: ${id} removed.`); + // Further logic to handle removal, e.g., update state + }; + + const sampleToast: IToastMessage = { + id: 1, + title: "Success!", + message: "Data has been saved successfully.", + time: 3000, + themeType: "success", + layer: 5 + }; + + return ; +}; + +export default MyToastComponent; +``` + +### Conclusion + +The `SingleToastMessage` component offers a sleek and user-friendly way to relay notifications to users. With automated dismissal and manual override capabilities, it ensures a smooth user experience while effectively conveying important messages. diff --git a/src/components/molecules/SingleToastMessage/SingleToastMessage.stories.tsx b/src/components/molecules/SingleToastMessage/SingleToastMessage.stories.tsx new file mode 100644 index 000000000..13399b650 --- /dev/null +++ b/src/components/molecules/SingleToastMessage/SingleToastMessage.stories.tsx @@ -0,0 +1,70 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import SingleToastMessage from './SingleToastMessage'; + +const HelperComponent = (props: React.ComponentProps) => ; +HelperComponent.displayName = 'SingleToastMessage'; + +const meta = { + component: HelperComponent, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A toast message to display a message to the user', + }, + }, + }, + + argTypes: { + remove: { + description: 'A function that is called from the toast when the it should be removed.', + type: { name: 'function' }, + }, + toast: { + description: 'The toast message to display.', + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Success: Story = { + render: (args) => , + args: { + toast: { + id: 1, + title: 'Success', + message: 'This is a success message', + time: 5000, + themeType: 'success', + }, + }, +}; +export const Error: Story = { + render: (args) => , + args: { + toast: { + id: 2, + title: 'warning', + message: 'this is a warning message', + time: 5000, + themeType: 'warning', + }, + }, +}; +export const Warning: Story = { + render: (args) => , + args: { + toast: { + id: 3, + title: 'error', + message: 'this is an error message', + time: 5000, + themeType: 'error', + }, + }, +}; diff --git a/src/components/molecules/SingleToastMessage/SingleToastMessage.style.tsx b/src/components/molecules/SingleToastMessage/SingleToastMessage.style.tsx new file mode 100644 index 000000000..5b9353276 --- /dev/null +++ b/src/components/molecules/SingleToastMessage/SingleToastMessage.style.tsx @@ -0,0 +1,70 @@ +import { styled, keyframes } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import colorTransparencyCalculator from '../../../design/designFunctions/colorTransparencyCalculator/colorTransparencyCalculator'; +import { boxShadow } from '../../../design/designFunctions/shadows/shadows'; +import { TLayer } from '@/interface/TLayer'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; + +type ToastMessageProps = 'success' | 'warning' | 'error' | 'info'; + +interface IToastMessage { + $messageType: ToastMessageProps; + $layer?: TLayer; + theme: TTheme; +} + +interface TimerLineProps { + $messageType: ToastMessageProps; + $layer?: TLayer; + theme: TTheme; + $time: number; +} + +// styles for single toast message +export const Container = styled.div` + z-index: 99; + display: flex; + position: relative; + flex-direction: column; + align-items: left; + color: ${({ $messageType, theme, $layer = 5 }) => + getBackgroundColor({ $themeType: $messageType, theme, $layer })}; //theme[$messageType]['5'] + border-radius: ${({ theme }) => theme.borderRadius.sm}; + padding: ${({ theme }) => theme.spacing.lg}; + background-color: ${({ theme }) => colorTransparencyCalculator(theme.colors.primary['0'], 0.95)}; + border-left: 4px solid ${({ $messageType, theme, $layer = 3 }) => getBackgroundColor({ $themeType: $messageType, theme, $layer })}; + box-sizing: border-box; + ${boxShadow.md} +`; + +export const Headline = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + button { + align-self: flex-start; + line-height: ${fontSize.lg}; + } +`; + +// animation for timer line +const timerAnimation = keyframes` + 0% { + width: 100%; + } + 100% { + width: 0; + } +`; +export const TimerLine = styled.div` + position: absolute; + bottom: 0; + left: 0; + height: 2px; + width: 100%; + background-color: ${({ $messageType, theme, $layer = 3 }) => getBackgroundColor({ $themeType: $messageType, theme, $layer })}; + animation: ${() => timerAnimation} ${({ $time }) => $time - 300}ms linear forwards; +`; diff --git a/src/components/molecules/SingleToastMessage/SingleToastMessage.tsx b/src/components/molecules/SingleToastMessage/SingleToastMessage.tsx new file mode 100644 index 000000000..c413c6219 --- /dev/null +++ b/src/components/molecules/SingleToastMessage/SingleToastMessage.tsx @@ -0,0 +1,44 @@ +import React, { forwardRef, useEffect } from 'react'; + +// Import necessary components and interfaces +import IToastMessage from './IToastMessage.model'; +import { Container, TimerLine, Headline } from './SingleToastMessage.style'; +import Typography from '../../atoms/Typography/Typography'; +import FancyXButton from '../../atoms/FancyXButton/FancyXButton'; + +// Define the interface for the component +interface ISingleToastMessage { + toast: IToastMessage; + remove: (id: number) => void; +} + +// A Single Toast Message Component wich +const SingleToastMessage = forwardRef((props, ref) => { + const { id, title, message, time, themeType, layer } = props.toast; + const remove = props.remove; + + // Remove toast after the time + useEffect(() => { + const timer = setTimeout(() => { + remove(id); + }, time); + + return () => clearTimeout(timer); + }, [id, time, remove]); + + return ( + + + + {title} + + remove(id)} themeType={themeType} layer={layer || 5} /> + + {message} + + + ); +}); + +// Export the component +export default SingleToastMessage; diff --git a/src/components/molecules/SingleToastMessage/index.ts b/src/components/molecules/SingleToastMessage/index.ts new file mode 100644 index 000000000..e6560b728 --- /dev/null +++ b/src/components/molecules/SingleToastMessage/index.ts @@ -0,0 +1 @@ +export { default as SingleToastMessage } from './SingleToastMessage'; diff --git a/src/components/molecules/SwipeUpModal/ISwipeUpModal.model.ts b/src/components/molecules/SwipeUpModal/ISwipeUpModal.model.ts new file mode 100644 index 000000000..7e4d69f14 --- /dev/null +++ b/src/components/molecules/SwipeUpModal/ISwipeUpModal.model.ts @@ -0,0 +1,13 @@ +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; + +export interface ISwipeUpModal { + isOpen?: boolean; + children?: React.ReactNode; + isCloseAble?: boolean; // if a error occurs and the modal should be closeable + isScalable?: boolean; // if the modal should be static or scalable + themeType?: TThemeTypes; + layer?: TLayer; + backdrop?: boolean; + onClose?: () => void; +} diff --git a/src/components/molecules/SwipeUpModal/SwipeUpModal.stories.tsx b/src/components/molecules/SwipeUpModal/SwipeUpModal.stories.tsx new file mode 100644 index 000000000..45a3e7e6d --- /dev/null +++ b/src/components/molecules/SwipeUpModal/SwipeUpModal.stories.tsx @@ -0,0 +1,89 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import SwipeUpModal from './SwipeUpModal'; + +const meta = { + component: SwipeUpModal, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A simple header component to display a header with slots', + }, + }, + }, + + argTypes: { + isOpen: { + description: 'The state of the modal', + type: { name: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + isCloseAble: { + description: 'If true, the modal can be closed.', + type: { name: 'boolean' }, + defaultValue: { + summary: true, + }, + }, + isScalable: { + description: 'If true, the modal can be scaled.', + type: { name: 'boolean' }, + defaultValue: { + summary: true, + }, + }, + onClose: { + description: 'A function that is called when the modal should be closed.', + type: { name: 'function' }, + }, + themeType: { + description: 'The theme type of the modal.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the modal.', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 0, + }, + }, + backdrop: { + description: 'If true, the backdrop is visible.', + type: { name: 'boolean' }, + defaultValue: { + summary: true, + }, + }, + }, + + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + isOpen: true, + isCloseAble: true, + isScalable: true, + themeType: 'primary', + layer: 1, + }, +}; diff --git a/src/components/molecules/SwipeUpModal/SwipeUpModal.style.tsx b/src/components/molecules/SwipeUpModal/SwipeUpModal.style.tsx new file mode 100644 index 000000000..c7f984388 --- /dev/null +++ b/src/components/molecules/SwipeUpModal/SwipeUpModal.style.tsx @@ -0,0 +1,48 @@ +import { styled } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +export const WrapperModal = styled.div` + display: flex; + justify-content: center; + align-items: flex-end; + position: fixed; + width: 100%; + height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 101; +`; + +export const WrapperAnimated = styled.div` + display: flex; + align-items: flex-end; + width: 100%; +`; + +export const ContentBox = styled.div<{ theme: TTheme }>` + overflow-y: scroll; + color: ${({ theme }) => theme.colors.secondary[0]}; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 100%; +`; + +export const WrapperContent = styled.div` + padding: 0; + width: 90%; + display: flex; + flex-direction: column; + gap: 24px; + margin-bottom: 24px; +`; + +export const Content = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 24px; +`; diff --git a/src/components/molecules/SwipeUpModal/SwipeUpModal.tsx b/src/components/molecules/SwipeUpModal/SwipeUpModal.tsx new file mode 100644 index 000000000..d40df744a --- /dev/null +++ b/src/components/molecules/SwipeUpModal/SwipeUpModal.tsx @@ -0,0 +1,132 @@ +import React, { useEffect, useState } from 'react'; +import { animated, useTransition } from '@react-spring/web'; + +import useWindowDimensions from '../../utils/hooks/useWindowDimensions/useWindowDimensions'; +import SwipeUpContainer from '../../atoms/SwipeUpContainer/SwipeUpContainer'; +import BackDrop from '../../atoms/BackDrop/BackDrop'; +import UseDelay from '../../utils/components/UseDelay/UseDelay'; +import ScalingSection from '../ScalingSection/ScalingSection'; +import { Content, ContentBox, WrapperAnimated, WrapperContent, WrapperModal } from './SwipeUpModal.style'; +import { ISwipeUpModal } from './ISwipeUpModal.model'; +import { TModalStatus } from '../../../interface/TModalStatus'; + +// --------------------------------------------------------------------------- // +// ----------- The Modal Molecule the displays the complete modal - ---------- // +// --------------------------------------------------------------------------- // +export default function SwipeUpModal(props: ISwipeUpModal) { + const { children, isOpen, isCloseAble, isScalable, onClose, themeType, layer, backdrop = true } = { ...defaultProps, ...props }; + + const [statusModal, setStatusModal] = useState('closed'); + const [modalPosition, setModalPosition] = useState({ height: '100%' }); + const [initialHeight, setInitialHeight] = useState(); + const { height } = useWindowDimensions(); + + //Opens the modal and set the overfolw to hidden + const openModal = () => { + document.body.style.overflow = 'hidden'; + setModalPosition({ height: 'auto' }); + setStatusModal('open'); + }; + + const moveModalHandler = (e: React.TouchEvent) => { + document.body.style.overflowY = 'hidden'; + const getToutch = e.changedTouches[0].clientY; + + // sets the initial height of the modal on the auto height + // this is used for the close calculation + if (!initialHeight) setInitialHeight(height - getToutch); + + //from the to 100% = 0px to the buttom 0% 844px + //height = 100% 844 + const onePercent = height / 100; + const getPosition = getToutch / onePercent; + const turnValue = 100 - getPosition; + + // update the modal position + setModalPosition({ height: turnValue + '%' }); + }; + + //Close the modal via the specific event and set the overflow back + //the closedBy is needed is needed to prevent the modal from closing --> + //when the user is interacting with the modal and the modal is not closeable + const closeModal = (cloesedBy: 'status' | 'intercation') => { + if (cloesedBy === 'intercation' && !isCloseAble) return; + + //close the gobal modal state + if (onClose) onClose(); + setStatusModal('closing'); + + //wait for the animation and remove the modal from the store + setTimeout(() => { + setStatusModal('closed'); + }, 250); + + document.body.style.overflow = 'overlay'; + }; + + // when the touchevent is active, can calucalte the height of the modal and close it + const toutchEnd = (e: React.TouchEvent) => { + const getToutch = e.changedTouches[0].clientY; + // this calulation is for good user experience + if (initialHeight !== undefined && height - getToutch < initialHeight * 0.85) { + closeModal('intercation'); + } + }; + + // the open transition for the modal via react-spring + const openTransition = useTransition(statusModal === 'open', { + from: { height: '0%' }, + enter: { height: '100%' }, + leave: { height: '0%' }, + }); + + // if the modal is open, open the modal else close it + useEffect(() => { + if (isOpen) { + openModal(); + } else { + closeModal('status'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isOpen]); + + return ( + + + {openTransition( + (styles, item) => + item && ( + + + {/*// ---------- The top of the modal is used for the scaling ---------- //*/} + {isScalable && isCloseAble && ( + { + moveModalHandler(e); + }} + touchEnd={toutchEnd} + click={() => closeModal('intercation')} + /> + )} + {/*// ---------- Content Area ---------- //*/} + + {/*// ---------- Header ---------- //*/} + + {children} + + + + + ) + )} + {backdrop && closeModal('intercation')} />} + + + ); +} + +const defaultProps: ISwipeUpModal = { + isCloseAble: true, + isScalable: true, + isOpen: true, +}; diff --git a/src/components/molecules/SwipeUpModal/index.ts b/src/components/molecules/SwipeUpModal/index.ts new file mode 100644 index 000000000..171e8bf36 --- /dev/null +++ b/src/components/molecules/SwipeUpModal/index.ts @@ -0,0 +1 @@ +export { default as SwipeUpModal } from './SwipeUpModal'; diff --git a/src/components/molecules/SwitchList/SwitchList.stories.tsx b/src/components/molecules/SwitchList/SwitchList.stories.tsx new file mode 100644 index 000000000..6e6a05ff0 --- /dev/null +++ b/src/components/molecules/SwitchList/SwitchList.stories.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import SwitchList from './SwitchList'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; +import { FancyBottomBarIcon } from '../../templates/FancyBottomBarIcon'; + +const meta = { + component: SwitchList, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A SwitchList that used to indicate which item is active', + }, + }, + }, + + argTypes: { + children: { + description: 'The children of the SwitchList.', + }, + whichIndexIsSelected: { + description: 'The index of the selected item.', + type: { name: 'number' }, + }, + activeItemHandler: { + description: 'A function that is called when the active item is changed.', + type: { name: 'function' }, + }, + indicatorType: { + description: 'The type of the active indicator.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'underline', + }, + }, + externalStyle: { + description: 'The external style of the SwitchList.', + type: { name: 'string' }, + }, + }, + + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => ( + + } /> + } /> + } /> + + ), + args: { + children: <>, + }, +}; diff --git a/src/components/molecules/SwitchList/SwitchList.style.tsx b/src/components/molecules/SwitchList/SwitchList.style.tsx new file mode 100644 index 000000000..b7d2ab820 --- /dev/null +++ b/src/components/molecules/SwitchList/SwitchList.style.tsx @@ -0,0 +1,15 @@ +import { styled, CSSProp } from 'styled-components'; +import ULRaw from '../../atoms/RawUL/RawUL'; + +export const StyledList = styled(ULRaw)<{ $externalStyle?: CSSProp; $direction?: 'horizontal' | 'vertical' }>` + display: flex; + flex-direction: ${({ $direction }) => ($direction === 'vertical' ? 'column' : 'row')}; + justify-content: space-evenly; + width: 100%; + ${({ $externalStyle }) => $externalStyle}; +`; + +export const ItemWrapper = styled.li` + position: relative; + flex-grow: 1; +`; diff --git a/src/components/molecules/SwitchList/SwitchList.tsx b/src/components/molecules/SwitchList/SwitchList.tsx new file mode 100644 index 000000000..d219f3d5f --- /dev/null +++ b/src/components/molecules/SwitchList/SwitchList.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useState } from 'react'; +import { CSSProp } from 'styled-components'; + +import SwitchActiveIndicator, { IActiveSwitchIndicator } from '../../atoms/SwitchActiveIndicator/SwitchActiveIndicator'; +import { ItemWrapper, StyledList } from './SwitchList.style'; + +type TSwitchActiveIndicator = Omit; + +interface IBottomBarListProps { + children?: React.ReactNode; + whichIndexIsSelected?: number; + activeItemHandler?: (activeKey: number) => void; + indicatorType?: React.ComponentProps['$type']; + externalStyle?: CSSProp; +} + +interface IKey { + $uniquekey: string; +} +// --------------------------------------------------------------------------- // +// -------------- The Switch List Indicates wich item is active -------------- // +// --------------------------------------------------------------------------- // +export default function SwitchList(props: IBottomBarListProps & TSwitchActiveIndicator) { + const { children, whichIndexIsSelected, activeItemHandler, indicatorType, externalStyle, $direction, ...indicatorProps } = props; + const [currentActive, setCurrentActive] = useState(''); + + const activeHandler = (uniqueKey: string) => { + setCurrentActive(uniqueKey); + activeItemHandler && activeItemHandler(Number(uniqueKey)); + }; + + useEffect(() => { + setCurrentActive(`${whichIndexIsSelected ? whichIndexIsSelected + 1 : 1}`); + }, [whichIndexIsSelected]); + + return ( + + {React.Children.map(children, (child, index) => { + if (React.isValidElement(child)) { + // Generate a unique key (or use any other unique identifier logic) + const uniqueKey = `${index + 1}`; + + // Clone the child and add the uniqueKey prop + const clonedChild = React.cloneElement(child as React.ReactElement, { $uniquekey: uniqueKey }); + + return ( + activeHandler(uniqueKey)}> + {clonedChild} + {index === 0 && ( + + )} + + ); + } + return child; + })} + + ); +} diff --git a/src/components/molecules/TabSwitch/TabSwitch.model.ts b/src/components/molecules/TabSwitch/TabSwitch.model.ts new file mode 100644 index 000000000..8e27e093d --- /dev/null +++ b/src/components/molecules/TabSwitch/TabSwitch.model.ts @@ -0,0 +1,24 @@ +import { TThemeTypes } from '@/interface/TUiColors'; +import { TLayer } from '@/interface/TLayer'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; +import { ITabSwitchDetailsLabelIcon, ITabSwitchDetailsChildren } from '../FancyTabSwitchButton/FancyTabSwitchButton.model'; +import { TSpacings } from '../../../interface/TSpacings'; + +export interface ITabSwitchProps { + wide?: boolean; + size?: 'sm' | 'md' | 'lg'; + textColor?: TThemeTypes; + themeType?: TThemeTypes; + layer?: TLayer; + disabled?: boolean; + tabSpacing?: TSpacings; + values: ITabSwitchDetailsChildren[] & ITabSwitchDetailsLabelIcon[]; + rounded?: TBorderRadiusSizes; + direction?: 'horizontal' | 'vertical'; + outlined?: boolean; + id?: string; + currentSelect?: string; + iconAlign?: 'left' | 'right'; + activeColor?: TThemeTypes; + handler?: (value: string) => void; +} diff --git a/src/components/molecules/TabSwitch/TabSwitch.style.tsx b/src/components/molecules/TabSwitch/TabSwitch.style.tsx new file mode 100644 index 000000000..f6c20434a --- /dev/null +++ b/src/components/molecules/TabSwitch/TabSwitch.style.tsx @@ -0,0 +1,70 @@ +import { styled } from 'styled-components'; + +import { borderRadius, spacingPx } from '../../../design/theme/designSizes'; +import { TLayer } from '@/interface/TLayer'; +import { TTheme } from '@/interface/TTheme'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TSpacings } from '../../../interface/TSpacings'; +import themeStore from '../../../design/theme/themeStore/themeStore'; + +// Define the different sizes for the tab switch +const getSpacingFromTheme = themeStore.getState().theme.spacing; +export const tabSwitchSizes = { + sm: { + paddingComponent: '4px', + }, + md: { + paddingComponent: getSpacingFromTheme.xs, + }, + lg: { + paddingComponent: getSpacingFromTheme.sm, + }, +}; + +// Define the interface for the styled-component +export interface IFancyTabSwitchStyle { + $transparent?: boolean; + $wide?: boolean; + $outlinedBackgroundStrength?: number; + $tabSpacing?: TSpacings; + $direction?: 'horizontal' | 'vertical'; + $rounded?: TBorderRadiusSizes; + $padding?: keyof typeof tabSwitchSizes; + $outlined?: boolean; + theme: TTheme; + $layer?: TLayer; + $themeType?: TThemeTypes; +} + +// ----------------------------------------------------------- // +// ---------- The main UL element for the component ---------- // +// ----------------------------------------------------------- // +// Define the styled-component for the unordered list of the tab switch +export const ULButtonSwitchList = styled.ul` + display: ${({ $wide }) => ($wide ? 'grid' : 'inline-grid')}; + grid-auto-flow: ${({ $direction }) => ($direction === 'vertical' ? 'row' : 'column')}; + grid-auto-rows: 1fr; + grid-auto-columns: 1fr; + gap: ${({ $tabSpacing }) => ($tabSpacing ? spacingPx[$tabSpacing] : '0')}; + border-radius: ${({ $rounded }) => ($rounded ? borderRadius[$rounded] : '0')}; + ${({ $wide }) => $wide && `justify-content: space-between`}; + align-items: center; + margin: 0; + padding: 0; + & > * { + /* Selects all direct children of the parent */ + min-width: 0; + } +`; + +// ----------------------------------- // +// ---------- Other styled ---------- // +// ----------------------------------- // +// Define the styled-component for the list item wrapper +export const ItemWrapper = styled.li` + position: relative; + height: 100%; + width: 100%; + list-style: none; +`; diff --git a/src/components/molecules/TabSwitch/TabSwitch.tsx b/src/components/molecules/TabSwitch/TabSwitch.tsx new file mode 100644 index 000000000..bb5fe6a3b --- /dev/null +++ b/src/components/molecules/TabSwitch/TabSwitch.tsx @@ -0,0 +1,108 @@ +import React, { useRef, useState } from 'react'; + +import { ItemWrapper, ULButtonSwitchList, tabSwitchSizes } from './TabSwitch.style'; +import FancyTabSwitchButton from '../FancyTabSwitchButton/FancyTabSwitchButton'; +import { borderRadius } from '../../../design/theme/designSizes'; +import SwitchActiveIndicator from '../../atoms/SwitchActiveIndicator/SwitchActiveIndicator'; +import { ITabSwitchProps } from './TabSwitch.model'; + +// --------------------------------------------------------------------------- // +// ------------ The tap SwitchComponent to slect specifc values -------------- // +// --------------------------------------------------------------------------- // +export default function TabSwitch(props: ITabSwitchProps) { + const { + values, + layer, + themeType, + textColor, + size, + wide, + disabled, + tabSpacing, + rounded, + direction, + outlined, + currentSelect, + handler, + iconAlign, + activeColor, + } = props; + // Define the state for the currently selected tab + const buttonRefs = useRef[]>(values.map(() => React.createRef())); + const [currentSelected, setCurrentSelect] = useState(currentSelect); + + // Define the function to handle the selection of a tab + const radioChangeHandler = (position: string) => { + const currentItem = values.find((item) => item.itemKey === position); + setCurrentSelect(position); + handler && handler(currentItem?.itemKey!); + }; + + // This handles the navigation with the keyboard + const handleKeyDown = (event: React.KeyboardEvent, itemKey: string) => { + const currentIndex = values.findIndex((item) => item.itemKey === itemKey); + let newIndex = -1; + + if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { + newIndex = (currentIndex + 1) % values.length; + } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { + newIndex = (currentIndex - 1 + values.length) % values.length; + } + + if (newIndex !== -1) { + const newPosition = values[newIndex].itemKey; + radioChangeHandler(newPosition); + buttonRefs.current[newIndex].current?.focus(); + } + }; + + return ( + + {/* Generate a list item for each switch value */} + {values.map((item, i) => ( + + {/* Generate the button for the switch item */} + radioChangeHandler(item.itemKey)} + onKeyDown={(e) => handleKeyDown(e, item.itemKey)} + tabIndex={item.itemKey === currentSelected ? 0 : -1} // Manage focus + /> + {/* Generate the switch active indicator wich is a blob or underline */} + {i === 0 && ( + + )} + + ))} + + ); +} diff --git a/src/components/molecules/TextInput/TextInput.stories.tsx b/src/components/molecules/TextInput/TextInput.stories.tsx new file mode 100644 index 000000000..348996a26 --- /dev/null +++ b/src/components/molecules/TextInput/TextInput.stories.tsx @@ -0,0 +1,41 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import TextInput from './TextInput'; + +// Define metadata for the story +const meta = { + component: TextInput, + parameters: { + docs: { + description: { + component: 'SwipeUpDash is a simple horizontal line', + }, + }, + }, + // Define arguments for the story + argTypes: { + value: { + description: 'The value of the input.', + type: { name: 'string' }, + }, + align: { + description: 'The alignment of the text.', + control: { type: 'select' }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: {}, +}; diff --git a/src/components/molecules/TextInput/TextInput.tsx b/src/components/molecules/TextInput/TextInput.tsx new file mode 100644 index 000000000..483ff30e5 --- /dev/null +++ b/src/components/molecules/TextInput/TextInput.tsx @@ -0,0 +1,25 @@ +import React, { InputHTMLAttributes } from 'react'; + +import RawInput, { TRawInputAlign } from '../../atoms/RawInput/RawInput'; + +export interface ITextInputProps extends Omit, 'type'> { + value?: string | number; + align?: TRawInputAlign; + activeHandler?: (value: boolean) => void; +} + +export default function TextInput(props: ITextInputProps) { + const { value, activeHandler, onChange, align, ...htmlInputProps } = props; + + return ( + activeHandler && activeHandler(true)} + onBlur={() => activeHandler && activeHandler(false)} + $align={align} + {...htmlInputProps} + /> + ); +} diff --git a/src/components/molecules/TextInput/index.ts b/src/components/molecules/TextInput/index.ts new file mode 100644 index 000000000..137a1443c --- /dev/null +++ b/src/components/molecules/TextInput/index.ts @@ -0,0 +1 @@ +export { default as TextInput } from './TextInput'; diff --git a/src/components/organisms/FancyBoxSet/FancyBoxSet.stories.tsx b/src/components/organisms/FancyBoxSet/FancyBoxSet.stories.tsx new file mode 100644 index 000000000..9e2a320ee --- /dev/null +++ b/src/components/organisms/FancyBoxSet/FancyBoxSet.stories.tsx @@ -0,0 +1,123 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyBoxSet from './FancyBoxSet'; + +const meta = { + component: FancyBoxSet, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyBoxSet Renders a Styled Box with a list of Items and a label.', + }, + }, + }, + argTypes: { + displayLine: { + description: 'The Horizontal Line is displayed.', + type: { name: 'boolean' }, + defaultValue: { + summary: true, + }, + }, + label: { + description: 'TheBoySet has a label.', + type: { name: 'string' }, + defaultValue: { + summary: '', + }, + }, + alignLabel: { + description: 'The alignment of the label.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'left', + }, + }, + fontVariantLegend: { + description: 'The font variant of the label.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'h3', + }, + }, + disabled: { + description: 'If true, the Fieldset is disabled.', + type: { name: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + themeType: { + description: 'The theme type of the modal.', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the modal.', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 1, + }, + }, + outlined: { + description: 'If true, the modal is outlined.', + type: { name: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + outlinedBackgroundStrength: { + description: 'The background strength of the modal.', + control: { + type: 'range', + min: 0, + max: 1, + step: 0.1, + }, + defaultValue: { + summary: 1, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => ( + +

Hiii

+

Hiii

+

Hiii

+

Hiii

+

Hiii

+
+ ), + args: { + layer: 1, + themeType: 'primary', + displayLine: true, + label: 'Label', + alignLabel: 'left', + fontVariantLegend: 'h3', + disabled: false, + }, +}; diff --git a/src/components/organisms/FancyBoxSet/FancyBoxSet.tsx b/src/components/organisms/FancyBoxSet/FancyBoxSet.tsx new file mode 100644 index 000000000..9ce404132 --- /dev/null +++ b/src/components/organisms/FancyBoxSet/FancyBoxSet.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { Fieldset } from '@/components/molecules/Fieldset'; +import { FancyListBox } from '@/components/molecules/FancyListBox'; +import { FancyLine } from '@/components/atoms/FancyLine'; +import { TLayer } from '@/interface/TLayer'; + +// get props from the Fieldset and the FancyListBox +type TFieldSetProps = React.ComponentProps; +type TFancyListProps = React.ComponentProps; + +// combine the props and add the displayLine prop +type TFancyBoxSet = TFieldSetProps & TFancyListProps & { displayLine?: boolean }; +// --------------------------------------------------------------------------- // +// ----- The FancyBoxSet to render the ListBox with a Fieldset and a Line ---- // +// --------------------------------------------------------------------------- // +export default function FancyBoxSet(props: TFancyBoxSet) { + const { label, alignLabel, fontVariantLegend, disabled, children, themeType, displayLine, layer, ...HTMLProps } = { + ...defaultProp, + ...props, + }; + + // is needed to get the length of the children for displaying the line + const childArray = React.Children.toArray(children); + + return ( + // Give the list with the Fieldset a label +
+ {/* The FancyListbox gives the style */} + + {childArray.map((child, i) => ( + <> + {/* Merge the Item with the FancyLine to Seperate the items */} + {child} + {displayLine && childArray.length - 1 !== i && ( + + )} + + ))} + +
+ ); +} + +const defaultProp: TFancyBoxSet = { + layer: 1, + displayLine: true, +}; diff --git a/src/components/organisms/FancyBoxSet/index.ts b/src/components/organisms/FancyBoxSet/index.ts new file mode 100644 index 000000000..2fccca4df --- /dev/null +++ b/src/components/organisms/FancyBoxSet/index.ts @@ -0,0 +1 @@ +export { default as FancyBoxSet } from './FancyBoxSet'; diff --git a/src/components/organisms/FancyButton/FancyButton.stories.tsx b/src/components/organisms/FancyButton/FancyButton.stories.tsx new file mode 100644 index 000000000..c847c433e --- /dev/null +++ b/src/components/organisms/FancyButton/FancyButton.stories.tsx @@ -0,0 +1,130 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyButton from './FancyButton'; +import SVGChevronLeft from '../../icons/SVGChevronLeft/SVGChevronLeft'; + +const meta = { + component: FancyButton, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: This component is a button with a lot of props to change the style.', + }, + }, + }, + argTypes: { + themeType: { + description: 'This prop will change the color of the bar', + control: { + type: 'select', + }, + }, + layer: { + description: 'This prop will change the layer of the bar', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + outlined: { + description: 'This prop will change the Nutton to outlined', + control: { + type: 'boolean', + }, + }, + iconAlign: { + description: 'This prop will align the icon inside the button to the left or right from the text', + control: { + type: 'radio', + }, + defaultValue: { + summary: 'left', + }, + }, + borderRadius: { + description: 'This prop will change the borderRadius of the button', + control: { + type: 'select', + }, + }, + isLoading: { + description: 'This prop will change the button to loading', + control: { + type: 'boolean', + }, + }, + textColor: { + description: 'This prop will change the color of the text', + control: { + type: 'select', + }, + }, + hoverColor: { + description: 'This prop will change the color of the hover', + control: { + type: 'select', + }, + }, + wide: { + description: 'This prop will change the width of the button', + control: { + type: 'boolean', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + icon: SVGChevronLeft, + themeType: 'accent', + label: 'Button', + size: 'md', + align: 'center', + layer: 0, + outlined: false, + borderRadius: 'md', + isLoading: false, + wide: false, + }, +}; + +export const Outlined: Story = { + render: (args) => , + args: { + icon: SVGChevronLeft, + themeType: 'accent', + label: 'Button', + size: 'md', + align: 'center', + layer: 0, + outlined: true, + borderRadius: 'md', + isLoading: false, + wide: false, + }, +}; + +export const OneToOne: Story = { + render: (args) => , + args: { + icon: SVGChevronLeft, + themeType: 'accent', + size: 'md', + align: 'center', + layer: 0, + outlined: true, + borderRadius: 'md', + isLoading: false, + wide: false, + }, +}; diff --git a/src/components/organisms/FancyButton/FancyButton.style.tsx b/src/components/organisms/FancyButton/FancyButton.style.tsx new file mode 100644 index 000000000..18f530a65 --- /dev/null +++ b/src/components/organisms/FancyButton/FancyButton.style.tsx @@ -0,0 +1,36 @@ +import { css } from 'styled-components'; + +import { generateBorderRadiusForComponent } from '../../../design/designFunctions/generateBorderRadiusForComponent/generateBorderRadiusForComponent'; +import { generateComponentPadding } from '../../../design/designFunctions/generatePaddingForComponent/generatepaddingForComponent'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; + +const generate1To1Button = ($size: 'sm' | 'md' | 'lg', $outlined?: boolean) => { + //this makes the button a square (1/1) if there is no $label and a $icon + const padding = generateComponentPadding({ size: $size ?? 'md', borderThinkness: $outlined ? 1.2 : 0 }); + + return css` + aspect-ratio: 1/1; + justify-content: center; + ${padding} + `; +}; + +interface IGenerateFancyButton { + $size?: 'sm' | 'md' | 'lg'; + $outlined?: boolean; + $oneToOne?: boolean; + $borderRadius?: TBorderRadiusSizes; + $justifyContent?: 'flex-start' | 'flex-end' | 'center'; + $noPadding?: boolean; +} + +export const generateFancyButton = (props: IGenerateFancyButton) => { + const { $size, $borderRadius, $outlined, $oneToOne, $justifyContent, $noPadding } = props; + + return css` + justify-content: ${$justifyContent ?? 'center'}; + ${generateBorderRadiusForComponent($size, $borderRadius)}; + ${!$noPadding && generateComponentPadding({ size: $size ?? 'md', borderThinkness: $outlined ? 1.2 : 0, doublePaddingLeftRight: true })}; + ${$oneToOne && generate1To1Button($size ?? 'md', $outlined)}; + `; +}; diff --git a/src/components/organisms/FancyButton/FancyButton.tsx b/src/components/organisms/FancyButton/FancyButton.tsx new file mode 100644 index 000000000..86a627de9 --- /dev/null +++ b/src/components/organisms/FancyButton/FancyButton.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { css } from 'styled-components'; + +import LoadingSVGArrows from '../../atoms/LoadingSVGArrows/LoadingSVGArrows'; +import Button, { IButtonProps } from '../../molecules/Button/Button'; +import FancyContent from '../../molecules/FancyContent/FancyContent'; +import { generateFancyButton } from './FancyButton.style'; + +import { IButton } from '../../molecules/Button/Button'; +import { TTypography } from '@/interface/TTypography'; +import { TBorderRadiusSizes } from '@/interface/TBorderRadius'; + +const alignment = { + left: 'flex-start' as const, + right: 'flex-end' as const, + center: 'center' as const, +}; + +export type IFancyButtonProps = { + isLoading?: boolean; + label?: string; + align?: 'left' | 'right' | 'center'; + iconAlign?: 'left' | 'right'; + size?: 'sm' | 'md' | 'lg'; + borderRadius?: TBorderRadiusSizes; + oneToOne?: boolean; + icon?: React.ReactNode; + fontVariant?: TTypography; + noPadding?: boolean; +}; + +// --------------------------------------------------------------------------- // +// ---------- The Fancy Button has a bit more options than another ---------- // +// --------------------------------------------------------------------------- // +type IFancyButton = IFancyButtonProps & IButton; +export default function FancyButton(props: IFancyButton) { + const { icon, label, isLoading, iconAlign, size, align, externalStyle, oneToOne, noPadding, fontVariant, borderRadius, ...buttonProps } = + { + ...defaultProps, + ...props, + }; + + const generateFancyStyle = generateFancyButton({ + $size: size, + $borderRadius: borderRadius, + $oneToOne: oneToOne || (Boolean(!label) && Boolean(icon)), + $outlined: props.outlined, + $justifyContent: alignment[align ?? 'center'], + $noPadding: noPadding, + }); + + // handle icon alignment + const alignIcon = iconAlign === 'left' ? 'row' : 'row-reverse'; + + return ( + + ); +} + +const defaultProps: IFancyButton = { + themeType: 'accent', + size: 'lg', + align: 'center', +}; diff --git a/src/components/organisms/FancyButton/IFancyButton.model.ts b/src/components/organisms/FancyButton/IFancyButton.model.ts new file mode 100644 index 000000000..089c23e83 --- /dev/null +++ b/src/components/organisms/FancyButton/IFancyButton.model.ts @@ -0,0 +1,12 @@ +import { ButtonHTMLAttributes } from 'react'; + +type NativeButtonAttributes = Omit, 'type'>; + +type IFancyButton = { + isLoading?: boolean; + align?: 'left' | 'right' | 'center'; + iconAlign?: 'left' | 'right'; + icon?: JSX.Element; +}; + +export type IFancyButtonProps = IFancyButton & NativeButtonAttributes; diff --git a/src/components/organisms/FancyButton/index.ts b/src/components/organisms/FancyButton/index.ts new file mode 100644 index 000000000..7af99e192 --- /dev/null +++ b/src/components/organisms/FancyButton/index.ts @@ -0,0 +1 @@ +export { default as FancyButton } from './FancyButton'; diff --git a/src/components/organisms/FancyChip/FancyChip.model.ts b/src/components/organisms/FancyChip/FancyChip.model.ts new file mode 100644 index 000000000..32a6f4684 --- /dev/null +++ b/src/components/organisms/FancyChip/FancyChip.model.ts @@ -0,0 +1 @@ +// Define the interface for the base chip props diff --git a/src/components/organisms/FancyChip/FancyChip.stories.tsx b/src/components/organisms/FancyChip/FancyChip.stories.tsx new file mode 100644 index 000000000..c01a3914b --- /dev/null +++ b/src/components/organisms/FancyChip/FancyChip.stories.tsx @@ -0,0 +1,150 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyChip from './FancyChip'; +import SVGChevronLeft from '../../icons/SVGChevronLeft/SVGChevronLeft'; + +const meta = { + component: FancyChip, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A chip is a component that displays a label and optionally an icon / image and delete button. ', + }, + }, + }, + argTypes: { + size: { + description: 'The size of the button.', + control: { + type: 'select', + }, + }, + themeType: { + description: 'The theme type of the button.', + control: { + type: 'select', + }, + }, + isActive: { + description: 'The active state of the button.', + control: { + type: 'boolean', + }, + }, + outlined: { + description: 'The outlined state of the button.', + control: { + type: 'boolean', + }, + }, + onDelete: { + description: 'The onDelete event of the button.', + control: { + type: 'function', + }, + }, + onClick: { + description: 'The onClick event of the button.', + control: { + type: 'function', + }, + }, + textColor: { + description: 'The text color of the button.', + control: { + type: 'select', + }, + }, + textLayer: { + description: 'The text layer of the button.', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + }, + layer: { + description: 'The layer of the button.', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + }, + image: { + description: 'The image of the button.', + control: { + type: 'text', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + label: 'Chip', + layer: 3, + icon: false, + size: 'sm', + outlined: false, + onDelete: undefined, + themeType: 'primary', + isActive: false, + }, +}; + +export const WithDelete: Story = { + render: (args) => , + args: { + label: 'Chip', + layer: 3, + icon: false, + size: 'sm', + outlined: false, + onDelete: undefined, + themeType: 'primary', + isActive: false, + }, +}; + +export const WithImage: Story = { + render: (args) => , + args: { + label: 'Chip', + layer: 3, + icon: false, + size: 'sm', + outlined: false, + themeType: 'primary', + isActive: false, + image: 'https://www.az-online.de/bilder/2019/08/23/12938342/2113799823-tobias-rester-2tyMMSkM2R73.jpg', + onDelete: () => { + console.log('DELETE'); + }, + }, +}; + +export const WithIcon: Story = { + render: (args) => , + args: { + label: 'Chip', + layer: 3, + size: 'sm', + outlined: false, + themeType: 'primary', + isActive: false, + icon: SVGChevronLeft, + onDelete: () => { + console.log('DELETE'); + }, + }, +}; diff --git a/src/components/organisms/FancyChip/FancyChip.style.tsx b/src/components/organisms/FancyChip/FancyChip.style.tsx new file mode 100644 index 000000000..141947320 --- /dev/null +++ b/src/components/organisms/FancyChip/FancyChip.style.tsx @@ -0,0 +1,107 @@ +import styled, { css } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; +import themeStore from '@/design/theme/themeStore/themeStore'; + +// Define the type for the spacing position +export type TSpacingPosition = 'left' | 'right' | 'booth'; + +const getSpacingFromTheme = themeStore.getState().theme.spacing; +const spacings = { + xs: parseFloat(getSpacingFromTheme.xs), + sm: parseFloat(getSpacingFromTheme.sm), + md: parseFloat(getSpacingFromTheme.md), +}; + +export const sizes = { + sm: { + height: '24px', + deleteButtonSize: '14px', + padding: spacings.xs, + paddingRight: spacings.xs, + paddingLeft: spacings.xs, + icon: '14px', + }, + md: { + height: '32px', + deleteButtonSize: '14px', + padding: spacings.sm, + paddingRight: spacings.sm, + paddingLeft: spacings.sm, + icon: '18px', + }, + lg: { + height: '38px', + deleteButtonSize: '14px', + padding: spacings.sm, + paddingRight: spacings.sm, + paddingLeft: spacings.md, + icon: '20px', + }, +}; + +// Define a function to generate the spacing based on the spacing position +interface IGenerateSpacing { + spacingPosition?: TSpacingPosition; + size?: keyof typeof sizes; +} +export const generateSpacing = ({ spacingPosition, size }: IGenerateSpacing) => { + const pickedSize = size ? size : 'md'; + + switch (spacingPosition) { + case 'left': + return css` + padding-left: ${sizes[pickedSize].paddingLeft + 2 + 'px'}; + `; + case 'right': + return css` + padding-right: ${sizes[pickedSize].paddingRight + 2 + 'px'}; + `; + case 'booth': + return css` + padding: 0 ${sizes[pickedSize].padding + 2 + 'px'}; + `; + default: + return null; + } +}; + +// Define the styled component for the X button +interface IXButton { + $size?: keyof typeof sizes; + theme: TTheme; +} +export const StyledXButton = styled.button` + border: none; + background-color: transparent; + cursor: pointer; + color: inherit; + background-color: transparent; + padding: 0; + line-height: 1; + display: flex; + align-items: center; + margin-left: ${({ theme }) => parseFloat(theme.spacing.xs) + 2 + 'px'}; + + svg { + width: ${({ $size }) => ($size ? sizes[$size].deleteButtonSize : sizes.md.deleteButtonSize)}; + height: ${({ $size }) => ($size ? sizes[$size].deleteButtonSize : sizes.md.deleteButtonSize)}; + } +`; + +// Define the styled component for the wrapper image +export const WrapperImage = styled.div<{ theme: TTheme }>` + height: 100%; + aspect-ratio: 1/1; + line-height: 0; + margin-right: ${({ theme }) => theme.spacing.xs}; + display: flex; + align-items: center; + + img { + width: 95%; + height: 95%; + object-fit: cover; + border-radius: 50%; + } +`; diff --git a/src/components/organisms/FancyChip/FancyChip.tsx b/src/components/organisms/FancyChip/FancyChip.tsx new file mode 100644 index 000000000..04dead1fd --- /dev/null +++ b/src/components/organisms/FancyChip/FancyChip.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { css } from 'styled-components'; + +import SVGXCircle from '../../icons/SVGXCircle/SVGXCircle'; +import FancyContent from '../../molecules/FancyContent/FancyContent'; + +import { StyledXButton, TSpacingPosition, WrapperImage, generateSpacing } from './FancyChip.style'; +import Chip from '../../molecules/Chip/Chip'; +import { IStyledChip } from '../../molecules/Chip/Chip'; + +export type TChipProps = { + label?: string; + isActive?: boolean; + icon?: React.ReactNode; + image?: string; + onDelete?: () => void; +} & IStyledChip & + React.HTMLAttributes; + +// Define the Chip component +export default function FancyChip(props: TChipProps) { + const { + label, + onDelete, + icon, + image, + size, + outlined, + themeType, + layer, + textColor, + textLayer, + isActive, + externalStyle, + ...HTMLAttributes + } = { + ...defaultProps, + ...props, + }; + + // Define a function to calculate the spacing position for the chip + const clacPosition = (): TSpacingPosition => { + if (icon && onDelete) return 'booth'; + if (image && onDelete) return 'right'; + if (image) return 'right'; + if (onDelete) return 'booth'; + if (icon) return 'booth'; + return 'booth'; + }; + + // Calculate the spacing position for the chip + const getCalcPosition = clacPosition(); + 3; + + // Render the Chip component with the appropriate props + return ( + + {image && ( + + chip + + )} + + + {icon && {icon}} + {label && ( + + {label} + + )} + + + {onDelete && ( + ) => { + e.stopPropagation(); + onDelete && onDelete(); + }} + > + + + )} + + ); +} + +// Define the default props for the Chip component +const defaultProps = { + size: 'md' as const, +}; diff --git a/src/components/organisms/FancyChip/index.ts b/src/components/organisms/FancyChip/index.ts new file mode 100644 index 000000000..ad4ade32b --- /dev/null +++ b/src/components/organisms/FancyChip/index.ts @@ -0,0 +1 @@ +export { default as FancyChip } from './FancyChip'; diff --git a/src/components/organisms/FancyChipList/FancyChipList.stories.tsx b/src/components/organisms/FancyChipList/FancyChipList.stories.tsx new file mode 100644 index 000000000..2339ff368 --- /dev/null +++ b/src/components/organisms/FancyChipList/FancyChipList.stories.tsx @@ -0,0 +1,74 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyChipList from './FancyChipList'; + +const meta = { + component: FancyChipList, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A Component that act like a tag list. It can be used to display tags/chips for some Informations.', + }, + }, + }, + argTypes: { + size: { + description: 'Size of the ChipList', + control: { + type: 'select', + options: ['sm', 'md', 'lg'], + }, + }, + chips: { + description: 'The Chips that should be displayed', + control: { + type: 'array', + }, + }, + label: { + description: 'The Label of the ChipList', + control: { + type: 'text', + }, + }, + systemInformation: { + description: 'The SystemInformation that should be displayed', + control: { + type: 'select', + }, + }, + outlined: { + description: 'If the ChipList should be outlined', + control: { + type: 'boolean', + }, + }, + layer: { + description: 'The Layer of the ChipList', + control: { + type: 'select', + }, + }, + themeType: { + description: 'The ThemeType of the ChipList', + control: { + type: 'select', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + chips: ['12', '1212', '1212', '121221', '121212211', '12121212', '12121212'], + size: 'md', + label: 'ChipList', + }, +}; diff --git a/src/components/organisms/FancyChipList/FancyChipList.style.tsx b/src/components/organisms/FancyChipList/FancyChipList.style.tsx new file mode 100644 index 000000000..b8456e039 --- /dev/null +++ b/src/components/organisms/FancyChipList/FancyChipList.style.tsx @@ -0,0 +1,25 @@ +import { styled } from 'styled-components'; + +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { getTextColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { IgenerateThemeForCard } from '../../../design/designFunctions/generateThemeForCard/generateThemeForCard'; +import { TTheme } from '@/interface/TTheme'; + +type IChipContainer = IStyledPrefixAndPicker & { theme: TTheme }; +export const InputLi = styled.li` + display: flex; + flex: 1; + + input { + border: none; + padding: 5px; + font-size: 14px; + background-color: transparent; + color: ${({ $themeType, theme }) => getTextColor({ $themeType: $themeType ?? 'primary', theme, turnColorTheme: true })}; + + &:focus { + outline: none; + width: 1ch; + } + } +`; diff --git a/src/components/organisms/FancyChipList/FancyChipList.tsx b/src/components/organisms/FancyChipList/FancyChipList.tsx new file mode 100644 index 000000000..dcaae968b --- /dev/null +++ b/src/components/organisms/FancyChipList/FancyChipList.tsx @@ -0,0 +1,134 @@ +import React, { KeyboardEvent, useEffect, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; + +import { Fieldset } from '../../molecules/Fieldset'; +import { TLayer } from '@/interface/TLayer'; +import ChipList from '../../molecules/ChipList/ChipList'; +import { FancyChip } from '../FancyChip'; +import { InputLi } from './FancyChipList.style'; +import { TThemeTypes } from '@/interface/TUiColors'; +import { TUiColorsSystemMessage } from '@/interface/TUiColors'; + +// Defining the interface for the component's props +export interface ChipListProps { + themeType?: Exclude; + systemInformation?: TUiColorsSystemMessage; + layer?: TLayer; + outlined?: boolean; + chips?: string[]; + inputPlaceholder?: string; + label?: string; + size?: 'sm' | 'md' | 'lg'; +} + +// Define the type for the chips with an id and label +type TChip = { + id: string; + label: string; +}; + +// The FancyChipList component definition +export default function FancyChipList(props: ChipListProps) { + // Destructure props and provide default values from defaultProps + const { themeType, layer, outlined, chips, inputPlaceholder, label, size, systemInformation } = { ...defaultProps, ...props }; + + // State to hold chips with unique identifiers and input values + const [chipsWithKeys, setChipsWithKeys] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [focusedChip, setFocusedChip] = useState(''); + + // Effect to initialize chipsWithKeys state when the chips prop changes + useEffect(() => { + setChipsWithKeys(chips!.map((label) => ({ id: uuidv4(), label }))); + }, [chips]); + + // Function to add a new chip + const addChip = (label: string) => { + setChipsWithKeys((prev) => [...prev, { id: uuidv4(), label }]); + }; + + // Function to delete a chip, curried to provide the chip id + const deleteChip = (chipToDelete: string) => () => { + setChipsWithKeys(chipsWithKeys.filter((chip) => chip.id !== chipToDelete)); + }; + + // Function to set the focused chip + const hanleChipFocus = (chipId: string) => () => { + setFocusedChip(chipId); + }; + + // Function to update the label of a chip + const updateChipLabel = (chipId: string, newLabel: string) => { + setChipsWithKeys((prev) => prev.map((chip) => (chip.id === chipId ? { ...chip, label: newLabel } : chip))); + }; + + // Function to handle editing of a chip label through keyboard events + const handleChipEdit = (chipId: string) => (e: React.KeyboardEvent) => { + if ((e.key === 'Enter' || e.key === ',') && chipId === focusedChip) { + e.preventDefault(); + updateChipLabel(chipId, (e.target as HTMLDivElement).innerText); + setFocusedChip(''); + (e.target as HTMLDivElement).blur(); + } + }; + + // Function to handle input keydown events for adding and removing chips + const handleInputKeyDown = (event: KeyboardEvent) => { + const val = event.currentTarget.value.trim(); + if ((event.key === 'Enter' || event.key === ',') && val) { + event.preventDefault(); + addChip(val); + setInputValue(''); + } else if (event.key === 'Backspace' && !val && chipsWithKeys.length) { + // On backspace, if input is empty, remove the last chip and add its label to the input + const lastChip = chipsWithKeys[chipsWithKeys.length - 1]; + setChipsWithKeys((prev) => prev.slice(0, -1)); + setInputValue(lastChip.label); + } + }; + + // Function to handle changes in the input field + const handleInputChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + }; + + // The component's JSX structure for rendering + return ( +
+ + {/* // Mapping through each chip in the state to render a FancyChip */} + {chipsWithKeys.map((chip, index) => ( + + ))} + + + + +
+ ); +} +// Default properties for the FancyChipList component +const defaultProps: ChipListProps = { + themeType: 'primary', + layer: 1, + chips: [], + size: 'md', +}; diff --git a/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.mdx b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.mdx new file mode 100644 index 000000000..add8264d7 --- /dev/null +++ b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.mdx @@ -0,0 +1,73 @@ +## FancyCodeVerificationInput Documentation + +### Overview + +The `FancyCodeVerificationInput` is a sophisticated input component tailored for single-input entries, such as verification codes. Offering an enhanced user experience, it not only transitions automatically to the subsequent input after a key press but also supports copy-paste functionality via `Ctrl + V`. Upon completing the code entry, the handler is activated, returning the entered value and its status, thus making it versatile and useful for various applications requiring code verification. + +### Integration Steps + +1. **Connect the Component to your Application** + + Import the component using the following: + + `import FancyCodeVerificationInput from './FancyCodeVerificationInput';` + +2. **Initialize and Handle Input State** + + The `FancyCodeVerificationInput` relies on properties such as `isSuccess` and `errorMessage` to manage its internal state. It's essential to set up handlers to determine input success and display potential error messages. + +### Additional Features + +The `FancyCodeVerificationInput` component accepts several props to enhance its functionality: + +- **handler**: Callback function invoked with the input value. +- **length**: Determines the total number of input fields, defaulting to 6. +- **automaticCase**: Determines whether input should be automatically converted to uppercase, lowercase, or remain unchanged. +- **debounceTime**: Provides a debounced input experience with a delay in milliseconds before the handler is called. +- **isSuccess**: Denotes whether the current input is successful. +- **errorMessage**: Allows for displaying custom error messages. + +### Example Usage + +To best demonstrate the component's functionality, here's a simplified usage: + +```React +import React, { useEffect, useState } from 'react'; +import FancyCodeVerificationInput from './FancyCodeVerificationInput'; + +const MyVerificationComponent = () => { + const [isSuccess, setIsSuccess] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const handleVerification = (value) => { + if (value === '123456') { + setIsSuccess(true); + setErrorMessage(''); + } else { + setIsSuccess(false); + setErrorMessage('Incorrect code'); + } + }; + + return ( + + ); +}; + +export default MyVerificationComponent; +``` + +In this example, the `handleVerification` function checks if the entered code matches '123456'. Depending on the match, the input's success status and potential error message are updated. + +### Customization + +The `FancyCodeVerificationInput` component offers a wide range of customization options. Props such as `length`, `automaticCase`, and `debounceTime` allow you to adjust the input's behavior and appearance. Furthermore, the component can be styled using CSS to fit the aesthetic of your application. + +### Conclusion + +With its rich set of features and customization options, the `FancyCodeVerificationInput` component stands out as an ideal choice for applications requiring code verification, providing an enhanced and seamless user experience. diff --git a/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.stories.tsx b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.stories.tsx new file mode 100644 index 000000000..3234cb9ba --- /dev/null +++ b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.stories.tsx @@ -0,0 +1,120 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyCodeVerificationInput from './FancyCodeVerificationInput'; +import React, { useEffect } from 'react'; + +// Define metadata for the story +const meta = { + component: FancyCodeVerificationInput, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyCodeVerificationInput component is for single input like a verification code', + }, + }, + }, + // Define arguments for the story + argTypes: { + handler: { + description: 'Callback function for the search value', + control: { + type: 'function', + }, + }, + length: { + description: 'The length of the input fields', + control: { + type: 'number', + }, + defaultValue: { + summary: '6', + }, + }, + automaticCase: { + description: 'If the input should be upper or lower case', + control: { + type: 'radio', + options: ['upper', 'lower', undefined], + }, + defaultValue: { + summary: undefined, + }, + }, + debounceTime: { + description: 'The debounce time for the handler', + control: { + type: 'number', + }, + defaultValue: { + summary: 700, + }, + }, + isSuccess: { + description: 'If the input is success', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + errorMessage: { + description: 'The error message', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + }, + // Add tags to the story +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +type PropsType = React.ComponentProps; + +function HelperComponent(props: PropsType) { + const [errorMessage, setErrorMessage] = React.useState(''); + const [isSuccess, setIsSuccess] = React.useState(false); + + useEffect(() => { + setIsSuccess(Boolean(props.isSuccess)); + setErrorMessage(props.errorMessage || ''); + }, [props.isSuccess, props.errorMessage]); + + return ( + { + console.log(value); + if (value === '123456') { + setIsSuccess(true); + setErrorMessage(''); + } else { + setIsSuccess(false); + } + }} + /> + ); +} + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + length: 6, + isSuccess: false, + errorMessage: '', + automaticCase: undefined, + debounceTime: 700, + }, +}; diff --git a/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.style.tsx b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.style.tsx new file mode 100644 index 000000000..d76039287 --- /dev/null +++ b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.style.tsx @@ -0,0 +1,43 @@ +import { styled, css } from 'styled-components'; + +import { fontSize } from '../../../design/theme/designSizes'; +import { TTheme } from '@/interface/TTheme'; + +export const WarpperComponent = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; + +export const MessageContainer = styled.div<{ theme: TTheme }>` + display: flex; + flex-direction: column; + align-items: center; + margin-top: ${({ theme }) => theme.spacing.sm}; +`; + +export const Message = styled.div<{ $isError?: boolean; theme: TTheme }>` + color: ${({ theme }) => theme.colors.secondary[0]}; + font-size: ${fontSize.sm}; + max-height: 0; + visibility: hidden; + overflow: hidden; + opacity: 0; + + ${({ $isError, theme }) => + $isError && + css` + color: ${theme.colors.error[0]}; + max-height: 100px; + visibility: visible; + opacity: 1; + `} + + transition: opacity 0.3s ease-in-out, max-height 0.3s ease-in-out, visibility 0.3s ease-in-out; +`; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; diff --git a/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.tsx b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.tsx new file mode 100644 index 000000000..ef84a8500 --- /dev/null +++ b/src/components/organisms/FancyCodeVerificationInput/FancyCodeVerificationInput.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect, useRef } from 'react'; + +import SingleInputs from '../../molecules/SingleInputs/SingleInputs'; +import { Container, WarpperComponent, Message, MessageContainer } from './FancyCodeVerificationInput.style'; +import { Typography } from '../../atoms/Typography'; + +// --------------------------------------------------------------------------- // +// -The main FancySinlgeInput Componet wich handle the apicall and the sattus- // +// --------------------------------------------------------------------------- // +interface IFancySingleInputs { + length?: number; + errorMessage?: string; + isSuccess?: boolean; + handler?: (value: string) => void; + automaticCase?: 'upper' | 'lower'; + debounceTime?: number; +} +export default function FancyCodeVerificationInput(props: IFancySingleInputs) { + const { length, errorMessage, isSuccess, handler, automaticCase, debounceTime } = { ...defaultProps, ...props }; + const [inputValue, setInputValue] = useState(''); + const debounceTimeoutRef = useRef(); + + // hanldes the input value change and validate it with the api + useEffect(() => { + if (inputValue.length === length) { + if (debounceTimeoutRef.current) { + clearTimeout(debounceTimeoutRef.current); + } + debounceTimeoutRef.current = setTimeout(() => handler && handler(inputValue), debounceTime || 700); + } + }, [inputValue, handler, length, debounceTime]); + + const valueHandler = (value: string) => { + setInputValue(value); + }; + + return ( + + + + + + {errorMessage} + + + + + ); +} + +const defaultProps: IFancySingleInputs = { + length: 6, +}; diff --git a/src/components/organisms/FancyCodeVerificationInput/index.ts b/src/components/organisms/FancyCodeVerificationInput/index.ts new file mode 100644 index 000000000..d70254aba --- /dev/null +++ b/src/components/organisms/FancyCodeVerificationInput/index.ts @@ -0,0 +1 @@ +export { default as FancyCodeVerificationInput } from './FancyCodeVerificationInput'; diff --git a/src/components/organisms/FancyColorPicker/FancyColorPicker.stories.tsx b/src/components/organisms/FancyColorPicker/FancyColorPicker.stories.tsx new file mode 100644 index 000000000..f273318b2 --- /dev/null +++ b/src/components/organisms/FancyColorPicker/FancyColorPicker.stories.tsx @@ -0,0 +1,71 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyColorPicker from './FancyColorPicker'; + +// Define metadata for the story +const meta = { + component: FancyColorPicker, + parameters: { + docs: { + description: { + component: + 'Smart-Comonent: A Color Picker component to pick a color from a color area, hue slider, opacity slider and color output', + }, + }, + }, + // Define arguments for the story + argTypes: { + colorArea: { + description: 'Show the color area', + control: { type: 'boolean' }, + }, + hueSlider: { + description: 'Show the hue slider', + control: { type: 'boolean' }, + }, + opacitySlider: { + description: 'Show the opacity slider', + control: { type: 'boolean' }, + }, + colorOutput: { + description: 'Show the color output', + control: { type: 'boolean' }, + }, + displayColor: { + description: 'Show the color display', + control: { type: 'boolean' }, + }, + inputColor: { + description: 'The initial color', + control: { type: 'text' }, + }, + outputFormat: { + description: 'The output color format', + control: { type: 'radio' }, + options: ['hsl', 'hex', 'rgb', 'rgba', 'hsla', 'hexa'], + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + colorArea: true, + hueSlider: true, + opacitySlider: true, + colorOutput: true, + displayColor: true, + inputColor: 'hsl(0, 100%, 50%)', + outputFormat: 'hsl', + }, +}; diff --git a/src/components/organisms/FancyColorPicker/FancyColorPicker.tsx b/src/components/organisms/FancyColorPicker/FancyColorPicker.tsx new file mode 100644 index 000000000..eacdfb773 --- /dev/null +++ b/src/components/organisms/FancyColorPicker/FancyColorPicker.tsx @@ -0,0 +1,103 @@ +import React, { useCallback, useEffect, useState } from 'react'; + +import Color from 'color'; +import { styled } from 'styled-components'; + +import ColorDisplay from '../../atoms/ColorDisplay/ColorDisplay'; +import ColorArea from '../../molecules/FancyColorArea/FancyColorArea'; +import FancyHueSlider from '../../molecules/FancyHueSlider/FancyHueSlider'; +import FancyOpacitySlider from '../../molecules/FancyOpacitySlider/FancyOpacitySlider'; +import FancyColorOutput from '../../molecules/FancyColorOutput/FancyColorOutput'; +import { emitSelectedColorChange } from './colorPickerUtils'; +import { IColorFormat } from '../../utils/variables/colorFormats'; +import { TTheme } from '../../../interface/TTheme'; + +const Wrapper = styled.div<{ theme: TTheme }>` + width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing.md}; +`; + +// --------------------------------------------------------------------------- // +// ------------------- The main ColorPicker Component ------------------------ // +// --------------------------------------------------------------------------- // +interface IColorPicker { + outputFormat?: IColorFormat; + colorArea?: boolean; + opacitySlider?: boolean; + hueSlider?: boolean; + colorOutput?: boolean; + displayColor?: boolean; + inputColor?: string; + handler?: (color: string) => void; +} +export default function FanyColorPicker(props: IColorPicker) { + const { colorArea, hueSlider, opacitySlider, colorOutput, outputFormat, displayColor, inputColor, handler } = { + ...defaultProps, + ...props, + }; + + const [displayColorValue, setDisplayColorValue] = useState(Color(inputColor ? inputColor : 'hsl(0, 100%, 50%)')); + const [rawColor, setRawColor] = useState(Color('hsl(0, 100%, 50%)')); + const [opacity, setOpacity] = useState(1); + const [hue, setHue] = useState(0); + const [colorType, setColorType] = useState('hsl'); + + //create a calculated main color and use the normal only for display (flicker on the color area) + //this sets the main color that will be used in the parent component=> { + const calculateGiveBackColor = useCallback(() => { + return emitSelectedColorChange({ color: rawColor, opacity, outputFormat }); + }, [rawColor, opacity, outputFormat]); + + handler && handler(calculateGiveBackColor()); + + //this function is handle the color change in the child FancyColorOutput component + useEffect(() => { + const calcDisplayColor = emitSelectedColorChange({ color: rawColor, opacity, outputFormat: colorType }); + setDisplayColorValue(calcDisplayColor); + }, [colorType, hue, rawColor, opacity, setDisplayColorValue]); + + useEffect(() => { + setRawColor(Color(rawColor).hue(hue)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hue]); + + useEffect(() => { + if (inputColor) { + setRawColor(Color(inputColor)); + setOpacity(Color(inputColor).alpha()); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + {displayColor && } + {colorArea && } + {hueSlider && } + {opacitySlider && } + {colorOutput && ( + + )} + + ); +} + +// Define defaultProps for ColorPicker +const defaultProps: IColorPicker = { + outputFormat: 'hex', + colorArea: true, + opacitySlider: true, + hueSlider: true, + colorOutput: true, + displayColor: true, + inputColor: '#000', +}; diff --git a/src/components/organisms/FancyColorPicker/colorPickerUtils.ts b/src/components/organisms/FancyColorPicker/colorPickerUtils.ts new file mode 100644 index 000000000..0c78ec128 --- /dev/null +++ b/src/components/organisms/FancyColorPicker/colorPickerUtils.ts @@ -0,0 +1,47 @@ +import Color from 'color'; + +interface IColorPickerUtils { + color: Color; + opacity: number; + outputFormat?: 'hex' | 'hexa' | 'rgb' | 'rgba' | 'hsl' | 'hsla'; +} + +export const emitSelectedColorChange = ({ color, opacity, outputFormat }: IColorPickerUtils) => { + const colorObj = Color(color).alpha(opacity); + let outputColor; + + switch (outputFormat) { + case 'rgb': + outputColor = colorObj.rgb().string(); + break; + case 'rgba': { + const { r, g, b } = colorObj.rgb().object(); + outputColor = `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${opacity})`; + break; + } + case 'hsl': { + const { h, s, l } = colorObj.hsl().object(); + outputColor = `hsl(${Math.round(h)}, ${Math.round(s)}%, ${Math.round(l)}%)`; + break; + } + case 'hsla': { + const { h, s, l } = colorObj.hsl().object(); + outputColor = `hsla(${Math.round(h)}, ${Math.round(s)}%, ${Math.round(l)}%, ${opacity})`; + break; + } + case 'hex': + outputColor = colorObj.hex(); + break; + case 'hexa': + outputColor = + colorObj.hex() + + Math.round(opacity * 255) + .toString(16) + .padStart(2, '0'); + break; + default: + outputColor = colorObj.hsl().string(); + } + + return outputColor; +}; diff --git a/src/components/organisms/FancyColorPicker/index.ts b/src/components/organisms/FancyColorPicker/index.ts new file mode 100644 index 000000000..069da9436 --- /dev/null +++ b/src/components/organisms/FancyColorPicker/index.ts @@ -0,0 +1 @@ +export { default as FancyColorPicker } from './FancyColorPicker'; diff --git a/src/components/organisms/FancyDateInput/FancyDateInput.stories.tsx b/src/components/organisms/FancyDateInput/FancyDateInput.stories.tsx new file mode 100644 index 000000000..f61ee6ebc --- /dev/null +++ b/src/components/organisms/FancyDateInput/FancyDateInput.stories.tsx @@ -0,0 +1,157 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyDateInput from './FancyDateInput'; + +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// Define metadata for the story +const meta = { + component: FancyDateInput, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A fancy date input with label, icon and error message and more', + }, + }, + }, + // Define arguments for the story + argTypes: { + label: { + description: 'Label for the input', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + align: { + description: 'Alignment of the label', + control: { + type: 'select', + }, + defaultValue: { + summary: 'left', + }, + }, + disabled: { + description: 'Disable the input', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + themeType: { + description: 'Theme type of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'Layer of the input', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 4, + }, + }, + errorMessage: { + description: 'Error message to be displayed', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + activeHandler: { + description: 'Handler for the input', + control: { + type: 'function', + }, + }, + placeholder: { + description: 'Placeholder for the input', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + icon: { + description: 'Icon for the input', + }, + isActive: { + description: 'Active state of the input', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + value: { + description: 'Value of the input', + control: { + type: 'text', + }, + }, + type: { + description: 'Type of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'date', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + label: 'Date', + align: 'left', + disabled: false, + placeholder: new Date().toLocaleDateString(), + themeType: 'secondary', + layer: 4, + value: '2023-10-26', + icon: , + activeHandler: (value: boolean) => console.log(value), + }, +}; + +export const WithError: Story = { + render: (args) => , + args: { + label: 'Date', + align: 'left', + disabled: false, + themeType: 'secondary', + layer: 4, + errorMessage: 'This is an error message', + activeHandler: (value: boolean) => console.log(value), + }, +}; diff --git a/src/components/organisms/FancyDateInput/FancyDateInput.style.tsx b/src/components/organisms/FancyDateInput/FancyDateInput.style.tsx new file mode 100644 index 000000000..68240ca43 --- /dev/null +++ b/src/components/organisms/FancyDateInput/FancyDateInput.style.tsx @@ -0,0 +1,48 @@ +import { css, styled } from 'styled-components'; +import RawInput, { IRawInput } from '../../atoms/RawInput/RawInput'; +import { getBackgroundColor } from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TLayer } from '@/interface/TLayer'; +import { IDateInputProps } from '../../molecules/DateInput/DateInput'; +import simpleColorTransition from '../../../design/designFunctions/simpleColorTransition/simpleTransition'; +import { TThemeTypes } from '@/interface/TUiColors'; + +interface IRawInputWrapper extends IRawInput { + value?: string; + $themeType?: TThemeTypes; + $layer?: TLayer; +} +export const StyledDatePicker = styled(RawInput)` + color: ${({ value }) => (value ? '' : 'transparent')}; + transition: color 0.3s ease-in; + + /* This renders a Placerholder in Text when its needed */ + ${({ placeholder, $themeType = 'secondary', $layer = 4, value, theme, align }) => { + if (placeholder && !value) { + return css` + &:not(:focus):before { + content: attr(placeholder); + width: 100%; + text-align: ${align}; + color: ${getBackgroundColor({ theme, $themeType, $layer })}; + position: absolute; + transition: all 0.3s ease-in-out; + pointer-events: none; + } + `; + } + }} + + /* This renders the calendar Icon with the color theme */ + &::-webkit-calendar-picker-indicator { + ${simpleColorTransition} + + ${({ theme, $themeType = 'secondary', $layer = 4 }) => css` + background-image: ${`url('data:image/svg+xml;utf8,')`}; + `}; + } +`; diff --git a/src/components/organisms/FancyDateInput/FancyDateInput.tsx b/src/components/organisms/FancyDateInput/FancyDateInput.tsx new file mode 100644 index 000000000..e69b9a60d --- /dev/null +++ b/src/components/organisms/FancyDateInput/FancyDateInput.tsx @@ -0,0 +1,53 @@ +import React, { useId, useState } from 'react'; + +import DateInput, { IDateInputPropsWithNativeAttrs } from '../../molecules/DateInput/DateInput'; +import InputWrapper, { IInputWrapperUserInputProps } from '../../molecules/InputWrapper/InputWrapper'; + +type IFancyDateInput = Omit & IDateInputPropsWithNativeAttrs; +// --------------------------------------------------------------------------- // +// ----The TextInput Comonent with surrounding icon, label and underline ----- // +// --------------------------------------------------------------------------- // +export default function FancyDateInput(props: IFancyDateInput) { + const { id, value, label, icon, errorMessage, align, disabled, activeHandler, themeType, layer, placeholder, ...inputProps } = props; + + //the states activity of the input + const [isActive, setIsActive] = useState(false); + + // if no id is provided, generate a random one + const useid = useId(); + const usedId = id ? id : useid; + + // handles the focus and blur events and calls the handler from the parent + const activeFocusHandler = (value: boolean) => { + setIsActive(value); + activeHandler && activeHandler(value); + }; + + return ( + + } + /> + ); +} diff --git a/src/components/organisms/FancyDateInput/index.ts b/src/components/organisms/FancyDateInput/index.ts new file mode 100644 index 000000000..0d9257618 --- /dev/null +++ b/src/components/organisms/FancyDateInput/index.ts @@ -0,0 +1 @@ +export { default as FancyDateInput } from './FancyDateInput'; diff --git a/src/components/organisms/FancyDatePicker/FancyDatePicker.stories.tsx b/src/components/organisms/FancyDatePicker/FancyDatePicker.stories.tsx new file mode 100644 index 000000000..df5465739 --- /dev/null +++ b/src/components/organisms/FancyDatePicker/FancyDatePicker.stories.tsx @@ -0,0 +1,132 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyDatePicker from './FancyDatePicker'; +import { IDateArray } from '../../molecules/RangeCalendar/IDateArray.model'; + +// Define metadata for the story +const meta = { + component: FancyDatePicker, + parameters: { + docs: { + description: { + component: 'Smart-Comonent: A fancy date picker with a range calendar and more', + }, + }, + }, + // Define arguments for the story + argTypes: { + rangeCalendar: { + description: 'Is Pickinga range of dates', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + handler: { + description: + 'Handler gives back the first and last date [first, last]... its not a RangePicker then it gives back the first date [frist, undefined]', + control: { + type: 'function', + }, + defaultValue: { + summary: '[undefined, undefined]', + }, + }, + selectedYear: { + description: 'Selected year', + control: { + type: 'number', + }, + defaultValue: { + summary: 'current year', + }, + }, + themeType: { + description: 'Theme type of the datepicker', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'Layer of the datepicker', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 1, + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + rangeCalendar: false, + handler: (date: IDateArray) => console.log(date), + selectedYear: new Date().getFullYear(), + themeType: 'primary', + layer: 1, + externalData: { + 2023: [ + { + monthIdx: 0, + dates: [ + { + date: 1, + isAvilable: 'partially', + }, + { + date: 2, + isAvilable: 'not', + }, + { + date: 3, + isAvilable: 'transparent', + }, + ], + }, + { + monthIdx: 11, + dates: [ + { + date: 5, + isAvilable: 'partially', + }, + { + date: 7, + isAvilable: 'not', + }, + { + date: 2, + isAvilable: 'transparent', + }, + ], + }, + ], + }, + disabledDateSetting: { + disabledWeekdays: [1, 6], + disableWeekends: true, + disablePastDates: false, + }, + }, +}; diff --git a/src/components/organisms/FancyDatePicker/FancyDatePicker.style.tsx b/src/components/organisms/FancyDatePicker/FancyDatePicker.style.tsx new file mode 100644 index 000000000..1a112ebe9 --- /dev/null +++ b/src/components/organisms/FancyDatePicker/FancyDatePicker.style.tsx @@ -0,0 +1,31 @@ +import { styled } from 'styled-components'; + +import { TLayer } from '@/interface/TLayer'; +import getColorsForComponent, { + getBackgroundColor, +} from '../../../design/designFunctions/colorCalculatorForComponent/colorCalculatorForComponet'; +import { TTheme } from '@/interface/TTheme'; +import { TThemeTypes } from '@/interface/TUiColors'; + +export const DatePickerContainer = styled.div<{ theme: TTheme; $themeType?: TThemeTypes; $layer?: TLayer }>` + display: flex; + flex-direction: column; + align-items: center; + ${({ theme, $themeType = 'primary', $layer = 1 }) => getColorsForComponent({ theme, $themeType, $layer })}; + border-radius: ${({ theme }) => theme.borderRadius.xl}; + padding: 20px; + width: 100%; + box-sizing: border-box; +`; + +export const WrapperYearSelector = styled.div<{ theme: TTheme }>` + width: 100%; + padding: ${({ theme }) => `${theme.spacing.md} ${theme.spacing.md} ${theme.spacing.md}`}; + box-sizing: border-box; +`; + +export const WrapperWeekdays = styled.div<{ theme: TTheme; $themeType?: TThemeTypes }>` + width: 100%; + border-bottom: solid 1px ${({ theme, $themeType = 'secondary' }) => getBackgroundColor({ theme, $themeType })}; + padding-bottom: ${({ theme }) => theme.spacing.xxs}; +`; diff --git a/src/components/organisms/FancyDatePicker/FancyDatePicker.tsx b/src/components/organisms/FancyDatePicker/FancyDatePicker.tsx new file mode 100644 index 000000000..8c045f9ea --- /dev/null +++ b/src/components/organisms/FancyDatePicker/FancyDatePicker.tsx @@ -0,0 +1,86 @@ +import React, { useEffect, useState } from 'react'; + +import YearSelector from '../../atoms/YearSelector/YearSelector'; +import WeekDays from '../../atoms/WeekDays/WeekDays'; +import RangeCalendar from '../../molecules/RangeCalendar/RangeCalendar'; +import { DatePickerContainer, WrapperWeekdays, WrapperYearSelector } from './FancyDatePicker.style'; +import IExternalYearWithMonths from '../../molecules/MonthWithDays/IExternalMonthWithDays.model'; +import DateOutputFromTo from '../../molecules/DateOutputFromTo/DateOutputFromTo'; +import { IDisabledDateSettings } from '../../molecules/MonthWithDays/IDisableDateSettings.model'; +import { IDateArray } from '../../molecules/RangeCalendar/IDateArray.model'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; + +interface IFancyDatePicker { + rangeCalendar?: boolean; + handler?: (date: IDateArray) => void; + selectedYear?: number; + disabledDateSetting?: IDisabledDateSettings; + externalData?: IExternalYearWithMonths; + themeType?: TThemeTypes; + layer?: TLayer; +} + +export default function FancyDatePicker(props: IFancyDatePicker) { + const { rangeCalendar, handler, selectedYear, disabledDateSetting, externalData, themeType, layer } = { ...defaultProps, ...props }; + const [selectedDate, setSelectedDate] = useState([new Date(), new Date()]); + const [currentlySelectedFromOrTo, setCurrentlySelectedFromOrTo] = useState<'from' | 'to'>('from'); + const [currentlySelectedYear, setCurrentlySelectedYear] = useState(new Date().getFullYear()); + const swapedTheme = themeType ? (themeType === 'primary' ? 'secondary' : 'primary') : undefined; + + const handleDateChange = (changedDate: IDateArray) => { + handler && handler(changedDate); + setSelectedDate(changedDate); + }; + + const handleSwitchFromTo = (change: 'from' | 'to') => { + setCurrentlySelectedFromOrTo(change); + }; + + useEffect(() => { + if (selectedYear) { + setCurrentlySelectedYear(selectedYear); + } + }, [selectedYear]); + + return ( + + + setCurrentlySelectedYear(year)} + themeType={swapedTheme} + layer={layer} + /> + + + + + + {rangeCalendar && ( + + )} + + ); +} + +const defaultProps: IFancyDatePicker = { + rangeCalendar: false, +}; diff --git a/src/components/organisms/FancyDatePicker/index.ts b/src/components/organisms/FancyDatePicker/index.ts new file mode 100644 index 000000000..098010256 --- /dev/null +++ b/src/components/organisms/FancyDatePicker/index.ts @@ -0,0 +1 @@ +export { default as FancyDatePicker } from './FancyDatePicker'; diff --git a/src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.stories.tsx b/src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.stories.tsx new file mode 100644 index 000000000..a4fa1caca --- /dev/null +++ b/src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.stories.tsx @@ -0,0 +1,103 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyDropDownMenue from './FancyDropDownMenue'; + +// Define metadata for the story +const meta = { + component: FancyDropDownMenue, + parameters: { + docs: { + description: { + component: 'Smart-Comonent: A fancy date picker with a range calendar and more', + }, + }, + }, + // Define arguments for the story + argTypes: { + isOpen: { + description: 'Is the dropdown open', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + themeType: { + description: 'Theme type of the datepicker', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'Layer of the datepicker', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 1, + }, + }, + width: { + description: 'Width of the dropdown', + control: { + type: 'text', + }, + defaultValue: { + summary: '50%', + }, + }, + alignHorizontal: { + description: 'Horizontal alignment of the dropdown', + control: { + type: 'select', + }, + defaultValue: { + summary: 'center', + }, + }, + alignVertical: { + description: 'Vertical alignment of the dropdown', + control: { + type: 'select', + }, + defaultValue: { + summary: 'top', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + isOpen: false, + themeType: 'primary', + layer: 1, + width: '50%', + alignHorizontal: 'center', + alignVertical: 'top', + children: ( + <> +

test

+

test

+ + ), + }, +}; diff --git a/src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.tsx b/src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.tsx new file mode 100644 index 000000000..6c356f8de --- /dev/null +++ b/src/components/organisms/FancyDropDownMenue/FancyDropDownMenue.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react'; + +import FancyUL, { IFancyUL } from '../../molecules/FancyDropDownUL/FancyDropDownUL'; +import UseDelay from '../../utils/components/UseDelay/UseDelay'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; + +// --------------------------------------------------------------------------- // +// ------------ The main component that renders the dropdown menu ------------ // +// --------------------------------------------------------------------------- // +interface IFancyDropDownMenue extends IFancyUL { + isOpen?: boolean; + themeType?: TThemeTypes; + layer?: TLayer; +} +export default function FancyDropDownMenue(props: IFancyDropDownMenue) { + const { isOpen } = props; + const [isOpenState, setIsOpenState] = useState(isOpen); + const [fristRender, setFristRender] = useState(false); + + // This useEffect hook sets the fristRender state to true after the component mounts + useEffect(() => { + setFristRender(true); + }, []); + + useEffect(() => { + if (isOpen) { + setIsOpenState(true); + } else { + setIsOpenState(false); + } + }, [isOpen]); + + // This component returns the FancyUL component wrapped in a UseDelay component + return fristRender ? ( + + + + ) : null; +} diff --git a/src/components/organisms/FancyDropDownMenue/index.ts b/src/components/organisms/FancyDropDownMenue/index.ts new file mode 100644 index 000000000..7673c246f --- /dev/null +++ b/src/components/organisms/FancyDropDownMenue/index.ts @@ -0,0 +1 @@ +export { default as FancyDropDownMenue } from './FancyDropDownMenue'; diff --git a/src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.stories.tsx b/src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.stories.tsx new file mode 100644 index 000000000..8c68acb1a --- /dev/null +++ b/src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.stories.tsx @@ -0,0 +1,148 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyDropDownSelect from './FancyDropDownSelect'; + +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// Define metadata for the story +const meta = { + component: FancyDropDownSelect, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: A fancy dropdown select with label, icon and error message and more', + }, + }, + }, + // Define arguments for the story + argTypes: { + label: { + description: 'Label for the input', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + placeholder: { + description: 'Placeholder for the input', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + disabled: { + description: 'Disable the input', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + align: { + description: 'Alignment of the label', + control: { + type: 'radio', + }, + defaultValue: { + summary: 'left', + }, + }, + themeType: { + description: 'Theme type of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'Layer of the input', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 4, + }, + }, + isActive: { + description: 'Is the input in focus', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + errorMessage: { + description: 'Error message to be displayed', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + values: { + description: 'Values for the dropdown', + control: { + type: 'array', + }, + defaultValue: { + summary: [], + }, + }, + value: { + description: 'The picked value of the dropdown', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + emptySelect: { + description: 'A Empty Item that is selectable', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + activeHandler: { + description: 'Handler gives back if the dropdown is on focused', + control: { + type: 'function', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + icon: , + label: 'Label', + placeholder: 'Placeholder', + values: ['test1', 'test2', 'test3'], + }, +}; diff --git a/src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.tsx b/src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.tsx new file mode 100644 index 000000000..71c9f95aa --- /dev/null +++ b/src/components/organisms/FancyDropDownSelect/FancyDropDownSelect.tsx @@ -0,0 +1,55 @@ +import React, { useId, useState } from 'react'; + +import InputWrapper from '../../molecules/InputWrapper/InputWrapper'; +import DropDownSelect, { IDropDownSelect } from '../../atoms/DropDownSelect/DropDownSelect'; + +import { IInputWrapperUserInputProps } from '../../molecules/InputWrapper/InputWrapper'; + +export type IFancyDropDownSelect = Omit & IDropDownSelect; + +// --------------------------------------------------------------------------- // +// ----The Dropdown Comonent with surrounding icon, label and underline ------ // +// --------------------------------------------------------------------------- // +export default function FancyDropDownSelect(props: IFancyDropDownSelect) { + const { id, value, placeholder, disabled, align, activeHandler, icon, label, errorMessage, themeType, layer, ...inputProps } = props; + + //the states for the value and the activity of the input + const [isActive, setIsActive] = useState(false); + + // if no id is provided, generate a random one + const useid = useId(); + const usedId = id ? id : useid; + + // handles the focus and blur events and calls the handler from the parent + const activeFocusHandler = (value: boolean) => { + setIsActive(value); + activeHandler && activeHandler(value); + }; + + return ( + + } + /> + ); +} diff --git a/src/components/organisms/FancyDropDownSelect/index.ts b/src/components/organisms/FancyDropDownSelect/index.ts new file mode 100644 index 000000000..f46fdc4e0 --- /dev/null +++ b/src/components/organisms/FancyDropDownSelect/index.ts @@ -0,0 +1 @@ +export { default as FancyDropDownSelect } from './FancyDropDownSelect'; diff --git a/src/components/organisms/FancyEditBar/EditBarItemsStructure/ButtonSettings.tsx b/src/components/organisms/FancyEditBar/EditBarItemsStructure/ButtonSettings.tsx new file mode 100644 index 000000000..cd196a6ba --- /dev/null +++ b/src/components/organisms/FancyEditBar/EditBarItemsStructure/ButtonSettings.tsx @@ -0,0 +1,105 @@ +import editBarIcons from './EditBarIcons'; +import { languagesEditBarEN } from '../languagesEditBar'; +const language = languagesEditBarEN; +import { IFancyBottomBarIcon } from '../../../templates/FancyBottomBarIcon/FancyBottomBarIcon'; +import { useEditBarStore } from '../../../molecules/EditBar/EditBar.state'; + +const setActiveBarItem = useEditBarStore.getState().setActiveEditbarItem; +const setActiveSecondBarItem = useEditBarStore.getState().setActiveSecondEditbarItem; + +type TButtonSettings = { + [key: string]: IFancyBottomBarIcon; +}; + +const groupButtons: TButtonSettings = { + color: { + id: 'color', + label: language.sectionSectionBox.color.label, + icon: editBarIcons.color, + onClick: () => setActiveBarItem('color'), + }, + distance: { + id: 'distance', + label: language.sectionSectionBox.distance.label, + icon: editBarIcons.distance, + onClick: () => setActiveBarItem('distance'), + }, + border: { + id: 'border', + label: language.sectionSectionBox.border.label, + icon: editBarIcons.border, + onClick: () => setActiveBarItem('border'), + }, + shadow: { + id: 'shadow', + label: language.sectionSectionBox.shadow.label, + icon: editBarIcons.shadow, + onClick: () => setActiveBarItem('shadow'), + }, +}; + +const buttonSettings: TButtonSettings = { + color: { + id: 'color', + label: language.sectionSectionBox.color.label, + icon: editBarIcons.color, + onClick: () => setActiveSecondBarItem('color'), + }, + distanceVertical: { + id: 'distanceVertical', + label: language.settings.distanceVertical.label, + icon: editBarIcons.distanceVertical, + onClick: () => setActiveSecondBarItem('distanceVertical'), + }, + distanceHorizontal: { + id: 'distanceHorizontal', + label: language.settings.distanceHorizontal.label, + icon: editBarIcons.distanceHorizontal, + onClick: () => setActiveSecondBarItem('distanceHorizontal'), + }, + distanceLeft: { + id: 'distanceLeft', + label: language.settings.distanceLeft.label, + icon: editBarIcons.distanceLeft, + onClick: () => setActiveSecondBarItem('distanceLeft'), + }, + distanceRight: { + id: 'distanceRight', + label: language.settings.distanceRight.label, + icon: editBarIcons.distanceRight, + onClick: () => setActiveSecondBarItem('distanceRight'), + }, + distanceTop: { + id: 'distanceTop', + label: language.settings.distanceTop.label, + icon: editBarIcons.distanceTop, + onClick: () => setActiveSecondBarItem('distanceTop'), + }, + distanceBottom: { + id: 'distanceBottom', + label: language.settings.distanceBottom.label, + icon: editBarIcons.distanceBottom, + onClick: () => setActiveSecondBarItem('distanceBottom'), + }, + thikness: { + id: 'thikness', + label: language.settings.thikness.label, + icon: editBarIcons.thikness, + onClick: () => setActiveSecondBarItem('thikness'), + }, + radius: { + id: 'radius', + label: language.settings.radius.label, + icon: editBarIcons.radius, + onClick: () => setActiveSecondBarItem('radius'), + }, + strength: { + id: 'strength', + label: language.settings.strength.label, + icon: editBarIcons.thikness, + onClick: () => setActiveSecondBarItem('strength'), + }, +}; + +export default buttonSettings; +export { groupButtons }; diff --git a/src/components/organisms/FancyEditBar/EditBarItemsStructure/EditBarIcons.tsx b/src/components/organisms/FancyEditBar/EditBarItemsStructure/EditBarIcons.tsx new file mode 100644 index 000000000..e3367d251 --- /dev/null +++ b/src/components/organisms/FancyEditBar/EditBarItemsStructure/EditBarIcons.tsx @@ -0,0 +1,89 @@ +const editBarIcons = { + distance: ( + + + + + ), + color: ( + + + + + ), + border: ( + + + + + ), + shadow: ( + + + + ), + position: ( + + + + ), + distanceVertical: ( + + + + ), + distanceHorizontal: ( + + + + ), + distanceLeft: ( + + + + + ), + distanceRight: ( + + + + + ), + distanceTop: ( + + + + + ), + distanceBottom: ( + + + + + ), + thikness: ( + + + + ), + radius: ( + + + + ), + background: ( + + + + ), + text: ( + + + + ), +}; + +export default editBarIcons; diff --git a/src/components/organisms/FancyEditBar/EditBarItemsStructure/IEditbarObjectSturcture.model.ts b/src/components/organisms/FancyEditBar/EditBarItemsStructure/IEditbarObjectSturcture.model.ts new file mode 100644 index 000000000..e77e63588 --- /dev/null +++ b/src/components/organisms/FancyEditBar/EditBarItemsStructure/IEditbarObjectSturcture.model.ts @@ -0,0 +1,58 @@ +import React from 'react'; + +import { IFancyBottomBarIcon } from '../../../templates/FancyBottomBarIcon/FancyBottomBarIcon'; +import FancyDropDownSelect from '../../FancyDropDownSelect/FancyDropDownSelect'; +import FancyTextInput from '../../FancyTextInput/FancyTextInput'; +import IFancyRangeSlider from '../../FancyRangeSlider/FancyRangeSlider.model'; +import { ITabSwitchProps } from '../../../molecules/TabSwitch/TabSwitch.model'; + +// --------------------------------------------------------------------------- // +// ---------- thes interfaces map the strings and JSX Item together ---------- // +// --------------------------------------------------------------------------- // + +//the input types wich the editbar can have +interface ISettingsItem { + settingsType: 'textInput' | 'select' | 'slider' | 'dropDown' | 'tab'; +} + +//---------- The mapped interfaces ----------// +export interface IDropDown extends ISettingsItem, React.ComponentProps { + settingsType: 'dropDown'; +} + +export interface ITextInput extends ISettingsItem, React.ComponentProps { + settingsType: 'textInput'; +} + +export interface IRangeSlider extends ISettingsItem, IFancyRangeSlider { + settingsType: 'slider'; +} + +export interface ITab extends ISettingsItem, ITabSwitchProps { + settingsType: 'tab'; +} + +// types which the settings array can have +export type InputSettings = ITextInput | IDropDown | IRangeSlider | ITab; + +// SubsectionSettingItem interface +//One Item has a subsectionItem(title, icon etc.) and settings (input, select, slider etc.) array +interface SubsectionSettingItem { + subsectionItem: IFancyBottomBarIcon; + settings: Array | null; +} + +// SubsectionSettingItems interface +interface SubsectionSettingItems { + [key: string]: SubsectionSettingItem; +} + +// ISectionItem interface +export interface ICategoryItem { + id: string; + sectionItem: IFancyBottomBarIcon; + subsectionSettingItems: SubsectionSettingItems; +} + +// ISection type +export type ICategory = ICategoryItem[]; diff --git a/src/components/organisms/FancyEditBar/EditBarItemsStructure/objectSettingsSection.ts b/src/components/organisms/FancyEditBar/EditBarItemsStructure/objectSettingsSection.ts new file mode 100644 index 000000000..031e488dc --- /dev/null +++ b/src/components/organisms/FancyEditBar/EditBarItemsStructure/objectSettingsSection.ts @@ -0,0 +1,157 @@ +import buttonSettings, { groupButtons } from './ButtonSettings'; +import { ICategory } from './IEditbarObjectSturcture.model'; + +//Dummy Category Box for demonstration +//the item is build up with three categories [mainCategoryItems (Arry) ---> SubSectionItems(Object) ---> Settings ---> ] +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const dummyItem = [ + { + //the CategoryItem is build up in SectionItem(displayed for the bar) and the SubsectionSettingItems + id: 'distance', + sectionItem: buttonSettings.distance, + subsectionSettingItems: { + //here are listed the items for the subsection (not in an array but in an object) + + distanceVertical: { + // this is like one SubSection Items looks like SectionItem and the settings array + subsectionItem: buttonSettings.distanceVertical, + settings: [ + //this is the settings array this sttings are displayed on the modal over the editbar + { + settingsType: 'textInput', + type: 'text', + label: 'Test1', + align: 'center', + }, + { + settingsType: 'textInput', + type: 'text', + label: 'Test1', + align: 'center', + }, + ], + }, + }, + }, +]; + +const sectionSectionBox: ICategory = [ + { + id: 'distance', + sectionItem: groupButtons.distance, + subsectionSettingItems: { + distanceVertical: { + subsectionItem: buttonSettings.distanceVertical, + settings: [ + { + settingsType: 'textInput', + label: 'Test1', + align: 'center', + }, + { + settingsType: 'textInput', + label: 'Test1', + align: 'center', + }, + ], + }, + distanceHorizontal: { + subsectionItem: buttonSettings.distanceHorizontal, + settings: [ + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + ], + }, + distanceLeft: { + subsectionItem: buttonSettings.distanceLeft, + settings: [ + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + ], + }, + distanceRight: { + subsectionItem: buttonSettings.distanceRight, + settings: [ + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + { + settingsType: 'dropDown', + label: 'Test', + }, + ], + }, + }, + }, + { + id: 'shadow', + sectionItem: groupButtons.shadow, + subsectionSettingItems: { + shadowPosition: { + subsectionItem: buttonSettings.shadow, + settings: [ + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + ], + }, + shadowStrength: { + subsectionItem: buttonSettings.strength, + settings: [ + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + ], + }, + shadowColor: { + subsectionItem: buttonSettings.color, + settings: [ + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + { + settingsType: 'textInput', + label: 'Test', + align: 'center', + }, + ], + }, + }, + }, +]; + +export default sectionSectionBox; diff --git a/src/components/organisms/FancyEditBar/EditBarSettings.tsx b/src/components/organisms/FancyEditBar/EditBarSettings.tsx new file mode 100644 index 000000000..e104592e6 --- /dev/null +++ b/src/components/organisms/FancyEditBar/EditBarSettings.tsx @@ -0,0 +1,17 @@ +import sectionSectionBox from './EditBarItemsStructure/objectSettingsSection'; + +// --------------------------------------------------------------------------- // +// ---------- Here are the design variants for sizing and alignment ---------- // +// --------------------------------------------------------------------------- // +export const mainSectionCreator = (sectionFor: 'header' | 'sectionBox' | 'background') => { + switch (sectionFor) { + case 'header': + //return sectionHeader; + break; + case 'sectionBox': + return sectionSectionBox; + case 'background': + + //return sectionBackground; + } +}; diff --git a/src/components/organisms/FancyEditBar/FancyEditBar.state.tsx b/src/components/organisms/FancyEditBar/FancyEditBar.state.tsx new file mode 100644 index 000000000..5d1ef03ac --- /dev/null +++ b/src/components/organisms/FancyEditBar/FancyEditBar.state.tsx @@ -0,0 +1,44 @@ +import { create } from 'zustand'; +import { IFancyBottomBarIcon } from '../../templates/FancyBottomBarIcon/FancyBottomBarIcon'; +import { ICategoryItem } from './EditBarItemsStructure/IEditbarObjectSturcture.model'; + +// --------------------------------------------------------------------------- // +// ------------- The Interface structure for the useEditBarStore ------------- // +// --------------------------------------------------------------------------- // +interface EditBarModuleStore { + //the current items for the main bar + currentItems: IFancyBottomBarIcon[] | null; + setCurrentItems: (items: IFancyBottomBarIcon[]) => void; + + //the active setting for the editbar + activeEditbarCategory: ICategoryItem | null; + setActiveEditbarCategory: (setting: ICategoryItem | null) => void; + + //the current items for the second bar + secondCurrentItems: IFancyBottomBarIcon[] | null; + setSecondCurrentItems: (items: IFancyBottomBarIcon[]) => void; + + //the active setting for the editbar + activeSetting: string | null; + setActiveSetting: (setting: string) => void; +} + +// --------------------------------------------------------------------------- // +// --------------- This is the global state for the editbar ------------------ // +// --------------------------------------------------------------------------- // +export const useFancyEditBarStore = create((set) => ({ + activeEditbarCategory: null, + setActiveEditbarCategory: (setting) => set(() => ({ activeEditbarCategory: setting })), + + //The State for the currently set bar items + currentItems: null, + setCurrentItems: (items) => set(() => ({ currentItems: items })), + + //The State for the second bar items they currently set + secondCurrentItems: null, + setSecondCurrentItems: (items) => set(() => ({ secondCurrentItems: items })), + + //The State for the active setting + activeSetting: null, + setActiveSetting: (setting) => set(() => ({ activeSetting: setting })), +})); diff --git a/src/components/organisms/FancyEditBar/FancyEditBar.tsx b/src/components/organisms/FancyEditBar/FancyEditBar.tsx new file mode 100644 index 000000000..b44ab024e --- /dev/null +++ b/src/components/organisms/FancyEditBar/FancyEditBar.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import EditBar from '../../molecules/EditBar/EditBar'; +import { mainSectionCreator } from './EditBarSettings'; +import { useEditBarStore } from '../../molecules/EditBar/EditBar.state'; + +import { ICategory } from './EditBarItemsStructure/IEditbarObjectSturcture.model'; +import { useFancyEditBarStore } from './FancyEditBar.state'; +import { getObjectMapper, getSubSectionItems } from './functions/ElementsObjectMapper'; + +export default function FancyEditBar({ active, sectionType }: { active?: boolean; sectionType: 'header' | 'sectionBox' | 'background' }) { + // Hooks to get and set state values from the edit bar store + const currentEditBarItems = useFancyEditBarStore((state) => state.currentItems); + const setCurrentEditBarItems = useFancyEditBarStore((state) => state.setCurrentItems); + + const activeEditbarItem = useEditBarStore((state) => state.activeEditbarItem); + const setActiveEditbarItem = useEditBarStore((state) => state.setActiveEditbarItem); + const activeSubSectionItem = useEditBarStore((state) => state.activeSecondEditbarItem); + + const activeEditbarCategory = useFancyEditBarStore((state) => state.activeEditbarCategory); + const setActiveEditbarCategory = useFancyEditBarStore((state) => state.setActiveEditbarCategory); + + //thhe state with the current editbar items + const [editBar, setEditBar] = useState(); + + // Get sub-section items and settings + const currentSubSectionItems = getSubSectionItems({ activeEditbarCategory }); + const currentSettings = getObjectMapper({ activeEditbarCategory, activeSubSectionItem }); + + // Update activeEditbarCategory based on activeEditbarItem + useEffect(() => { + const getEditBarSetting = editBar?.find((item) => item.id === activeEditbarItem); + + setActiveEditbarCategory(getEditBarSetting || null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeEditbarItem]); + + // Reset activeEditbarItem when the active prop changes to false + useEffect(() => { + if (!active) { + setActiveEditbarItem(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [active]); + + // Update currentEditBarItems when the editBar state changes (when the sectionType changes) + useEffect(() => { + const currentEditBarItems = editBar?.map((item) => item.sectionItem); + setCurrentEditBarItems(currentEditBarItems!); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editBar]); + + // Initialize editBar and currentEditBarItems based on sectionType + useEffect(() => { + // Generate the edit bar based on the sectionType Image / Section / Background + const createDynamicSection = mainSectionCreator(sectionType); + setEditBar(createDynamicSection); + }, [sectionType]); + + // Render the EditBar component if active + return ( +
+ {active && } +
+ ); +} diff --git a/src/components/organisms/FancyEditBar/functions/ElementsObjectMapper.tsx b/src/components/organisms/FancyEditBar/functions/ElementsObjectMapper.tsx new file mode 100644 index 000000000..0400b8538 --- /dev/null +++ b/src/components/organisms/FancyEditBar/functions/ElementsObjectMapper.tsx @@ -0,0 +1,65 @@ +import { IFancyBottomBarIcon } from '../../../templates/FancyBottomBarIcon/FancyBottomBarIcon'; +import { ICategoryItem, IDropDown, IRangeSlider, ITextInput, InputSettings } from '../EditBarItemsStructure/IEditbarObjectSturcture.model'; + +import { FancyTextInput } from '../../FancyTextInput'; +import FancyDropDownSelect from '../../FancyDropDownSelect/FancyDropDownSelect'; +import FancyRangeSlider from '../../FancyRangeSlider/FancyRangeSlider'; +import FancyTabSwitch from '../../../molecules/FancyTabSwitch/FancyTabSwitch'; +import { ITabSwitchProps } from '../../../molecules/TabSwitch/TabSwitch.model'; + +// --------------------------------------------------------------------------- // +// --------- Give back a Element of the the specified item string ------------ // +// --------------------------------------------------------------------------- // +const settingsToJSXMapper = (itemObject: InputSettings) => { + const { settingsType, ...inputProps } = itemObject; + + switch (settingsType) { + case 'textInput': + return ; + case 'dropDown': + return ; + case 'slider': + return ; + case 'tab': + return ; + } +}; + +// --------------------------------------------------------------------------- // +// ------ get the items of SubSection based on the active category item ------ // +// --------------------------------------------------------------------------- // +interface IObjectMapper { + activeEditbarCategory: ICategoryItem | null; + activeSubSectionItem: string | null; +} + +// Get settings based on active category of and the activeSubSectionItem +export const getObjectMapper = ({ activeEditbarCategory, activeSubSectionItem }: IObjectMapper): React.ReactElement[] | null => { + //get the values and check if they there + if (!activeEditbarCategory || !activeSubSectionItem) return null; + + //get the JSX SETTINGS from the currently ACTIVE CATEGORY ---> ACTIVE SUBSECTION ---> and get the SETTINGS + const currentActiveSettings = activeEditbarCategory.subsectionSettingItems[activeSubSectionItem].settings; + + console.log('currentActiveSettings', currentActiveSettings, activeEditbarCategory); + //map the settings to JSX + const JSXElements = currentActiveSettings ? currentActiveSettings.map((item) => settingsToJSXMapper(item)) : null; + + return JSXElements; +}; + +// --------------------------------------------------------------------------- // +// ------ get the items of SubSection based on the active Section item ------- // +// --------------------------------------------------------------------------- // +interface ICategoryItems { + activeEditbarCategory: ICategoryItem | null; +} + +// Get the current sub-section items from the activeEditbarCategory +export const getSubSectionItems = ({ activeEditbarCategory }: ICategoryItems): IFancyBottomBarIcon[] | null => { + if (!activeEditbarCategory) return null; + const getSubsetionItems = activeEditbarCategory.subsectionSettingItems; + + //return the SubSectionItems of the values form the object + return Object.values(getSubsetionItems).map((item) => item.subsectionItem); +}; diff --git a/src/components/organisms/FancyEditBar/index.ts b/src/components/organisms/FancyEditBar/index.ts new file mode 100644 index 000000000..d0c7cf875 --- /dev/null +++ b/src/components/organisms/FancyEditBar/index.ts @@ -0,0 +1 @@ +export { default as FancyEditBar } from './FancyEditBar'; diff --git a/src/components/organisms/FancyEditBar/languagesEditBar.ts b/src/components/organisms/FancyEditBar/languagesEditBar.ts new file mode 100644 index 000000000..e9f722ddb --- /dev/null +++ b/src/components/organisms/FancyEditBar/languagesEditBar.ts @@ -0,0 +1,23 @@ +export const languagesEditBarEN = { + sectionSectionBox: { + distance: { label: 'Distance' }, + color: { label: 'Color' }, + border: { label: 'Border' }, + shadow: { label: 'Shadow' }, + }, + settings: { + distanceVertical: { label: 'Vertical' }, + distanceHorizontal: { label: 'Horizontal' }, + distanceTop: { label: 'Top' }, + distanceBottom: { label: 'Bottom' }, + distanceLeft: { label: 'Left' }, + distanceRight: { label: 'Right' }, + thikness: { label: 'Thikness' }, + radius: { label: 'Radius' }, + shadow: { label: 'Shadow' }, + position: { label: 'Position' }, + strength: { label: 'Strength' }, + color: { label: 'Color' }, + background: { label: 'Background' }, + }, +}; diff --git a/src/components/organisms/FancyModal/FancyModal.mdx b/src/components/organisms/FancyModal/FancyModal.mdx new file mode 100644 index 000000000..8735fd2df --- /dev/null +++ b/src/components/organisms/FancyModal/FancyModal.mdx @@ -0,0 +1,72 @@ +## FancyModal Documentation + +### Overview + +The FancyModal component, streamlined with the `useFancyModalStore`, presents a modern approach to creating and managing modals in your applications. This approach simplifies the integration process by handling modal configurations automatically, allowing developers to effortlessly incorporate modals without the complications of manual prop settings. + +### Setting Up FancyModal + +1. **Render the Main Component in the Base File**: + + Ensure that the `` component is added to a suitable location in your main file for correct modal behavior. + +2. **Specify the DOM Element for Rendering**: + + Designate a DOM element to serve as the container for the modal. For example: + + ```html +
+ ``` + +### Implementing the Modal + +1. **Import the Store in the Component**: + + Within the component where you plan to initiate the modal, start by importing the `useFancyModalStore`. + + ```javascript + const openModal = useFancyModalStore((state) => state.openModal); + ``` + +2. **Open the Modal**: + + To display the modal, employ the `openModal` function. This function requires the following parameters: + + - `appendToDomID`: The ID corresponding to the DOM element where the modal will appear. + - Child components: The specific content or components you want to show within the modal. + - Configuration object: This sets various modal properties. + + ```javascript + const openModalHandler = () => { + openModal( + 'modalTest', +
+ + closeModal('modalTest')} label="Close Modal"> +
, + { + isCloseAble: false, + } + ); + }; + ``` + +3. **Close the Modal**: + + To terminate an active modal, use the `closeModal` method from the `useFancyModalStore`. + + ```javascript + const closeModal = useFancyModalStore((state) => state.closeModal); + ``` + + Subsequently, invoke `closeModal` using the ID of the modal you wish to shut, as depicted in the `openModalHandler` example. + +### Configuration Options + +The FancyModal's adaptability is showcased through the configuration object provided during modal initiation. One available option is: + +- `isCloseAble`: A boolean that denotes whether users can manually terminate the modal. + +### Conclusion + +The evolved FancyModal, bolstered by `useFancyModalStore`, offers developers a smoother and more intuitive modal management experience. This refurbished framework undeniably simplifies the modal integration process, ensuring both efficient development and streamlined user interactions. diff --git a/src/components/organisms/FancyModal/FancyModal.state.tsx b/src/components/organisms/FancyModal/FancyModal.state.tsx new file mode 100644 index 000000000..7f252cff2 --- /dev/null +++ b/src/components/organisms/FancyModal/FancyModal.state.tsx @@ -0,0 +1,44 @@ +import { create } from 'zustand'; + +import { TModalStatus } from '../../../interface/TModalStatus'; +import { IModal } from '../../molecules/Modal/Modal'; + +type TModalConfig = Omit; + +type IModals = { + id: string; + children: React.ReactNode; + status: TModalStatus; + config?: TModalConfig; +}; + +// openModal(id,
hi
, {}) + +// --------------------------------------------------------------------------- // +// --------------------- The state for the ModalModuel ----------------------- // +// --------------------------------------------------------------------------- // +interface IModalModule { + modals: IModals[]; + openModal: (id: string, content: React.ReactNode, config: TModalConfig) => void; + removeModal: (id: string) => void; + closeModal: (id: string) => void; +} +export const useFancyModalStore = create((set) => ({ + // the state array for the modals + modals: [], + // add a new modal to the state array + openModal: (id, children, config) => + set((state) => ({ + modals: [...state.modals, { id, children, status: 'open', config }], + })), + // change the status of the modal to closing + closeModal: (id) => + set((state) => ({ + modals: state.modals.map((modal) => (modal.id === id ? { ...modal, status: 'closing' } : modal)), + })), + // remove the modal from the state array + removeModal: (id) => + set((state) => ({ + modals: state.modals.filter((modal) => modal.id !== id), + })), +})); diff --git a/src/components/organisms/FancyModal/FancyModal.stories.tsx b/src/components/organisms/FancyModal/FancyModal.stories.tsx new file mode 100644 index 000000000..2f4b77634 --- /dev/null +++ b/src/components/organisms/FancyModal/FancyModal.stories.tsx @@ -0,0 +1,72 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyModal from './FancyModal'; +import { useFancyModalStore } from './FancyModal.state'; +import { FancyButton } from '../FancyButton'; +import { IModal } from '../../molecules/Modal/Modal'; +import { FancyTextInput } from '../FancyTextInput'; + +// Define metadata for the story +const meta = { + component: HelperComponent, + title: 'components/ui/organisms/FancyModal', + parameters: { + docs: { + description: { + component: 'Smart-Comonent: The FancyModal is a smart component that handles all the logic for the Modal.', + }, + }, + }, + // Define arguments for the story + argTypes: {}, + // Add tags to the story +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +function HelperComponent(props: React.ComponentProps & Omit) { + const { appendToDomID, children, ...configProps } = props; + const openModal = useFancyModalStore((state) => state.openModal); + const closeModal = useFancyModalStore((state) => state.closeModal); + + const openModalHandler = () => { + openModal( + appendToDomID || 'modalTest', + children || ( +
+ + closeModal('modalTest')} label="Close Modal"> +
+ ), + configProps || { + isCloseAble: false, + } + ); + }; + return ( + <> + + openModalHandler()} /> + + ); +} + +HelperComponent.displayName = 'FancyModal'; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + <> + + + + ), + args: { + appendToDomID: '', + }, +}; diff --git a/src/components/organisms/FancyModal/FancyModal.tsx b/src/components/organisms/FancyModal/FancyModal.tsx new file mode 100644 index 000000000..7b1ba0616 --- /dev/null +++ b/src/components/organisms/FancyModal/FancyModal.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import { useFancyModalStore } from './FancyModal.state'; +import Modal from '../../molecules/Modal/Modal'; +import FancyPortal from '../../utils/components/FancyPortal/FancyPortal'; + +// ---------- How to use the Module ------- // +//--- use it +// Append this module to the root of the app you dont need to pass any props +// just use the useModalModuleStore to open a modal +//--- open modal +// useModalModuleStore.openModal( 'ID', , '{CONFIG OBJECT}) +//--- close modal +// when you want to close the modal with a custom button just use the closeModal function with the "ID" of the modal +// useModalModuleStore.closeModal('id') + +// --------------------------------------------------------------------------- // +// ----------------- The modalModule to build up a Moadal ------------------- // +// --------------------------------------------------------------------------- // +interface IFancyModal { + appendToDomID: string; +} +export default function FancyModal({ appendToDomID }: IFancyModal) { + const modals = useFancyModalStore((state) => state.modals); + const closeModal = useFancyModalStore((state) => state.closeModal); + const removeModal = useFancyModalStore((state) => state.removeModal); + + const closeModalHandler = (id: string) => { + // closing the modal is a two step process + // first we set the status to "closing" with the closeModal function + // this will trigger the animation in the modal component + closeModal(id); + + //wait for the animation and remove the modal from the store + setTimeout(() => { + removeModal(id); + }, 300); + }; + + return ( + <> + {/* ----- The FancModal Ports the Modal out of the root div in the spearte "modal" div ----- */} + + {modals.map((modal, key) => ( + closeModalHandler(modal.id)} {...modal.config}> + {/* ----- The Content of the Modal ----- */} + {modal.children} + + ))} + + + ); +} diff --git a/src/components/organisms/FancyModal/index.ts b/src/components/organisms/FancyModal/index.ts new file mode 100644 index 000000000..5e17e9e55 --- /dev/null +++ b/src/components/organisms/FancyModal/index.ts @@ -0,0 +1 @@ +export { default as FancyModal } from './FancyModal'; diff --git a/src/components/organisms/FancyNumberInput/FancyNumberInput.tsx b/src/components/organisms/FancyNumberInput/FancyNumberInput.tsx new file mode 100644 index 000000000..295850594 --- /dev/null +++ b/src/components/organisms/FancyNumberInput/FancyNumberInput.tsx @@ -0,0 +1,55 @@ +import React, { useId, useState } from 'react'; +import NumberInput, { INumberInput } from '../../molecules/NumberInput/NumberInput'; +import InputWrapper, { IInputWrapperUserInputProps } from '../../molecules/InputWrapper/InputWrapper'; + +type IFancyNumberInput = INumberInput & IInputWrapperUserInputProps & { autoWidth?: boolean }; + +// --------------------------------------------------------------------------- // +// ----The NumberInput Comonent with surrounding icon, label and underline --- // +// --------------------------------------------------------------------------- // +export default function FancyNumberInput(props: IFancyNumberInput) { + const { value, label, icon, activeHandler, disabled, errorMessage, align, id, themeType, layer, autoWidth, placeholder, ...inputProps } = + props; + + //the states activity of the input + const [isActive, setIsActive] = useState(false); + + // if no id is provided, generate a random one + const useid = useId(); + const usedId = id ? id : useid; + + // handles the focus and blur events and calls the handler from the parent + const activeFocusHandler = (value: boolean) => { + setIsActive(value); + activeHandler && activeHandler(value); + }; + + return ( + + } + /> + ); +} diff --git a/src/components/organisms/FancyNumberInput/index.ts b/src/components/organisms/FancyNumberInput/index.ts new file mode 100644 index 000000000..1ff247088 --- /dev/null +++ b/src/components/organisms/FancyNumberInput/index.ts @@ -0,0 +1 @@ +export { default as FancyNumberInput } from './FancyNumberInput'; diff --git a/src/components/organisms/FancyPasswordInput/FancyPasswordInput.stories.tsx b/src/components/organisms/FancyPasswordInput/FancyPasswordInput.stories.tsx new file mode 100644 index 000000000..36696c9a2 --- /dev/null +++ b/src/components/organisms/FancyPasswordInput/FancyPasswordInput.stories.tsx @@ -0,0 +1,102 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyPasswordInput from './FancyPasswordInput'; + +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// Define metadata for the story +const meta = { + component: FancyPasswordInput, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + value: { + description: 'The value of the input', + control: { + type: 'text', + }, + }, + align: { + description: 'The alignment of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'left', + }, + }, + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: '4', + }, + }, + label: { + description: 'The label of the input', + control: { + type: 'text', + }, + }, + icon: { + description: 'The icon of the input', + defaultValue: { + summary: 'none', + }, + }, + isActive: { + description: 'Is the input focused', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + errorMessage: { + description: 'The error message of the input', + control: { + type: 'text', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + align: 'left', + themeType: 'secondary', + layer: 4, + label: 'Password', + icon: , + isActive: false, + }, +}; diff --git a/src/components/organisms/FancyPasswordInput/FancyPasswordInput.tsx b/src/components/organisms/FancyPasswordInput/FancyPasswordInput.tsx new file mode 100644 index 000000000..3a956ee82 --- /dev/null +++ b/src/components/organisms/FancyPasswordInput/FancyPasswordInput.tsx @@ -0,0 +1,54 @@ +import React, { useId, useState } from 'react'; +import PasswordInput, { IPasswordInputProps } from '../../molecules/PasswordInput/PasswordInput'; + +import InputWrapper, { IInputWrapperUserInputProps } from '../../molecules/InputWrapper/InputWrapper'; +type IFancyTextInputProps = IPasswordInputProps & Omit; + +// --------------------------------------------------------------------------- // +// ----The PasswordInput Comonent with surrounding icon, label and underline-- // +// --------------------------------------------------------------------------- // +export default function FancyPasswordInput(props: IFancyTextInputProps) { + const { id, value, placeholder, activeHandler, errorMessage, disabled, align, themeType, layer, icon, label, ...inputProps } = props; + + //the states activity of the input + const [isActive, setIsActive] = useState(false); + + // if no id is provided, generate a random one + const useid = useId(); + const usedId = id ? id : useid; + + // handles the focus and blur events and calls the handler from the parent + const activeFocusHandler = (value: boolean) => { + setIsActive(value); + activeHandler && activeHandler(value); + }; + + return ( + + } + /> + ); +} diff --git a/src/components/organisms/FancyPasswordInput/index.ts b/src/components/organisms/FancyPasswordInput/index.ts new file mode 100644 index 000000000..48ceea88d --- /dev/null +++ b/src/components/organisms/FancyPasswordInput/index.ts @@ -0,0 +1 @@ +export { default as FancyPasswordInput } from './FancyPasswordInput'; diff --git a/src/components/organisms/FancyRadio/FancyRadio.tsx b/src/components/organisms/FancyRadio/FancyRadio.tsx new file mode 100644 index 000000000..bb08883a2 --- /dev/null +++ b/src/components/organisms/FancyRadio/FancyRadio.tsx @@ -0,0 +1,33 @@ +import React, { useId } from 'react'; + +import FancySelectWrapper from '@/components/molecules/FancySelectWrapper/FancySelectWrapper'; +import { RawRadio } from '@/components/atoms/RawRadio'; + +export type TFancyRadioProps = Omit, 'inputElement'> & + React.ComponentProps; + +// --------------------------------------------------------------------------- // +// --- The FancyRadio is a RadioInput with a Label and description porp ---- // +// --------------------------------------------------------------------------- // +const FancyRadio = React.forwardRef((props, ref) => { + const { align, alignInput, label, description, externalStyle, checked, id: idExternal, name, value, ...HTMLProps } = props; + const id = useId(); + + const pickedId = idExternal ? idExternal : id; + + return ( +
+ } + /> +
+ ); +}); + +export default FancyRadio; diff --git a/src/components/organisms/FancyRadio/index.ts b/src/components/organisms/FancyRadio/index.ts new file mode 100644 index 000000000..1c6f79482 --- /dev/null +++ b/src/components/organisms/FancyRadio/index.ts @@ -0,0 +1 @@ +export { default as FancyRadio } from './FancyRadio'; diff --git a/src/components/organisms/FancyRangeSlider/FancyRangeSlider.model.ts b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.model.ts new file mode 100644 index 000000000..3d22b61c9 --- /dev/null +++ b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.model.ts @@ -0,0 +1,19 @@ +import { ChangeEvent } from 'react'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; + +interface IFancyRangeSlider { + label?: string; + align?: 'left' | 'center'; + icon?: JSX.Element; + value?: number; + min?: number; + max?: number; + displayNumber?: boolean; + onChange?: (e: ChangeEvent) => void; + themeType?: TThemeTypes; + layer?: TLayer; + disabled?: boolean; +} + +export default IFancyRangeSlider; diff --git a/src/components/organisms/FancyRangeSlider/FancyRangeSlider.stories.tsx b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.stories.tsx new file mode 100644 index 000000000..24a4ccfed --- /dev/null +++ b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.stories.tsx @@ -0,0 +1,133 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +import FancyRangeSlider from './FancyRangeSlider'; + +// Define metadata for the story +const meta = { + component: FancyRangeSlider, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + label: { + description: 'The label of the input', + control: { + type: 'text', + }, + }, + align: { + description: 'The alignment of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'left', + }, + }, + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the input', + control: { + type: 'range ', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '4', + }, + }, + icon: { + description: 'The icon of the input', + defaultValue: { + summary: 'none', + }, + }, + disabled: { + description: 'The disabled state of the input', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + min: { + description: 'The minimum value of the input', + control: { + type: 'number', + }, + defaultValue: { + summary: 0, + }, + }, + max: { + description: 'The maximum value of the input', + control: { + type: 'number', + }, + defaultValue: { + summary: 100, + }, + }, + value: { + description: 'The current value of the input', + control: { + type: 'number', + }, + defaultValue: { + summary: 50, + }, + }, + displayNumber: { + description: 'The display number of the input', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + label: 'Label', + align: 'left', + themeType: 'secondary', + layer: 4, + icon: , + disabled: false, + min: 0, + max: 100, + value: 50, + displayNumber: true, + }, +}; diff --git a/src/components/organisms/FancyRangeSlider/FancyRangeSlider.style.tsx b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.style.tsx new file mode 100644 index 000000000..1bfe21680 --- /dev/null +++ b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.style.tsx @@ -0,0 +1,33 @@ +import { styled } from 'styled-components'; +import { AlignedInputLabel } from '../../atoms/AlignedInputLabel/AlignedInputLabel'; +import { TTheme } from '@/interface/TTheme'; + +export const RangeSliderContainer = styled.div` + grid-row: 2/3; + grid-column: 2/3; + position: relative; + width: 100%; +`; + +export const NumberContainer = styled.div` + position: relative; + box-sizing: border-box; + grid-row: 2 / 3; + grid-column: 3 / 4; + margin-left: 10px; +`; + +//the label inhert the style from the AlignedInputLabel +export const Label = styled(AlignedInputLabel)` + grid-row: 1/2; + grid-column: 2/3; +`; + +export const Icon = styled.div<{ theme: TTheme }>` + grid-column: 1/2; + grid-row: 2/3; + margin-bottom: ${({ theme }) => theme.spacing.xxs}; + margin-right: ${({ theme }) => theme.spacing.sm}; + display: flex; + align-items: center; +`; diff --git a/src/components/organisms/FancyRangeSlider/FancyRangeSlider.tsx b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.tsx new file mode 100644 index 000000000..e10491420 --- /dev/null +++ b/src/components/organisms/FancyRangeSlider/FancyRangeSlider.tsx @@ -0,0 +1,97 @@ +import React, { ChangeEvent, useId, useState } from 'react'; + +import RawSlider from '../../atoms/RawSlider/RawSlider'; +import FancyNumberInput from '../FancyNumberInput/FancyNumberInput'; +import { StyledInputWrapper } from '../../molecules/InputWrapper/InputWrapper.style'; +import FancySVGAtom from '../../atoms/FancySVGAtom/FancySVGAtom'; +import { Label, NumberContainer, RangeSliderContainer, Icon } from './FancyRangeSlider.style'; +import IFancyRangeSlider from './FancyRangeSlider.model'; +import calcColorState from '../../../design/designFunctions/calcColorState/calcColorState'; + +// --------------------------------------------------------------------------- // +// -------------------- The main FancySlider Component ----------------------- // +// --------------------------------------------------------------------------- // +export default function FancyRangeSlider(props: IFancyRangeSlider) { + const { label, align, icon, value, min, max, displayNumber, onChange, themeType, layer = 4, disabled } = { ...defaultProps, ...props }; + + const [isActive, setIsActive] = useState(false); + const [toutched, setToutched] = useState(false); + + const id = useId(); + + const colorStateLabel = calcColorState({ type: 'label', isActive: isActive || toutched, value }); + + // this function is called when the slider is moved + const changeHandler = (e: ChangeEvent) => { + // this line handles the the input bewteen number and slider + setToutched(true); + onChange && onChange(e); + }; + + // this function is called when the slider is clicked + const activeHandler = (value: boolean) => { + setIsActive(value); + }; + + return ( + + {/* Icon for the left side of the slider */} + {icon && ( + + + {icon} + + + )} + + {/* label for the top side of the slider */} + {label && ( + + )} + {/* Range Slider */} + + + + + {/* Number input for typing the number */} + {displayNumber && ( + + + + )} + + ); +} + +const defaultProps: IFancyRangeSlider = { + min: 0, + max: 100, +}; diff --git a/src/components/organisms/FancyRangeSlider/index.ts b/src/components/organisms/FancyRangeSlider/index.ts new file mode 100644 index 000000000..0482c873f --- /dev/null +++ b/src/components/organisms/FancyRangeSlider/index.ts @@ -0,0 +1 @@ +export { default as FancyRangeSlider } from './FancyRangeSlider'; diff --git a/src/components/organisms/FancySearchBar/FancySearchBar.stories.tsx b/src/components/organisms/FancySearchBar/FancySearchBar.stories.tsx new file mode 100644 index 000000000..81f21225c --- /dev/null +++ b/src/components/organisms/FancySearchBar/FancySearchBar.stories.tsx @@ -0,0 +1,82 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancySearchBar from './FancySearchBar'; + +import { FancyLoadingSpinner } from '../../atoms/FancyLoadingSpinner'; + +// Define metadata for the story +const meta = { + component: FancySearchBar, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + handlerSearchValue: { + description: 'Callback function for the search value', + control: { + type: 'function', + }, + }, + searchListWidth: { + description: 'The width of the search list', + control: { + type: 'text', + }, + defaultValue: { + summary: '100%', + }, + }, + searchValue: { + description: 'The search value', + control: { + type: 'text', + }, + }, + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'secondary', + }, + }, + layer: { + description: 'The layer of the input', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '4', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + handlerSearchValue: (value: string) => console.log(value), + searchListWidth: '100%', + layer: 2, + children: , + }, +}; diff --git a/src/components/organisms/FancySearchBar/FancySearchBar.tsx b/src/components/organisms/FancySearchBar/FancySearchBar.tsx new file mode 100644 index 000000000..c86189b38 --- /dev/null +++ b/src/components/organisms/FancySearchBar/FancySearchBar.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { styled } from 'styled-components'; + +import SearchBar from '../../molecules/SearchBar/SearchBar'; +import SearchBarList from '../../molecules/SearchBarList/SearchBarList'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; + +// Styled component for the entire search bar +const StyledFancySearchBar = styled.div` + height: 90%; + width: 100%; + max-height: 50px; + position: relative; + justify-content: center; + display: flex; + flex-direction: column; +`; + +// Styled component for the wrapper around the search bar list +const WrapperListInput = styled.div` + position: relative; +`; + +// Styled component for the search bar list +const WrapperList = styled.div<{ $width?: string }>` + position: absolute; + top: calc(100% + 4px); + width: ${({ $width }) => $width || '100%'}; // Set the width of the search bar list + left: 50%; + transform: translateX(-50%); + z-index: 100; +`; + +// Props for the FancySearchBar component +interface IFancySearchBar { + handlerSearchValue?: (searchValue: string) => void; + children?: React.ReactNode; + searchListWidth?: string; + searchValue?: string; + themeType?: TThemeTypes; + layer?: TLayer; +} + +// The FancySearchBar component +export default function FancySearchBar(props: IFancySearchBar) { + const { handlerSearchValue, children, searchListWidth, searchValue, themeType, layer } = props; + const [isActive, setIsActive] = useState(false); // State for whether the search bar list is active + + // Function to handle changes to the isActive state + const activeHandler = (isActive: boolean) => { + setIsActive(isActive); + }; + + // Function to handle changes to the search value + const searchValueHandler = (searchValue: string) => { + handlerSearchValue && handlerSearchValue(searchValue); + }; + + return ( + + + {/* The search bar where something can be searched */} + + {/* The search bar list */} + + + {children} + + + + + ); +} diff --git a/src/components/organisms/FancySearchBar/index.ts b/src/components/organisms/FancySearchBar/index.ts new file mode 100644 index 000000000..556bab95f --- /dev/null +++ b/src/components/organisms/FancySearchBar/index.ts @@ -0,0 +1 @@ +export { default as FancySearchBar } from './FancySearchBar'; diff --git a/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.mdx b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.mdx new file mode 100644 index 000000000..8e2e01a53 --- /dev/null +++ b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.mdx @@ -0,0 +1,80 @@ +## FancySwipeUpModal Documentation + +### Overview + +The FancySwipeUpModal is now powered by the `useFancySwipeUpStore`. This enhancement further simplifies the process of creating modals, while retaining the component's adaptability and feature-rich nature. With this update, developers can effortlessly integrate FancySwipeUpModal into their applications without the previous manual prop configurations. + +### Initial Setup + +1. **Render the Main Component in the Base File**: + + Place `` at the desired location in your main file. + +2. **Specify the DOM Element for Rendering**: + + Create a DOM element where the modal will be rendered. For instance: + + ```html + + ``` + +3. **Connect the Modal to the DOM**: + + Pass the `appendToDomID` prop to the modal component. For instance, `appendToDomID="modal"`. This ties the modal to the specified DOM element. Configuration settings are now managed by the store, simplifying the overall process. + +### Using the Modal + +1. **Import the Store in the Component**: + + First, you need to import the `useFancySwipeUpModalStore` within the component where you intend to trigger the modal. + + ```javascript + const openModal = useFancySwipeUpModalStore((state) => state.openSwipeUpModal); + ``` + +2. **Open the Modal**: + + Use the `openModal` method with the following parameters: + + - Modal ID (e.g., "CustomerModal") + - Child components to be displayed within the modal + - Configuration object specifying various options + + ```javascript + const openModalHandler = () => { + openModal( + 'modalTest', +
+ + closeModal('modalTest')} label="Close Modal"> +
, + { + isCloseAble: false, + isScalable: true, + } + ); + }; + ``` + +3. **Close the Modal**: + + To close the modal, you will need to utilize the `closeModal` method from the `useFancySwipeUpModalStore`. + + ```javascript + const closeModal = useFancySwipeUpModalStore((state) => state.closeSwipeUpModal); + ``` + + You can then call `closeModal` with the ID of the modal you wish to close, as shown in the `openModalHandler` example above. + +### Configuration Options + +When opening a modal, you can provide a configuration object with the following optional properties: + +- `themeType`: Accepts different theme values to alter the modal's appearance. +- `layer`: Sets the modal's depth. +- `isCloseAble`: A boolean that specifies if the user can close the modal. +- `isScalable`: A boolean that indicates whether the modal's height can be adjusted by the user. + +### Conclusion + +The revamped FancySwipeUpModal, powered by `useFancySwipeUpStore`, offers an even more streamlined approach to managing modals in applications. This update makes it easier to incorporate the modal component, providing a straightforward and efficient user experience. diff --git a/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.state.ts b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.state.ts new file mode 100644 index 000000000..2b0aae52b --- /dev/null +++ b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.state.ts @@ -0,0 +1,46 @@ +import { create } from 'zustand'; + +import { TModalStatus } from '../../../interface/TModalStatus'; +import { ISwipeUpModal } from '../../molecules/SwipeUpModal/ISwipeUpModal.model'; + +export type ModalSettings = Omit; + +export interface IFancySwipeUpModal { + id: string; + children: React.ReactNode; + status: TModalStatus; + config?: ModalSettings; +} + +export interface IFancySwipeUpModalStore { + modals: IFancySwipeUpModal[]; + openSwipeUpModal: (id: string, children: React.ReactNode, config?: ModalSettings) => void; + removeSwipeUpModal: (id: string) => void; + closeSwipeUpModal: (id: string) => void; +} + +// --------------------------------------------------------------------------- // +// ------------- The globale State to open and close ------------------------- // +// --------------------------------------------------------------------------- // +export const useFancySwipeUpModalStore = create((set) => ({ + // the state array for the modals + modals: [], + // add a new modal to the state array + openSwipeUpModal: (id, children, config) => + set((state) => { + console.log('openSwipeUpModal', id, children, config); + return { modals: [...state.modals, { children, id, status: 'open', config }] }; + }), + // change the status of the modal to closing + closeSwipeUpModal: (id) => + set((state) => ({ + modals: state.modals.map((modal) => (modal.id === id ? { ...modal, status: 'closing' } : modal)), + })), + // remove the modal from the state array + removeSwipeUpModal: (id) => + set((state) => ({ + modals: state.modals.filter((modal) => modal.id !== id), + })), + + isOpen: false, +})); diff --git a/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.stories.tsx b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.stories.tsx new file mode 100644 index 000000000..18f0c8f4b --- /dev/null +++ b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.stories.tsx @@ -0,0 +1,135 @@ +// Import necessary dependencies +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancySwipeUpModal from './FancySwipeUpModal'; +import { FancyButton } from '../FancyButton'; +import { useFancySwipeUpModalStore } from './FancySwipeUpModal.state'; +import { FancyTextInput } from '../FancyTextInput'; +import { ISwipeUpModal } from '../../molecules/SwipeUpModal/ISwipeUpModal.model'; + +// Define metadata for the story +const meta = { + title: 'components/ui/organisms/FancySwipeUpModal', + component: HelperComponent, + parameters: { + docs: { + description: { + component: 'Smart-Comonent: The FancySwipeUpModal is a smart component that handles all the logic for the SwipeUpModal.', + }, + }, + }, + // Define arguments for the story + argTypes: { + appendToDomID: { + description: 'The ID of the DOM element to append the modal to', + control: { + type: 'text', + }, + defaultValue: { + summary: 'body', + }, + }, + isOpen: { + description: 'The state of the modal', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + isScalable: { + description: 'Is the modal scalable by the user', + control: { + type: 'boolean', + }, + defaultValue: { + summary: true, + }, + }, + isCloseAble: { + description: 'Is the modal closeable by the user', + control: { + type: 'boolean', + }, + defaultValue: { + summary: true, + }, + }, + themeType: { + description: 'The theme of the modal', + control: { + type: 'select', + }, + options: ['primary', 'secondary', 'accent'], + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the modal', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 0, + }, + }, + }, + // Add tags to the story +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +function HelperComponent(props: React.ComponentProps & Omit) { + const { appendToDomID, children, ...configProps } = props; + const openModal = useFancySwipeUpModalStore((state) => state.openSwipeUpModal); + const closeModal = useFancySwipeUpModalStore((state) => state.closeSwipeUpModal); + + const openModalHandler = () => { + openModal( + appendToDomID || 'modalTest', + children || ( +
+ + closeModal('modalTest')} label="Close Modal"> +
+ ), + configProps || { + isCloseAble: false, + isScalable: true, + } + ); + }; + + return ( + <> + + openModalHandler()} /> + + ); +} + +HelperComponent.displayName = 'FancyModal'; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + <> + + + + ), + args: { + themeType: 'primary', + isCloseAble: true, + }, +}; diff --git a/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.tsx b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.tsx new file mode 100644 index 000000000..e3c29b4de --- /dev/null +++ b/src/components/organisms/FancySwipeUpModal/FancySwipeUpModal.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import SwipeUpModal from '../../molecules/SwipeUpModal/SwipeUpModal'; +import FancyPortal from '../../utils/components/FancyPortal/FancyPortal'; + +import { useFancySwipeUpModalStore } from './FancySwipeUpModal.state'; + +// --------------------------------------------------------------------------- // +// ----------- The main FancySwipeUpModal to handle all everything ----------- // +// --------------------------------------------------------------------------- // +interface IFancySwipeUpModal { + appendToDomID?: string; +} +export default function FancySwipeUpModal({ appendToDomID }: IFancySwipeUpModal) { + // get the global states and actions from the store to handle the modal + const modals = useFancySwipeUpModalStore((state) => state.modals); + const removeSwipeUpModal = useFancySwipeUpModalStore((state) => state.removeSwipeUpModal); + const closeSwipeUpModal = useFancySwipeUpModalStore((state) => state.closeSwipeUpModal); + + const closeModalHandler = (id: string) => { + // closing the modal is a two step process + // first we set the status to "closing" with the closeModal function + // this will trigger the animation in the modal component + closeSwipeUpModal(id); + + //wait for the animation and remove the modal from the store + setTimeout(() => { + removeSwipeUpModal(id); + }, 250); + }; + + return ( + + {/* The Mobile Modal Component */} + {modals.map((modal, key) => ( + closeModalHandler(modal.id)} isOpen={modal.status === 'open'}> + {/* render the content of the modal */} + {modal.children} + + ))} + + ); +} diff --git a/src/components/organisms/FancySwipeUpModal/index.ts b/src/components/organisms/FancySwipeUpModal/index.ts new file mode 100644 index 000000000..e6d55b765 --- /dev/null +++ b/src/components/organisms/FancySwipeUpModal/index.ts @@ -0,0 +1 @@ +export { default as FancySwipeUpModal } from './FancySwipeUpModal'; diff --git a/src/components/organisms/FancyTextInput/FancyTextInput.stories.tsx b/src/components/organisms/FancyTextInput/FancyTextInput.stories.tsx new file mode 100644 index 000000000..a29f7e960 --- /dev/null +++ b/src/components/organisms/FancyTextInput/FancyTextInput.stories.tsx @@ -0,0 +1,97 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyTextInput from './FancyTextInput'; + +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; + +// Define metadata for the story +const meta = { + component: FancyTextInput, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + value: { + description: 'The value of the input', + defaultValue: '', + control: { + type: 'text', + }, + }, + + layer: { + description: 'The layer of the modal', + defaultValue: 0, + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + themeType: { + description: 'The theme of the modal', + defaultValue: 'primary', + control: { + type: 'select', + }, + }, + isActive: { + description: 'The activity state of the input', + defaultValue: false, + control: { + type: 'boolean', + }, + }, + align: { + description: 'The alignment of the input', + defaultValue: 'left', + control: { + type: 'select', + }, + }, + label: { + description: 'The label of the input', + defaultValue: 'Label', + control: { + type: 'text', + }, + }, + errorMessage: { + description: 'The error message of the input', + defaultValue: '', + control: { + type: 'text', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + value: '', + layer: 0, + themeType: 'secondary', + isActive: false, + align: 'left', + label: 'Label', + errorMessage: '', + icon: , + }, +}; diff --git a/src/components/organisms/FancyTextInput/FancyTextInput.tsx b/src/components/organisms/FancyTextInput/FancyTextInput.tsx new file mode 100644 index 000000000..a4c309ddf --- /dev/null +++ b/src/components/organisms/FancyTextInput/FancyTextInput.tsx @@ -0,0 +1,51 @@ +import React, { useId, useState } from 'react'; +import TextInput, { ITextInputProps } from '../../molecules/TextInput/TextInput'; +import InputWrapper, { IInputWrapperUserInputProps } from '../../molecules/InputWrapper/InputWrapper'; + +type IFancyTextInputProps = ITextInputProps & Omit; +// --------------------------------------------------------------------------- // +// ----The TextInput Comonent with surrounding icon, label and underline ----- // +// --------------------------------------------------------------------------- // +export default function FancyTextInput(props: IFancyTextInputProps) { + const { id, value, activeHandler, themeType, layer, placeholder, errorMessage, disabled, align, icon, label, ...inputProps } = props; + + //the states activity of the input + const [isActive, setIsActive] = useState(false); + + // if no id is provided, generate a random one + const useid = useId(); + const usedId = id ? id : useid; + + // handles the focus and blur events and calls the handler from the parent + const activeFocusHandler = (value: boolean) => { + setIsActive(value); + activeHandler && activeHandler(value); + }; + + return ( + + } + /> + ); +} diff --git a/src/components/organisms/FancyTextInput/index.ts b/src/components/organisms/FancyTextInput/index.ts new file mode 100644 index 000000000..96aeeac3e --- /dev/null +++ b/src/components/organisms/FancyTextInput/index.ts @@ -0,0 +1 @@ +export { default as FancyTextInput } from './FancyTextInput'; diff --git a/src/components/organisms/FancyToastMessage/FancyToastMessage.state.tsx b/src/components/organisms/FancyToastMessage/FancyToastMessage.state.tsx new file mode 100644 index 000000000..20d17fdd4 --- /dev/null +++ b/src/components/organisms/FancyToastMessage/FancyToastMessage.state.tsx @@ -0,0 +1,23 @@ +import { create } from 'zustand'; +import IToastMessage from '../../molecules/SingleToastMessage/IToastMessage.model'; + +//omit id from IToastMessage because the store will add the id +type IToastMessageProps = Omit; + +type ToastMessageStore = { + toastQueue: IToastMessage[]; + addToast: (toast: IToastMessageProps) => void; + removeToast: (id: number) => void; +}; +//the toastQueue with the add and remove functions +export const useFancyToastMessageStore = create((set) => ({ + toastQueue: [], + addToast: (toast) => + set((state) => ({ + toastQueue: [...state.toastQueue, { ...toast, id: Date.now(), time: toast.time || 5000 }], + })), + removeToast: (id) => + set((state) => ({ + toastQueue: state.toastQueue.filter((toast) => toast.id !== id), + })), +})); diff --git a/src/components/organisms/FancyToastMessage/FancyToastMessage.stories.tsx b/src/components/organisms/FancyToastMessage/FancyToastMessage.stories.tsx new file mode 100644 index 000000000..dcec6b16a --- /dev/null +++ b/src/components/organisms/FancyToastMessage/FancyToastMessage.stories.tsx @@ -0,0 +1,49 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyToastMessage from './FancyToastMessage'; +import { useFancyToastMessageStore } from './FancyToastMessage.state'; +import { FancyButton } from '../FancyButton'; + +// Define metadata for the story +const meta = { + component: FancyToastMessage, + parameters: { + docs: { + description: { + component: + 'Smart-Comonent: The FancyToastMessage Component is a smart component to handle all toast messages in the application. It should be used as overlay in the application to make sure the toast messages are always displaey on top. To use this component in the application you have to add this component to the dom (Main Component) of the application. To add the toast messages you have to import the useToastMessageStore and addToast function const addToast = useToastMessageStore((state) => state.addToast); addToast({ title: "My Title of the titel ", message: "This is my toast message hjsadhjgdshjag.", time: 50500, type: "error", });', + }, + }, + }, + // Define arguments for the story + argTypes: {}, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +const clickHandler = () => { + useFancyToastMessageStore.getState().addToast({ + title: 'My Title of the titel ', + message: 'This is my toast message hjsadhjgdshjag.', + time: 50500, + themeType: 'error', + }); +}; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + <> + + + + ), + args: {}, +}; diff --git a/src/components/organisms/FancyToastMessage/FancyToastMessage.style.tsx b/src/components/organisms/FancyToastMessage/FancyToastMessage.style.tsx new file mode 100644 index 000000000..7f1439648 --- /dev/null +++ b/src/components/organisms/FancyToastMessage/FancyToastMessage.style.tsx @@ -0,0 +1,14 @@ +import { styled } from 'styled-components'; + +import { TTheme } from '@/interface/TTheme'; + +export const ToastsWrapper = styled.div<{ theme: TTheme }>` + position: fixed; + z-index: 1000; + top: ${({ theme }) => theme.spacing.md}; + right: ${({ theme }) => theme.spacing.md}; + width: calc(100% - ${({ theme }) => theme.spacing.md} * 2); + max-width: 350px; + display: flex; + flex-direction: column; +`; diff --git a/src/components/organisms/FancyToastMessage/FancyToastMessage.tsx b/src/components/organisms/FancyToastMessage/FancyToastMessage.tsx new file mode 100644 index 000000000..056211df7 --- /dev/null +++ b/src/components/organisms/FancyToastMessage/FancyToastMessage.tsx @@ -0,0 +1,60 @@ +import React, { useMemo } from 'react'; +import { useTransition, animated } from '@react-spring/web'; + +import { useFancyToastMessageStore } from './FancyToastMessage.state'; +import SingleToastMessage from '../../molecules/SingleToastMessage/SingleToastMessage'; +import { ToastsWrapper } from './FancyToastMessage.style'; +import IToastMessage from '../../molecules/SingleToastMessage/IToastMessage.model'; + +//this comonent should be used as overlay in the application to make sure the toast messages are always displaey on top + +// to use this component in the application you have to add this component to the dom (Main Component) of the application +// to add the toast messages you have to import the useToastMessageStore and addToast function const addToast = useToastMessageStore((state) => state.addToast); +// addToast({ +// title: 'My Title of the titel ', +// message: 'This is my toast message hjsadhjgdshjag.', +// time: 50500, +// type: 'error', +// }); + +// --------------------------------------------------------------------------- // +// ------- The Main Toast Message Module to displayed multible messages ------ // +// --------------------------------------------------------------------------- // +export default function FancyToastMessage() { + const toastQueue = useFancyToastMessageStore((state) => state.toastQueue); + const removeToast = useFancyToastMessageStore((state) => state.removeToast); + + // create a refMap to store the height of each toast message + const refMap = useMemo(() => new WeakMap(), []); + + // create the transitions for the toast messages + const transitions = useTransition(toastQueue, { + from: { opacity: 0, height: '0px', transform: 'translateX(200%)', marginBottom: '0px' }, + keys: (item: IToastMessage) => item.id, + enter: (item: IToastMessage) => async (next) => { + const getItem = refMap.get(item) as HTMLDivElement; + await next({ + opacity: 1, + transform: 'translateX(0%)', + marginBottom: '16px', + height: getItem.offsetHeight + 'px', + //config: { duration: refMap.get(item).time }, + }); + }, + leave: [ + { transform: 'translateX(60%)', opacity: 0, config: { duration: 220, tension: 250, friction: 125, precision: 0.1 } }, + { height: '0px', marginBottom: '0px', config: { duration: 265, tension: 250, friction: 125, precision: 0.8 } }, + ], + onDestroyed: (item: IToastMessage) => removeToast(item.id), + }); + + return ( + + {transitions(({ ...style }, item: IToastMessage) => ( + + ref && refMap.set(item, ref)} toast={item} remove={removeToast} /> + + ))} + + ); +} diff --git a/src/components/organisms/FancyToastMessage/index.ts b/src/components/organisms/FancyToastMessage/index.ts new file mode 100644 index 000000000..482696292 --- /dev/null +++ b/src/components/organisms/FancyToastMessage/index.ts @@ -0,0 +1 @@ +export { default as FancyToastMessage } from './FancyToastMessage'; diff --git a/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.stories.tsx b/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.stories.tsx new file mode 100644 index 000000000..2ce877d4a --- /dev/null +++ b/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.stories.tsx @@ -0,0 +1,103 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; +import SVGCheckMark from '../../icons/SVGCheckMark/SVGCheckMark'; +// Import the component to be tested +import FancyBottomBarIcon from './FancyBottomBarIcon'; +// Define metadata for the story +const meta = { + component: FancyBottomBarIcon, + parameters: { + docs: { + description: { + component: + 'The FancyBottomBarIcon component is for displaying a fancy bottom bar icon, it can be used for displaying a navivation button in da navigation bar.
ItΒ΄s only the content wrapped in a ComponentAndActionWrapper component, so it can be used as a link or button or wrapped with the WrapperComponent prop in a e.g. NextJS Link Component . ', + }, + }, + }, + + // Define arguments for the story + argTypes: { + type: { + control: { type: 'select' }, + defaultValue: { + summary: 'a', + }, + }, + href: { + control: { type: 'text' }, + }, + isActive: { + control: { type: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + label: { + control: { type: 'text' }, + }, + layer: { + control: { type: 'range', min: 0, max: 10, step: 1 }, + defaultValue: { + summary: 1, + }, + }, + onClick: { + control: { type: 'function' }, + }, + disabled: { + control: { type: 'boolean' }, + defaultValue: { + summary: false, + }, + }, + themeType: { + control: { type: 'select' }, + defaultValue: { + summary: 'secondary', + }, + }, + icon: { + control: { type: 'object' }, + }, + WrapperComponent: { + control: { type: 'object' }, + description: 'Provide a wrapper component like NextJS Link Component or a Custom component. ', + }, + hideLabel: { + control: { type: 'boolean' }, + description: 'Hide the label text. When The label is hidden, the icon will get bigger and the aria-label is putted to the icon.', + defaultValue: { + summary: false, + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + type: 'a', + href: 'https://www.google.com', + isActive: false, + label: 'Label', + disabled: false, + themeType: 'secondary', + layer: 1, + icon: , + }, + parameters: { + docs: { + description: { + story: '', + }, + }, + }, +}; diff --git a/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.style.tsx b/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.style.tsx new file mode 100644 index 000000000..7014c681a --- /dev/null +++ b/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.style.tsx @@ -0,0 +1,17 @@ +import { styled } from 'styled-components'; + +// Define the styled component for the item wrapper +export const ItemWrapper = styled.div` + flex: 1 0 64px; + filter: drop-shadow(0px 0px 16px rgba(0, 0, 0, 0.55)); +`; + +export const Icon = styled.div` + height: 18px; + width: 18px; + + svg { + height: 100%; + width: 100%; + } +`; diff --git a/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.tsx b/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.tsx new file mode 100644 index 000000000..533f163d4 --- /dev/null +++ b/src/components/templates/FancyBottomBarIcon/FancyBottomBarIcon.tsx @@ -0,0 +1,25 @@ +import React, { ComponentProps } from 'react'; + +import ComponentAndActionWrapper, { IComponentAndActionWrapper } from '../../molecules/ComponentAndActionWrapper/ComponentAndActionWrapper'; +import BottomBarIcon from '../../molecules/BottomBarIcon/BottomBarIcon'; + +export type IFancyBottomBarIcon = ComponentProps & IComponentAndActionWrapper; +// --------------------------------------------------------------------------- // +// ------ This Component Puts only the content and a wrapper together -------- // +// --------------------------------------------------------------------------- // +export default function FancyBottomBarIcon(props: IFancyBottomBarIcon) { + const { WrapperComponent, type, href, onClick, ...rest } = props; + + return ( + { + onClick && onClick(); + }} + > + + + ); +} diff --git a/src/components/templates/FancyBottomBarIcon/index.ts b/src/components/templates/FancyBottomBarIcon/index.ts new file mode 100644 index 000000000..68c89ad61 --- /dev/null +++ b/src/components/templates/FancyBottomBarIcon/index.ts @@ -0,0 +1 @@ +export { default as FancyBottomBarIcon } from './FancyBottomBarIcon'; diff --git a/src/components/templates/FancyFlexBox/FancyFlexBox.model.ts b/src/components/templates/FancyFlexBox/FancyFlexBox.model.ts new file mode 100644 index 000000000..fef0a5f4c --- /dev/null +++ b/src/components/templates/FancyFlexBox/FancyFlexBox.model.ts @@ -0,0 +1,12 @@ +export type TStyleProps = { + flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'; + flexJustify?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; + flexAlign?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'; + gap?: string; +}; + +export type FancyFlexBoxProps = { + children?: React.ReactNode; + separator?: React.ReactNode; + inline?: boolean; +} & TStyleProps; diff --git a/src/components/templates/FancyFlexBox/FancyFlexBox.style.tsx b/src/components/templates/FancyFlexBox/FancyFlexBox.style.tsx new file mode 100644 index 000000000..63a884649 --- /dev/null +++ b/src/components/templates/FancyFlexBox/FancyFlexBox.style.tsx @@ -0,0 +1,26 @@ +import { styled, css } from 'styled-components'; +import { TStyleProps } from './FancyFlexBox.model'; +import IStyledPrefixAndPicker from '../../../interface/IStyledPrefixAndPicker.model'; +import { TTheme } from '@/interface/TTheme'; + +const generateFlexSytles = (props: TStyledFlexBoxProps & { theme?: TTheme }) => { + const { $flexDirection, $flexJustify, $flexAlign, $gap, theme } = props; + + return css` + display: flex; + width: 100%; + flex-direction: ${$flexDirection ?? 'row'}; + justify-content: ${$flexJustify ?? 'flex-start'}; + align-items: ${$flexAlign ?? 'flex-start'}; + gap: ${$gap ?? theme!.spacing.xxs}; + `; +}; + +type TStyledFlexBoxProps = IStyledPrefixAndPicker; +export const StyledFlexBox = styled.div` + ${(props: TStyledFlexBoxProps) => generateFlexSytles({ ...props })} +`; + +export const StyledInlineFlexBox = styled.span` + ${(props: TStyledFlexBoxProps) => generateFlexSytles({ ...props })} +`; diff --git a/src/components/templates/FancyFlexBox/FancyFlexBox.tsx b/src/components/templates/FancyFlexBox/FancyFlexBox.tsx new file mode 100644 index 000000000..b8272141f --- /dev/null +++ b/src/components/templates/FancyFlexBox/FancyFlexBox.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { FancyFlexBoxProps } from './FancyFlexBox.model'; +import { StyledFlexBox, StyledInlineFlexBox } from './FancyFlexBox.style'; + +// --------------------------------------------------------------------------- // +// ------------ A layout component that helps align with flex box ------------ // +// --------------------------------------------------------------------------- // +export default function FancyFlexBox(props: FancyFlexBoxProps) { + const { children, inline, separator, ...flexAligns } = props; + + // Determine which container to use based on the inline prop. + const Container = inline ? StyledInlineFlexBox : StyledFlexBox; + + // Modify the children components to include a separator if specified. + const modifiedChilds = separator + ? React.Children.map(children, (child, index) => { + if (index === 0) return child; + return ( + <> + {separator} + {child} + + ); + }) + : children; + + // Render the flexbox container with the modified children components and flex alignment props. + return ( + + {modifiedChilds} + + ); +} diff --git a/src/components/templates/FancyFlexBox/index.ts b/src/components/templates/FancyFlexBox/index.ts new file mode 100644 index 000000000..9eb917dbb --- /dev/null +++ b/src/components/templates/FancyFlexBox/index.ts @@ -0,0 +1 @@ +export { default as FancyFlexBox } from './FancyFlexBox'; diff --git a/src/components/templates/FancyGrid/FancyGrid.mdx b/src/components/templates/FancyGrid/FancyGrid.mdx new file mode 100644 index 000000000..3298cc498 --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGrid.mdx @@ -0,0 +1,64 @@ +# FancyGrid Component Documentation + +`FancyGrid` is a custom grid layout components. It's built using styled-components and is designed to be both flexible and responsive. + +## Installation + +Before using `FancyGrid`, make sure you have React, TypeScript, and styled-components installed in your project. + +## Usage + +### FancyGrid Container + +The `FancyGrid` container establishes the grid layout. + +**Props**: + +- `grid`: Specifies the number of columns in the grid (e.g., 12). +- `space`: Sets the spacing between grid items (e.g., "2px"). + +**Example**: + +```jsx +import FancyGrid from './FancyGrid'; + +const MyComponent = () => { + return ( + + {/* Grid items will be placed here */} + + ); +}; +``` + +### FancyGrid.Item + +`FancyGrid.Item` represents individual items within the `FancyGrid`. + +**Props**: + +- `gridSpace`: Determines the number of columns the item should span. + +**Example**: + +```jsx + + {/* Content for the first item */} + {/* Content for the second item */} + {/* Content for the third item */} + {/* Since the grid limit is reached, the next item starts on a new line */} + {/* Content for the fourth item */} + +``` + +## Responsive Design + +`FancyGrid` supports responsive design. You can specify different `gridSpace` values for different screen sizes to ensure your layout adapts to various devices. + +## Customization + +You can customize the `FancyGrid` and `FancyGrid.Item` by passing additional styling props or by extending the styled components to fit your design requirements. + +--- + +This documentation provides a basic guideline on how to integrate and use the `FancyGrid` component in your React applications. Adjust your implementation as needed to match the specific requirements of your project. diff --git a/src/components/templates/FancyGrid/FancyGrid.stories.tsx b/src/components/templates/FancyGrid/FancyGrid.stories.tsx new file mode 100644 index 000000000..65e0c87b9 --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGrid.stories.tsx @@ -0,0 +1,70 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyGrid from './FancyGrid'; +import { FancyButton } from '../../organisms/FancyButton'; + +// Define metadata for the story +const meta = { + component: FancyGrid, + parameters: { + docs: { + description: { + component: 'Layout-Component: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + grid: { + description: 'The value of the input', + defaultValue: '12', + control: { + type: 'number', + }, + }, + gap: { + description: 'The layer of the modal', + defaultValue: '0', + control: { + type: 'text', + }, + }, + }, +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + + + + + + + + + + + + + + + + + + + + ), + args: { + grid: 12, + gap: '12px', + }, +}; diff --git a/src/components/templates/FancyGrid/FancyGrid.tsx b/src/components/templates/FancyGrid/FancyGrid.tsx new file mode 100644 index 000000000..86cdd4665 --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGrid.tsx @@ -0,0 +1,35 @@ +// FancyGrid.tsx +import React from 'react'; +import { styled } from 'styled-components'; + +import FancyGridItem from './FancyGridItem/FancyGridItem'; +import IStyledPrefixAndOmitter from '@/interface/IStyledPrefixAndOmiter.model'; + +interface FancyGridProps { + grid?: number; + gap?: string; + children?: React.ReactNode; +} + +const GridContainer = styled.div>` + display: grid; + width: 100%; + grid-template-columns: repeat(${(props) => props.$grid}, 1fr); + grid-gap: ${(props) => props.$gap}; +`; + +// --------------------------------------------------------------------------- // +// ------- The FancyGrid to allocate the grid and give the items space ------- // +// --------------------------------------------------------------------------- // +function FancyGrid(props: FancyGridProps) { + const { children, grid = 12, gap } = props; + return ( + + {children} + + ); +} + +FancyGrid.Item = FancyGridItem; + +export default FancyGrid; diff --git a/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.mdx b/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.mdx new file mode 100644 index 000000000..3c97099fa --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.mdx @@ -0,0 +1,46 @@ +# FancyGrid.Item Component Documentation + +`FancyGrid.Item` is a subcomponent of the `FancyGrid` component, designed for creating and managing individual items within the `FancyGrid` layout in React applications. It's built using styled-components and allows for responsive and flexible item placement within the grid. + +## Installation + +Ensure that `FancyGrid` is already set up in your project, as `FancyGrid.Item` is a part of it. Also, make sure React, TypeScript, and styled-components are installed. + +## Usage + +### Basic Use of FancyGrid.Item + +`FancyGrid.Item` is used within the `FancyGrid` container and requires the `FancyGrid` component to be properly functioning. + +**Props**: + +- `gridSpace`: Specifies the number of columns the item should span within the grid. + +**Example**: + +```jsx +import FancyGrid from './FancyGrid'; + +const MyComponent = () => { + return ( + + {/* Content for a half-width item */} + {/* Content for another half-width item */} + + ); +}; +``` + +In this example, each `FancyGrid.Item` spans half the width of the grid (6 out of 12 columns). + +### Responsive Design + +`FancyGrid.Item` can be used to create responsive layouts within `FancyGrid`. Adjust the `gridSpace` prop based on the desired responsiveness of your layout. + +### Customization + +`FancyGrid.Item` can be customized further by passing additional styling props or by extending its styled component for more specific design needs. + +--- + +This documentation provides guidance on using the `FancyGrid.Item` component within the `FancyGrid` layout. It's designed to be flexible and easy to integrate into various types of grid-based layouts in React applications. diff --git a/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.stories.tsx b/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.stories.tsx new file mode 100644 index 000000000..232941591 --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.stories.tsx @@ -0,0 +1,45 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyGridItem from './FancyGridItem'; +import { FancyButton } from '../../../organisms/FancyButton'; + +// Define metadata for the story +const meta = { + component: FancyGridItem, + parameters: { + docs: { + description: { + component: 'Layout-Component: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + gridSpace: { + description: 'The space wich the item should take in the grid', + defaultValue: '12', + control: { + type: 'number', + }, + }, + }, +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + + + ), + args: { + gridSpace: 6, + }, +}; diff --git a/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.tsx b/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.tsx new file mode 100644 index 000000000..2b3bec0ed --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGridItem/FancyGridItem.tsx @@ -0,0 +1,19 @@ +import { styled } from 'styled-components'; +import IStyledPrefixAndPicker from '../../../../interface/IStyledPrefixAndPicker.model'; + +interface FancyGridItemProps { + gridSpace: number; + children?: React.ReactNode; +} + +type TGirdItemProps = IStyledPrefixAndPicker; +const GridItem = styled.div` + grid-column: span ${({ $gridSpace }) => $gridSpace}; +`; + +// --------------------------------------------------------------------------- // +// ------------ The FancyGrid Item to get the space for the item - ----------- // +// --------------------------------------------------------------------------- // +export default function FancyGridItem({ children, gridSpace }: FancyGridItemProps) { + return {children}; +} diff --git a/src/components/templates/FancyGrid/FancyGridItem/index.ts b/src/components/templates/FancyGrid/FancyGridItem/index.ts new file mode 100644 index 000000000..421f27168 --- /dev/null +++ b/src/components/templates/FancyGrid/FancyGridItem/index.ts @@ -0,0 +1 @@ +export { default as FancyGridItem } from './FancyGridItem'; diff --git a/src/components/templates/FancyGrid/index.ts b/src/components/templates/FancyGrid/index.ts new file mode 100644 index 000000000..9bcb88acc --- /dev/null +++ b/src/components/templates/FancyGrid/index.ts @@ -0,0 +1 @@ +export { default as FancyGrid } from './FancyGrid'; diff --git a/src/components/templates/FancyHandyNav/FancyHandyNav.store.ts b/src/components/templates/FancyHandyNav/FancyHandyNav.store.ts new file mode 100644 index 000000000..e4bad8aa7 --- /dev/null +++ b/src/components/templates/FancyHandyNav/FancyHandyNav.store.ts @@ -0,0 +1,19 @@ +import { create } from 'zustand'; + +interface IFancyHandyNavStore { + isVisible: boolean; + setIsVisible: (isVisible: boolean) => void; + + whichIsActive: string; + setWhichIsActive: (whichIsActive: string) => void; +} + +const useFancyHandyNavStore = create((set) => ({ + isVisible: true, + setIsVisible: (isVisible: boolean) => set({ isVisible }), + + whichIsActive: 'home', + setWhichIsActive: (whichIsActive: string) => set({ whichIsActive }), +})); + +export default useFancyHandyNavStore; diff --git a/src/components/templates/FancyHandyNav/FancyHandyNav.stories.tsx b/src/components/templates/FancyHandyNav/FancyHandyNav.stories.tsx new file mode 100644 index 000000000..f87269786 --- /dev/null +++ b/src/components/templates/FancyHandyNav/FancyHandyNav.stories.tsx @@ -0,0 +1,151 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyHandyNav from './FancyHandyNav'; +import { IFancyBottomBarIcon } from '../FancyBottomBarIcon/FancyBottomBarIcon'; + +const svg = ( + + + + +); + +const items2: IFancyBottomBarIcon[] = [ + { icon: svg, type: 'button', label: 'myButton', onClick: () => console.log('asas') }, + { icon: svg, type: 'button', label: 'myButton', href: 'http://google.de' }, + { icon: svg, type: 'button', label: 'myButton', href: 'http://google.de' }, + { icon: svg, type: 'button', label: 'myButton', href: 'http://google.de' }, +]; + +// Define metadata for the story +const meta = { + component: FancyHandyNav, + parameters: { + docs: { + description: { + component: 'Layout-Component: The FancyPasswordInput Comonent with surrounding icon, label and underline', + }, + }, + }, + // Define arguments for the story + argTypes: { + isVisible: { + description: 'Is the nav visible', + defaultValue: { + summary: true, + }, + control: { + type: 'boolean', + }, + }, + wichIndexIsActive: { + description: 'The layer of the modal', + defaultValue: { + summary: '0', + }, + control: { + type: 'text', + }, + }, + items: { + description: 'The items of the nav', + defaultValue: '', + control: { + type: 'object', + }, + }, + themeType: { + description: 'The themeType of the nav', + defaultValue: { + summary: 'primary', + }, + control: { + type: 'select', + }, + }, + themeTypeIcons: { + description: 'The themeType of the icons', + defaultValue: { + summary: 'secondary', + }, + control: { + type: 'select', + }, + }, + themeTypeSwitchList: { + description: 'The themeType of the switchlist', + defaultValue: { + summary: 'accent', + }, + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the nav', + defaultValue: { + summary: 1, + }, + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + externalStyle: { + description: 'The external style of the nav', + defaultValue: { + summary: '', + }, + control: { + type: 'object', + }, + }, + outlined: { + description: 'Is the nav outlined', + defaultValue: { + summary: false, + }, + control: { + type: 'boolean', + }, + }, + outlinedBackgroundStrength: { + description: 'The background strength of the nav', + defaultValue: { + summary: 0.9, + }, + control: { + type: 'range', + min: 0, + max: 1, + step: 0.1, + }, + }, + }, +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + items: items2, + isVisible: true, + wichIndexIsActive: '0', + themeType: 'primary', + layer: 1, + externalStyle: '', + outlined: false, + outlinedBackgroundStrength: 0.9, + themeTypeIcons: 'secondary', + themeTypeSwitchList: 'accent', + }, +}; diff --git a/src/components/templates/FancyHandyNav/FancyHandyNav.tsx b/src/components/templates/FancyHandyNav/FancyHandyNav.tsx new file mode 100644 index 000000000..8e5f6d061 --- /dev/null +++ b/src/components/templates/FancyHandyNav/FancyHandyNav.tsx @@ -0,0 +1,82 @@ +import React, { useEffect } from 'react'; +import { CSSProp, css } from 'styled-components'; + +import SwitchList from '../../molecules/SwitchList/SwitchList'; +import useFancyHandyNavStore from './FancyHandyNav.store'; +import BottomBar from '../../molecules/BottomBar/BottomBar'; +import FancyBottomBarIcon, { IFancyBottomBarIcon } from '../FancyBottomBarIcon/FancyBottomBarIcon'; +import RawNav from '../../atoms/RawNav/RawNav'; +import { TLayer } from '@/interface/TLayer'; +import { TThemeTypes } from '@/interface/TUiColors'; +interface IFancyHandyNav { + items?: IFancyBottomBarIcon[]; + isVisible?: boolean; + wichIndexIsActive?: string; + themeType?: TThemeTypes; + themeTypeIcons?: TThemeTypes; + themeTypeSwitchList?: TThemeTypes; + layer?: TLayer; + outlined?: boolean; + outlinedBackgroundStrength?: number; + externalStyle?: CSSProp; + className?: string; +} +// --------------------------------------------------------------------------- // +// ---------- A handyNavBar that can dynamicly generated via objects---------- // +// --------------------------------------------------------------------------- // +export default function FancyHandyNav(props: IFancyHandyNav) { + const { items, isVisible, wichIndexIsActive, themeType, themeTypeIcons, themeTypeSwitchList, layer, externalStyle } = props; + + // setup a global zustand store for the visibility and the active index + const isVisibleState = useFancyHandyNavStore((state) => state.isVisible); + const setIsVisible = useFancyHandyNavStore((state) => state.setIsVisible); + const stateWhichIsActive = useFancyHandyNavStore((state) => state.whichIsActive); + const setWhichIsActiveState = useFancyHandyNavStore((state) => state.setWhichIsActive); + + // Set the default values / initial values + useEffect(() => { + setWhichIsActiveState(wichIndexIsActive ?? '0'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // handle the visibility of the nav + useEffect(() => { + setIsVisible(isVisible ?? true); + }, [isVisible, setIsVisible]); + + return ( + <> + {isVisibleState && ( + // The Navbar container + + + {/* The List with the items */} + + {items?.map((item, index) => ( + { + setWhichIsActiveState(index.toString()); + item.onClick && item.onClick(); + }} + /> + ))} + + + + )} + + ); +} diff --git a/src/components/templates/FancyHandyNav/index.ts b/src/components/templates/FancyHandyNav/index.ts new file mode 100644 index 000000000..2bd0577b4 --- /dev/null +++ b/src/components/templates/FancyHandyNav/index.ts @@ -0,0 +1 @@ +export { default as FancyHandyNav } from './FancyHandyNav'; diff --git a/src/components/templates/FancyInfoCard/FancyInfoCard.stories.tsx b/src/components/templates/FancyInfoCard/FancyInfoCard.stories.tsx new file mode 100644 index 000000000..22ecac0b8 --- /dev/null +++ b/src/components/templates/FancyInfoCard/FancyInfoCard.stories.tsx @@ -0,0 +1,68 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyInfoCard from './FancyInfoCard'; + +const meta = { + component: FancyInfoCard, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: Thats only the Style of the InfoCard, it accepts all kind of children.', + }, + }, + }, + argTypes: { + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + }, + outlined: { + description: 'The component has a outline style', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + outlinedBackgroundStrength: { + description: 'The background strength of the outline', + control: { + type: 'range', + min: 0, + max: 1, + step: 0.01, + }, + defaultValue: { + summary: 10, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + icon: <> πŸŒ‡ , + title: 'This is a InfoCard', + description: 'This is a InfoCard', + themeType: 'primary', + layer: 1, + }, +}; diff --git a/src/components/templates/FancyInfoCard/FancyInfoCard.tsx b/src/components/templates/FancyInfoCard/FancyInfoCard.tsx new file mode 100644 index 000000000..e638d9d7b --- /dev/null +++ b/src/components/templates/FancyInfoCard/FancyInfoCard.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import InfoCard from '../../molecules/InfoCard/InfoCard'; +import FancyContent from '../../molecules/FancyContent/FancyContent'; +import { TTypography } from '@/interface/TTypography'; +import { TSizes } from '../../../interface/TComponentSizes'; + +type TSizeObj = { + iconSize: TSizes; + title: TTypography; + descriptionSize: TTypography; +}; + +type TSizesObject = { + [K in TSizes]: TSizeObj; +}; + +// the template for the sizes +const sizes: TSizesObject = { + sm: { + iconSize: 'md', + title: 'h6', + descriptionSize: 'smText', + }, + md: { + iconSize: 'lg', + title: 'h5', + descriptionSize: 'content', + }, + lg: { + iconSize: 'lg', + title: 'h4', + descriptionSize: 'button', + }, +}; + +type TFancyInfoCardProps = { + title?: string; + description?: string; + icon?: React.ReactNode; +} & React.ComponentProps; +// --------------------------------------------------------------------------- // +// ------- This is a Template for a Infocard with icon Title und desc. ------- // +// --------------------------------------------------------------------------- // +export default function FancyInfoCard(props: TFancyInfoCardProps) { + const { title, description, icon, size = 'md', ...infoCardProps } = props; + + return ( + + + {icon && {icon}} + {title && {title}} + {description && {description}} + + + ); +} diff --git a/src/components/templates/FancyInfoCard/index.ts b/src/components/templates/FancyInfoCard/index.ts new file mode 100644 index 000000000..a38e54f44 --- /dev/null +++ b/src/components/templates/FancyInfoCard/index.ts @@ -0,0 +1 @@ +export { default as FancyInfoCard } from './FancyInfoCard'; diff --git a/src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.stories.tsx b/src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.stories.tsx new file mode 100644 index 000000000..7f3a6b788 --- /dev/null +++ b/src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.stories.tsx @@ -0,0 +1,65 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyMenu from './FancyMenu'; + +// Define metadata for the story +const meta = { + component: FancyMenu, + parameters: { + docs: { + description: { + component: 'Template: The FancyMenueItem is a template for a finished Item it used by the FancyMenu', + }, + }, + }, + // Define arguments for the story + argTypes: { + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the button hover effect', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '3', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => ( + + βš™οΈ} /> + + βš™οΈ} /> + + βš™οΈ} /> + βš™οΈ} /> + + πŸšͺ} /> + + ), + args: {}, +}; diff --git a/src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.tsx b/src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.tsx new file mode 100644 index 000000000..71a1d78d4 --- /dev/null +++ b/src/components/templates/FancyMenueComponent/FancyMenu/FancyMenu.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import FancyMenuItem from '../FancyMenuItem/FancyMenuItem'; +import ListDivider from '../../../atoms/ListDivider/ListDivider'; +import MenuList from '../../../molecules/MenuList/MenuList'; + +type TFancyMenuProps = { + children?: React.ReactNode; +} & React.ComponentProps; +// --------------------------------------------------------------------------- // +// --- The FancyList is a Template with Anchor the Menue Item and Divider ---- // +// --------------------------------------------------------------------------- // +function FancyMenu(props: TFancyMenuProps) { + const { children, ...menueListProps } = props; + return {children}; +} + +FancyMenu.Item = FancyMenuItem; +FancyMenu.Divider = ListDivider; + +export default FancyMenu; diff --git a/src/components/templates/FancyMenueComponent/FancyMenu/index.ts b/src/components/templates/FancyMenueComponent/FancyMenu/index.ts new file mode 100644 index 000000000..130e6328a --- /dev/null +++ b/src/components/templates/FancyMenueComponent/FancyMenu/index.ts @@ -0,0 +1 @@ +export { default as FancyMenuList } from './FancyMenu'; diff --git a/src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.stories.tsx b/src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.stories.tsx new file mode 100644 index 000000000..08c281b9a --- /dev/null +++ b/src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.stories.tsx @@ -0,0 +1,84 @@ +// Import necessary dependencies +import { Meta, StoryObj } from '@storybook/react'; + +// Import the component to be tested +import FancyMenuItem from './FancyMenuItem'; + +// Define metadata for the story +const meta = { + component: FancyMenuItem, + parameters: { + docs: { + description: { + component: 'Template: The FancyMenueItem is a template for a finished Item it used by the FancyMenuList', + }, + }, + }, + // Define arguments for the story + argTypes: { + label: { + description: 'The label of the item', + control: { + type: 'text', + }, + defaultValue: { + summary: 'Logout', + }, + }, + as: { + description: 'The as of the item', + control: { + type: 'text', + }, + defaultValue: { + summary: 'button', + }, + }, + icon: { + description: 'The icon of the item', + control: { + type: 'text', + }, + defaultValue: { + summary: '', + }, + }, + themeType: { + description: 'The theme of the input', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the button hover effect', + control: { + type: 'range', + min: 1, + max: 10, + step: 1, + }, + defaultValue: { + summary: '3', + }, + }, + }, + // Add tags to the story + tags: ['autodocs'], +} satisfies Meta; + +// Export the metadata +export default meta; +// Define the story object +type Story = StoryObj; + +// Define the primary story +export const Primary: Story = { + render: (args) => , + args: { + icon: 'πŸ‘‹', + label: 'Logout', + }, +}; diff --git a/src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.tsx b/src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.tsx new file mode 100644 index 000000000..591fb0e4e --- /dev/null +++ b/src/components/templates/FancyMenueComponent/FancyMenuItem/FancyMenuItem.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import MenuItem from '../../../atoms/MenuItem/MenuItem'; +import FancyContent from '../../../molecules/FancyContent/FancyContent'; + +type TFancyMenueItemProps = { + label?: string; + icon?: React.ReactNode; +} & React.ComponentProps; +// --------------------------------------------------------------------------- // +// ---------- The FancyMenueItem is a template for a finished Item ---------- // +// --------------------------------------------------------------------------- // +export default function FancyMenuItem(props: TFancyMenueItemProps) { + const { label, icon, ...menuItemProps } = props; + return ( + + + {label} + {icon} + + + ); +} diff --git a/src/components/templates/FancyMenueComponent/FancyMenuItem/index.ts b/src/components/templates/FancyMenueComponent/FancyMenuItem/index.ts new file mode 100644 index 000000000..7ba75fab1 --- /dev/null +++ b/src/components/templates/FancyMenueComponent/FancyMenuItem/index.ts @@ -0,0 +1 @@ +export { default as FancyMenuItem } from './FancyMenuItem'; diff --git a/src/components/templates/FancyRadioList/FancyRadioList.model.ts b/src/components/templates/FancyRadioList/FancyRadioList.model.ts new file mode 100644 index 000000000..d9a29edfe --- /dev/null +++ b/src/components/templates/FancyRadioList/FancyRadioList.model.ts @@ -0,0 +1,15 @@ +import { FancyListBox } from '@/components/molecules/FancyListBox'; +import { Fieldset } from '@/components/molecules/Fieldset'; + +export interface FancyRadioListItem { + title: string; + description?: string; + itemKey: string; +} + +export type FancyRadioListProps = { + items: FancyRadioListItem[]; + name: string; + handler?: (itemKey: string) => void; +} & Omit, 'children'> & + Omit, 'children'>; diff --git a/src/components/templates/FancyRadioList/FancyRadioList.stories.tsx b/src/components/templates/FancyRadioList/FancyRadioList.stories.tsx new file mode 100644 index 000000000..b5a97c9e2 --- /dev/null +++ b/src/components/templates/FancyRadioList/FancyRadioList.stories.tsx @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import FancyRadioList from './FancyRadioList'; + +const meta = { + component: FancyRadioList, + parameters: { + docs: { + description: { + component: 'Dumb-Comonent: The FancyRadioList Renders a Styled Box with a list of RadioButtons and a label.', + }, + }, + }, + argTypes: { + themeType: { + description: 'The theme type of the component', + control: { + type: 'select', + }, + defaultValue: { + summary: 'primary', + }, + }, + layer: { + description: 'The layer of the component', + control: { + type: 'range', + min: 0, + max: 10, + step: 1, + }, + defaultValue: { + summary: 1, + }, + }, + outlined: { + description: 'The component has a outline style', + control: { + type: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + outlinedBackgroundStrength: { + description: 'The background strength of the outline', + control: { + type: 'range', + min: 0, + max: 1, + step: 0.01, + }, + defaultValue: { + summary: 1, + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: (args) => , + args: { + label: 'FancyRadioList', + alignLabel: 'left', + fontVariantLegend: 'h3', + disabled: false, + themeType: 'primary', + layer: 1, + outlined: false, + outlinedBackgroundStrength: 1, + + items: [ + { + title: 'Item 1', + itemKey: '1', + }, + { + title: 'Item 2', + description: 'Description 2', + itemKey: '2', + }, + { + title: 'Item 3', + description: 'Description 3', + itemKey: '3', + }, + ], + name: 'radio', + }, +}; diff --git a/src/components/templates/FancyRadioList/FancyRadioList.style.tsx b/src/components/templates/FancyRadioList/FancyRadioList.style.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/templates/FancyRadioList/FancyRadioList.tsx b/src/components/templates/FancyRadioList/FancyRadioList.tsx new file mode 100644 index 000000000..85ef510fc --- /dev/null +++ b/src/components/templates/FancyRadioList/FancyRadioList.tsx @@ -0,0 +1,76 @@ +import React, { useRef, useState } from 'react'; + +import { FancyRadio } from '@/components/organisms/FancyRadio'; +import { FancyListBox } from '@/components/molecules/FancyListBox'; +import { FancyLine } from '@/components/atoms/FancyLine'; +import { Fieldset } from '@/components/molecules/Fieldset'; +import { clampLayer } from '@/components/utils/functions/clampLayer'; +import { FancyRadioListProps } from './FancyRadioList.model'; + +// --------------------------------------------------------------------------- // +// ------- The FancyRadioList renders a List of RadioButtons dynamicly ------- // +// --------------------------------------------------------------------------- // +export default function FancyRadioList(props: FancyRadioListProps) { + const { items, name, handler, themeType, layer, ...fieldSetProps } = props; + const [currentItem, setCurrentSelect] = useState('1'); + const buttonRefs = useRef[]>(items.map(() => React.createRef())); + + // Define the function to handle the selection of a tab + const radioChangeHandler = (position: string) => { + const currentItem = items.find((item) => item.itemKey === position); + setCurrentSelect(position); + handler && handler(currentItem?.itemKey!); + }; + + // This handles the navigation with the keyboard arrows + const handleKeyDown = (event: React.KeyboardEvent, itemKey: string) => { + const currentIndex = items.findIndex((item) => item.itemKey === itemKey); + + let newIndex = -1; + // Check if the user pressed the arrow keys + if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { + newIndex = (currentIndex + 1) % items.length; + } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { + newIndex = (currentIndex - 1 + items.length) % items.length; + } + + // Set the focus on the new item + if (newIndex !== -1) { + const newPosition = items[newIndex].itemKey; + radioChangeHandler(newPosition); + buttonRefs.current[newIndex].current?.focus(); + } + }; + + return ( + // Give the list with the Fieldset a label +
+ {/* The FancyListbox gives the style */} + + {items.map((item, index) => ( + + {/* Render the Radio with a Label and Description */} + + radioChangeHandler(item.itemKey)} + onKeyDown={(e) => handleKeyDown(e, item.itemKey)} + externalStyle={{ width: '100%' }} + aria-checked={item.itemKey === currentItem} + tabIndex={item.itemKey === currentItem ? 0 : -1} + /> + + {/* Render a line between the items */} + {items.length - 1 !== index && } + + ))} + +
+ ); +} diff --git a/src/components/templates/FancyRadioList/index.ts b/src/components/templates/FancyRadioList/index.ts new file mode 100644 index 000000000..e02bdc1fd --- /dev/null +++ b/src/components/templates/FancyRadioList/index.ts @@ -0,0 +1 @@ +export { default as FancyRadioList } from './FancyRadioList'; diff --git a/src/components/templates/Inputs/FancyDateDropDown/FancyDateDropDown.tsx b/src/components/templates/Inputs/FancyDateDropDown/FancyDateDropDown.tsx new file mode 100644 index 000000000..b0ab3a8b8 --- /dev/null +++ b/src/components/templates/Inputs/FancyDateDropDown/FancyDateDropDown.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import FancyDropDownSelect, { IFancyDropDownSelect } from '../../../organisms/FancyDropDownSelect/FancyDropDownSelect'; +import generateYearZone from '../../../utils/functions/generateFunctions/generateYearZone'; +import generateMonthNames from '../../../utils/functions/generateFunctions/generateMonthNames'; +import generateDayNumbers from '../../../utils/functions/generateFunctions/generateDayNumbers'; + +// this function creates the options for the dropdown day month and year +const generateOptions = (type: DateType, dateLimits?: [number, number]) => { + const limitStart = dateLimits ? dateLimits[0] : undefined; + const limitEnd = dateLimits ? dateLimits[1] : undefined; + + //default limits for year + const currentYear = new Date().getFullYear(); + + // add the options to the dropdown with the given limits + switch (type) { + case 'year': { + // create limits for the year with default values if not given + const startYear = limitStart ? limitStart : currentYear - 120; + const endYear = limitEnd ? limitEnd : new Date().getFullYear(); + + return generateYearZone(startYear, endYear); + } + case 'month': { + // create limits for the month with default values if not given + const startMonth = limitStart ? limitStart : 0; + const endMonth = limitEnd ? limitEnd : 11; + + return generateMonthNames(startMonth, endMonth); + } + case 'day': { + // create limits for the day with default values if not given + const startDay = limitStart ? limitStart : 1; + const endDay = limitEnd ? limitEnd : 31; + + return generateDayNumbers(startDay, endDay); + } + } +}; + +type DateType = 'year' | 'month' | 'day'; +interface IFancyDateDropDown extends IFancyDropDownSelect { + type: DateType; + dateLimits?: [number, number]; +} +// --------------------------------------------------------------------------- // +// -------------- Fancy Date DropDown to Pick a day/month/year --------------- // +// --------------------------------------------------------------------------- // +export default function FancyDateDropDown(props: IFancyDateDropDown) { + const { type = 'year', dateLimits, ...inputProps } = props; + + const dateMonthOrYearNumbers = generateOptions(type, dateLimits); + + return ; +} diff --git a/src/components/templates/Inputs/FancyDateDropDown/index.ts b/src/components/templates/Inputs/FancyDateDropDown/index.ts new file mode 100644 index 000000000..1d57d15d9 --- /dev/null +++ b/src/components/templates/Inputs/FancyDateDropDown/index.ts @@ -0,0 +1 @@ +export { default as FancyDateDropDown } from './FancyDateDropDown'; diff --git a/src/components/utils/hooks/useIntersectionObserver/index.ts b/src/components/utils/hooks/useIntersectionObserver/index.ts new file mode 100644 index 000000000..a2c58d511 --- /dev/null +++ b/src/components/utils/hooks/useIntersectionObserver/index.ts @@ -0,0 +1 @@ +export { default as useIntersectionObserver } from './useIntersectionObserver'; diff --git a/src/components/utils/hooks/useIntersectionObserver/useIntersectionObserver.tsx b/src/components/utils/hooks/useIntersectionObserver/useIntersectionObserver.tsx new file mode 100644 index 000000000..e3793c804 --- /dev/null +++ b/src/components/utils/hooks/useIntersectionObserver/useIntersectionObserver.tsx @@ -0,0 +1,33 @@ +import { useState, useEffect, useRef, MutableRefObject } from 'react'; + +const useIntersectionObserver = (): [MutableRefObject, boolean] => { + const ref = useRef(null); + const [isInView, setIsInView] = useState(false); + const observer = useRef(null); + + useEffect(() => { + if (observer.current) { + observer.current.disconnect(); + } + + observer.current = new IntersectionObserver( + ([entry]) => setIsInView(entry.isIntersecting) + //{ threshold: 1 } + ); + + if (ref.current) { + observer.current.observe(ref.current); + } + + return () => { + if (observer.current) { + observer.current.disconnect(); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ref.current]); + + return [ref, isInView]; +}; + +export default useIntersectionObserver; diff --git a/src/components/utils/hooks/useSlider/IUseSlider.model.ts b/src/components/utils/hooks/useSlider/IUseSlider.model.ts new file mode 100644 index 000000000..b4a2e99b9 --- /dev/null +++ b/src/components/utils/hooks/useSlider/IUseSlider.model.ts @@ -0,0 +1,29 @@ +import Color from 'color'; + +// --------------------------------------------------------------------------- // +// ---------------- Define the interfaces for the useSlider Hook ------------- // +// --------------------------------------------------------------------------- // +export interface IUseSlider { + color?: Color | null; + hue?: number; + opacity?: number; + type: 'hue' | 'opacity' | 'color'; + handlerSlider?: (value: number) => void; + handlerColor?: (color: Color) => void; + sliderPositionToColorFunc?: (clientX: number, rect: DOMRect) => number; + positionToColorFunc?: (hue: number, clientX: number, clientY: number, rect: DOMRect) => { h: number; s: number; v: number } | number; + colorToPositionFunc: (color: Color, rect: DOMRect) => { x: number; y: number }; +} + +export interface IMarkerPosition { + x: number; + y: number; +} + +export interface IUseSliderReturn { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sliderRef: React.MutableRefObject; + markerPosition: IMarkerPosition; + handleInteractionStart: (event: React.MouseEvent | React.TouchEvent) => void; + isInteracting: boolean; +} diff --git a/src/components/utils/hooks/useSlider/useSilder.tsx b/src/components/utils/hooks/useSlider/useSilder.tsx new file mode 100644 index 000000000..56ed07471 --- /dev/null +++ b/src/components/utils/hooks/useSlider/useSilder.tsx @@ -0,0 +1,173 @@ +import { useState, useRef, useEffect, useCallback } from 'react'; +import Color from 'color'; + +import { IMarkerPosition, IUseSlider, IUseSliderReturn } from './IUseSlider.model'; + +// Throttle function to prevent too many rerenders +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const throttle = (func: (...args: any[]) => void): ((...args: any[]) => void) => { + let isThrottled = false; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (...args: any[]) => { + if (!isThrottled) { + isThrottled = true; + requestAnimationFrame(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + func(...args); + isThrottled = false; + }); + } + }; +}; + +// --------------------------------------------------------------------------- // +// ------------------ Define the main useSlider Hoook function --------------- // +// --------------------------------------------------------------------------- // +const useSlider = ({ + color, + hue, + type, + sliderPositionToColorFunc, + positionToColorFunc, + colorToPositionFunc, + handlerSlider, + handlerColor, +}: IUseSlider): IUseSliderReturn => { + const [markerPosition, setMarkerPosition] = useState({ x: 0, y: 0 }); + const [isInteracting, setIsInteracting] = useState(false); + const sliderRef = useRef(); + + //updates the marker position when the color changes + const updateMarkerPosition = useCallback( + (color: Color) => { + if (!sliderRef.current) return; + const rect = sliderRef.current.getBoundingClientRect(); + const newPosition = colorToPositionFunc(color, rect); + setMarkerPosition(newPosition); + }, + [colorToPositionFunc] + ); + + //hanlde the interaction with the slider and the color area + const handleInteraction = useCallback( + (clientX: number, clientY: number) => { + if (!sliderRef.current) return; + const rect = sliderRef.current.getBoundingClientRect(); + if (type === 'hue') { + const newColor = sliderPositionToColorFunc && sliderPositionToColorFunc(clientX, rect); + handlerSlider && handlerSlider(Math.floor(newColor!) ?? 0); + const createColor = Color({ h: newColor, s: 100, l: 50 }); + updateMarkerPosition(createColor); + } else if (type === 'opacity') { + const newColor = sliderPositionToColorFunc && sliderPositionToColorFunc(clientX, rect); + const alpha = Math.max(0, Math.min(newColor as number, 1)); + handlerSlider && handlerSlider(alpha ?? 1); + const createColor = Color({ r: 255, g: 255, b: 255 }).alpha(alpha); + updateMarkerPosition(createColor); + } else { + const newColor = positionToColorFunc && positionToColorFunc(hue ?? 0, clientX, clientY, rect); + const createColor = Color(newColor); + handlerColor && handlerColor(createColor); + updateMarkerPosition(createColor); + } + }, + [updateMarkerPosition, positionToColorFunc, type, hue, sliderPositionToColorFunc, handlerSlider, handlerColor] + ); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const throttledHandleInteraction = useCallback(throttle(handleInteraction), [handleInteraction]); + + //handle the interaction start with the slider and the color area + const handleInteractionStart = (event: React.MouseEvent | React.TouchEvent) => { + setIsInteracting(true); + if (event.nativeEvent instanceof MouseEvent) { + throttledHandleInteraction(event.nativeEvent.clientX, event.nativeEvent.clientY); + } else if (event.nativeEvent instanceof TouchEvent) { + throttledHandleInteraction(event.nativeEvent.touches[0].clientX, event.nativeEvent.touches[0].clientY); + } + }; + + // this useEffect is used to prevent the body from scrolling when the user is interacting with the slider + useEffect(() => { + if (isInteracting) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'auto'; + } + return () => { + document.body.style.overflow = 'auto'; + }; + }, [isInteracting]); + + //handle the interaction with the slider and the color area + const handleInteractionMove = useCallback( + (event: MouseEvent | TouchEvent) => { + if (!isInteracting) return; + + if (event instanceof MouseEvent) { + event.preventDefault(); + throttledHandleInteraction(event.clientX, event.clientY); + } else if (event instanceof TouchEvent) { + if (event.cancelable) event.preventDefault(); + throttledHandleInteraction(event.touches[0].clientX, event.touches[0].clientY); + } + }, + [isInteracting, throttledHandleInteraction] + ); + + //handle the interaction end with the slider and the color area + const handleInteractionEnd = useCallback(() => { + setIsInteracting(false); + }, []); + + //update the marker position when the color changes + useEffect(() => { + if (!color) return; + updateMarkerPosition(color); + }, [color, updateMarkerPosition]); + + //update the marker position when the hue changes + useEffect(() => { + if (!hue) return; + if (type === 'color' && color) { + updateMarkerPosition(color.hue(hue)); + } else { + updateMarkerPosition(Color({ h: hue, s: 50, l: 50 })); + } + }, [hue, updateMarkerPosition, color, type]); + + //set the initial position of the marker + useEffect(() => { + if (!sliderRef.current) return; + // eslint-disable-next-line react-hooks/exhaustive-deps + if (!color) type === 'hue' ? (color = Color({ h: hue, s: 100, l: 50 })) : (color = Color({ r: 255, g: 255, b: 255 }).alpha(1)); + + const rect = sliderRef.current.getBoundingClientRect(); + const initialPosition = colorToPositionFunc(color, rect); + setMarkerPosition(initialPosition); + }, []); + + //the mouse event are listen to the window because the mouse can be outside the slider or area + useEffect(() => { + const handleInteractionMoveFunc = (event: MouseEvent | TouchEvent) => handleInteractionMove(event); + const handleInteractionEndFunc = () => handleInteractionEnd(); + if (isInteracting) { + window.addEventListener('mousemove', handleInteractionMoveFunc); + window.addEventListener('touchmove', handleInteractionMoveFunc, { passive: false }); + window.addEventListener('mouseup', handleInteractionEndFunc); + window.addEventListener('touchend', handleInteractionEndFunc, { passive: false }); + } + + return () => { + window.removeEventListener('mousemove', handleInteractionMoveFunc); + window.removeEventListener('touchmove', handleInteractionMoveFunc); + window.removeEventListener('mouseup', handleInteractionEndFunc); + window.removeEventListener('touchend', handleInteractionEndFunc); + }; + }, [handleInteractionMove, handleInteractionEnd, isInteracting]); + + return { sliderRef, markerPosition, handleInteractionStart, isInteracting }; +}; + +export default useSlider; diff --git a/src/components/utils/hooks/useWindowDimensions/index.ts b/src/components/utils/hooks/useWindowDimensions/index.ts new file mode 100644 index 000000000..891054d2a --- /dev/null +++ b/src/components/utils/hooks/useWindowDimensions/index.ts @@ -0,0 +1 @@ +export { default as useWindowDimensions } from './useWindowDimensions'; diff --git a/src/components/utils/hooks/useWindowDimensions/useWindowDimensions.tsx b/src/components/utils/hooks/useWindowDimensions/useWindowDimensions.tsx new file mode 100644 index 000000000..581406b40 --- /dev/null +++ b/src/components/utils/hooks/useWindowDimensions/useWindowDimensions.tsx @@ -0,0 +1,27 @@ +import { useState, useEffect } from 'react'; + +// This function gets the current window dimensions +function getWindowDimensions() { + const { innerWidth: width, innerHeight: height } = window; + return { + width, + height, + }; +} + +// This custom hook returns the current window dimensions and updates them on window resize +export default function useWindowDimensions() { + const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); + + // This useEffect hook adds a window resize event listener and removes it on cleanup + useEffect(() => { + function handleResize() { + setWindowDimensions(getWindowDimensions()); + } + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return windowDimensions; +} diff --git a/src/components/utils/variables/colorFormats.ts b/src/components/utils/variables/colorFormats.ts new file mode 100644 index 000000000..d74627498 --- /dev/null +++ b/src/components/utils/variables/colorFormats.ts @@ -0,0 +1,6 @@ +export type IColorFormat = 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hex' | 'hexa'; +type IColorFormatsArray = IColorFormat[]; + +const colorFormats: IColorFormatsArray = ['rgb', 'rgba', 'hsl', 'hsla', 'hex', 'hexa']; + +export default colorFormats;