diff --git a/packages/frontend/src/assets/icons/checkmark.svg b/packages/frontend/src/assets/icons/checkmark.svg new file mode 100644 index 00000000..ad41aad3 --- /dev/null +++ b/packages/frontend/src/assets/icons/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/frontend/src/components/mobile-filters/MobileFiltersModule.tsx b/packages/frontend/src/components/mobile-filters/MobileFiltersModule.tsx new file mode 100644 index 00000000..62a0aa2a --- /dev/null +++ b/packages/frontend/src/components/mobile-filters/MobileFiltersModule.tsx @@ -0,0 +1,509 @@ +import { FC, ReactNode, useMemo, useState } from "react"; +import { + Disclosure, + FullPageModal, + Input, + Toggle, + themeColors, + twMerge, +} from "@alphaday/ui-kit"; +import { ReactComponent as CheckedSVG } from "src/assets/icons/checkmark.svg"; +import { ReactComponent as ChevronSVG } from "src/assets/icons/chevron-down2.svg"; + +enum EOptionCategory { + MEDIA = "mediaOptions", + COINS = "coinsOptions", + DATE = "dateOptions", +} + +type TOption = { + id: number; + name: string; + selected: boolean; + color: string; +}; + +type TDateOption = { + id: number; + name: string; + selected: boolean; + value: number | undefined; +}; + +const sortOptions = (mediaOptions: TOption[]): TOption[] => { + return mediaOptions.sort((a, b) => { + // If a is selected and b is not, a should come first + if (a.selected && !b.selected) { + return -1; + } + // If b is selected and a is not, b should come first + if (b.selected && !a.selected) { + return 1; + } + // If both have the same selected status, maintain their order + return 0; + }); +}; + +const ALL_OPTIONS = { + mediaOptions: [ + { id: 1, name: "News", selected: true, color: themeColors.categoryOne }, + { + id: 2, + name: "Videos", + selected: false, + color: themeColors.categoryTwo, + }, + { + id: 3, + name: "Podcasts", + selected: true, + color: themeColors.categoryThree, + }, + { + id: 4, + name: "Images", + selected: false, + color: themeColors.categoryFour, + }, + { + id: 5, + name: "Events", + selected: false, + color: themeColors.categoryFive, + }, + { + id: 6, + name: "Price action", + selected: false, + color: themeColors.categorySix, + }, + { + id: 7, + name: "Social Posts", + selected: false, + color: themeColors.categorySeven, + }, + { + id: 8, + name: "Forums", + selected: false, + color: themeColors.categoryEight, + }, + { + id: 9, + name: "TVL", + selected: false, + color: themeColors.categoryNine, + }, + { + id: 10, + name: "Blogs", + selected: false, + color: themeColors.categoryTen, + }, + { + id: 11, + name: "Perons", + selected: false, + color: themeColors.categoryEleven, + }, + { + id: 12, + name: "Memes", + selected: false, + color: themeColors.categoryTwelve, + }, + ], + coinsOptions: [ + { + id: 1, + name: "BTC", + selected: true, + color: themeColors.accentVariant100, + }, + { + id: 2, + name: "ETH", + selected: false, + color: themeColors.accentVariant100, + }, + { + id: 3, + name: "SOL", + selected: true, + color: themeColors.accentVariant100, + }, + { + id: 4, + name: "DOT", + selected: false, + color: themeColors.accentVariant100, + }, + { + id: 5, + name: "MANTLE", + selected: false, + color: themeColors.accentVariant100, + }, + { + id: 6, + name: "WAX", + selected: false, + color: themeColors.accentVariant100, + }, + { + id: 7, + name: "AVAX", + selected: false, + color: themeColors.accentVariant100, + }, + { + id: 8, + name: "OP", + selected: false, + color: themeColors.accentVariant100, + }, + { + id: 9, + name: "LINK", + selected: false, + color: themeColors.accentVariant100, + }, + ], + dateOptions: [ + { id: 1, name: "Anytime", value: undefined, selected: false }, + { id: 2, name: "Last 24 hours", value: 24, selected: true }, + { id: 3, name: "Last 7 days", value: 168, selected: false }, + { id: 4, name: "Last 30 days", value: 720, selected: false }, + { id: 5, name: "last 90 days", value: 2160, selected: false }, + { id: 6, name: "last 6 months", value: 4320, selected: false }, + ], +}; + +const TagButton: FC<{ + name: string; + selected: boolean; + bgColor: string; + onClick: () => void; +}> = ({ name, onClick, selected, bgColor }) => { + const handleClick = ( + e: React.MouseEvent + ) => { + e.preventDefault(); + e.stopPropagation(); + onClick(); + }; + return ( + + ); +}; + +export const OptionsDisclosure: FC<{ + title: string; + options: TOption[] | TDateOption[]; + onSelect: (id: number) => void; + children: ReactNode; + pillType?: boolean; + subtext?: string; +}> = ({ options, onSelect, children, title, pillType, subtext }) => { + const pillsToShow = pillType + ? options.slice(0, 6) + : [ + // @ts-ignore - typescript doesn't like the fact that we're using a union type here + options.find( + (option: TOption | TDateOption) => option.selected + ) || options[0], + ]; + return ( + + {({ open }) => ( + <> + +
+

+ {title} +

+ + +
+

+ {subtext} +

+ + {!open && ( +
+ {pillsToShow.map((option) => ( + onSelect(option.id)} + /> + ))} +
+ )} +
+ + {children} + + + )} +
+ ); +}; + +const MobileFiltersModule: FC<{ + toggleFeedFilters: () => void; + show: boolean; +}> = ({ toggleFeedFilters, show }) => { + const [isOpen, setIsOpen] = useState(true); + const [enabled, setEnabled] = useState(true); + + const [allOptions, setAllOptions] = useState({ + ...ALL_OPTIONS, + coinsOptions: sortOptions(ALL_OPTIONS.coinsOptions), + mediaOptions: sortOptions(ALL_OPTIONS.mediaOptions), + }); + + const handlePillSelect = (id: number, optionCategory: EOptionCategory) => { + setAllOptions((prev) => { + if ( + optionCategory === EOptionCategory.MEDIA || + optionCategory === EOptionCategory.COINS + ) { + const selectedOptions: TOption[] = []; + const nonSelectedOptions: TOption[] = []; + let selectedOption: TOption | undefined; + prev[optionCategory].forEach((option) => { + if (option.id === id) { + selectedOption = option; + return; + } + if (option.selected) { + selectedOptions.push(option); + return; + } + nonSelectedOptions.push(option); + }); + if (selectedOption !== undefined) { + return { + ...prev, + [optionCategory]: [ + ...selectedOptions, + { + ...selectedOption, + selected: !selectedOption?.selected, + }, + ...nonSelectedOptions, + ], + }; + } + } + return prev; + }); + }; + + const handleDateSelect = (id: number) => { + setAllOptions((prev) => { + const updatedDateOptions = prev.dateOptions.map((option) => { + if (option.id === id) { + return { + ...option, + selected: true, + }; + } + return { + ...option, + selected: false, + }; + }); + return { + ...prev, + dateOptions: updatedDateOptions, + }; + }); + }; + + const selectedDateOption = useMemo( + () => allOptions.dateOptions.find((option) => option.selected), + [allOptions] + ); + + if (show) { + return ( + setIsOpen(false)}> +
+ +

+ Superfeed filters +

+
+
+

+ Craft your ideal superfeed by customizing the filters + below. +

+ {/* //TODO use search when backend is ready */} +
+ +
+
+
+

+ trending +

+ setEnabled((prev) => !prev)} + /> +
+
+ + handlePillSelect(id, EOptionCategory.MEDIA) + } + pillType + > +
+ {allOptions.mediaOptions.map((option) => ( + + handlePillSelect( + option.id, + EOptionCategory.MEDIA + ) + } + /> + ))} +
+
+
+
+ + handlePillSelect(id, EOptionCategory.COINS) + } + pillType + > +
+ {allOptions.coinsOptions.map((option) => ( + + handlePillSelect( + option.id, + EOptionCategory.COINS + ) + } + /> + ))} +
+
+
+
+ handleDateSelect(id)} + > +
+
+ {selectedDateOption && ( + {}} + /> + )} +
+ + Notification method + +
+ {allOptions.dateOptions.map((option) => ( +
+ handleDateSelect(option.id) + } + > + + +
+ ))} +
+
+
+
+
+ ); + } + return null; +}; + +export default MobileFiltersModule; diff --git a/packages/frontend/src/containers/mobile-filters/MobileFilterContainer.tsx b/packages/frontend/src/containers/mobile-filters/MobileFilterContainer.tsx new file mode 100644 index 00000000..3851c029 --- /dev/null +++ b/packages/frontend/src/containers/mobile-filters/MobileFilterContainer.tsx @@ -0,0 +1,16 @@ +import { FC } from "react"; +import MobileFiltersModule from "src/components/mobile-filters/MobileFiltersModule"; + +const MobileFilterContainer: FC<{ + toggleFeedFilters: () => void; + show: boolean; +}> = ({ toggleFeedFilters, show }) => { + return ( + + ); +}; + +export default MobileFilterContainer; diff --git a/packages/frontend/src/mobile-pages/superfeed.tsx b/packages/frontend/src/mobile-pages/superfeed.tsx index 1a5d79b8..4343a0c3 100644 --- a/packages/frontend/src/mobile-pages/superfeed.tsx +++ b/packages/frontend/src/mobile-pages/superfeed.tsx @@ -1,9 +1,60 @@ +import { FC, useRef, useState } from "react"; +import { twMerge } from "@alphaday/ui-kit"; +import { useOnScreen } from "src/api/hooks"; +import { ReactComponent as SettingsSVG } from "src/assets/icons/settings.svg"; +import { ReactComponent as Settings2SVG } from "src/assets/icons/settings3.svg"; +import MobileFilterContainer from "src/containers/mobile-filters/MobileFilterContainer"; import SuperfeedContainer from "src/containers/superfeed/SuperfeedContainer"; import MobileLayout from "src/layout/MobileLayout"; +const FiltersButton: FC<{ toggleFeedFilters: () => void }> = ({ + toggleFeedFilters, +}) => { + const element1: React.Ref = useRef(null); + const element2: React.Ref = useRef(null); + const element1Visible = useOnScreen(element1); + + return ( + <> +
+

+ Craft your superfeed with personalized filters +

+ +
+
+ +
+ + ); +}; + const SuperfeedPage = () => { + const [showFeedFilters, setshowFeedFilters] = useState(false); + const toggleFeedFilters = () => setshowFeedFilters(!showFeedFilters); return ( + + ); diff --git a/packages/ui-kit/index.ts b/packages/ui-kit/index.ts index c5f75f0f..8918fc7f 100644 --- a/packages/ui-kit/index.ts +++ b/packages/ui-kit/index.ts @@ -1,4 +1,5 @@ import { EventClickArg, DatesSetArg } from "@fullcalendar/core"; +import { Disclosure } from "@headlessui/react"; import { useLayer } from "react-laag"; import { twMerge } from "tailwind-merge"; import { Arrow } from "./src/components/arrow/Arrow"; @@ -62,6 +63,7 @@ import { listItemVariants, } from "./src/components/listItem/ListItem"; import { ErrorModal } from "./src/components/modal/ErrorModal"; +import { FullPageModal } from "./src/components/modal/FullPageModal"; import { Modal } from "./src/components/modal/Modal"; import { ModulePreview } from "./src/components/module-preview/ModulePreview"; import { ModuleLoader } from "./src/components/moduleLoader/ModuleLoader"; @@ -80,6 +82,7 @@ import { KeyValueTable } from "./src/components/table/KeyValueTable"; import { TabsBar } from "./src/components/tabs/TabsBar"; import { TextOverlay } from "./src/components/text-overlay/TextOverlay"; import { Timer } from "./src/components/timer/Timer"; +import { Toggle } from "./src/components/toggle/Toggle"; import { CalendarTooltip } from "./src/components/tooltip/CalendarTooltip"; import { ViewTabMenu, @@ -117,7 +120,9 @@ export { CarouselImage, Lightbox, Timer, + Toggle, ErrorModal, + FullPageModal, Footer, Logo, HeaderWrapper, @@ -172,4 +177,5 @@ export { NavHeader, Pager, FeedCard, + Disclosure, }; diff --git a/packages/ui-kit/src/assets/svg/chevron-down.svg b/packages/ui-kit/src/assets/svg/chevron-down.svg index 1f9c7126..d41fa911 100644 --- a/packages/ui-kit/src/assets/svg/chevron-down.svg +++ b/packages/ui-kit/src/assets/svg/chevron-down.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/ui-kit/src/components/modal/FullPageModal.tsx b/packages/ui-kit/src/components/modal/FullPageModal.tsx new file mode 100644 index 00000000..bab13b9a --- /dev/null +++ b/packages/ui-kit/src/components/modal/FullPageModal.tsx @@ -0,0 +1,48 @@ +import { FC, Fragment, ReactNode } from "react"; +import { Dialog, Transition } from "@headlessui/react"; + +interface IProps { + isOpen: boolean; + closeModal: () => void; + children: ReactNode; +} + +export const FullPageModal: FC = ({ isOpen, closeModal, children }) => { + return ( + + + +
+ + +
+
+ + + {children} + + +
+
+ + + ); +}; + +export default FullPageModal; diff --git a/packages/ui-kit/src/components/toggle/Toggle.tsx b/packages/ui-kit/src/components/toggle/Toggle.tsx new file mode 100644 index 00000000..922cea99 --- /dev/null +++ b/packages/ui-kit/src/components/toggle/Toggle.tsx @@ -0,0 +1,38 @@ +import { FC } from "react"; +import { Switch } from "@headlessui/react"; +import { ReactComponent as CheckedSVG } from "src/assets/svg/checkmark.svg"; +import { twMerge } from "tailwind-merge"; + +export const Toggle: FC<{ enabled: boolean; onChange: () => void }> = ({ + enabled, + onChange, +}) => { + return ( +
+ + Use setting + + +
+ ); +}; diff --git a/packages/ui-kit/src/globalStyles/colors.scss b/packages/ui-kit/src/globalStyles/colors.scss index dbda9115..50de0606 100644 --- a/packages/ui-kit/src/globalStyles/colors.scss +++ b/packages/ui-kit/src/globalStyles/colors.scss @@ -86,6 +86,21 @@ http://ionicframework.com/docs/theming/ */ --alpha-yellow-green: #b9e187; --alpha-steel-pink: #CE4BD9; + /** category colors **/ + --alpha-category-1: #1D7CBF; + --alpha-category-2: #CC4747; + --alpha-category-3: #FF59C8; + --alpha-category-4: #3EB265; + --alpha-category-5: #3EB2B2; + --alpha-category-6: #9B50E5; + --alpha-category-7: #CE4BD9; + --alpha-category-8: #E57339; + --alpha-category-9: #4747CC; + --alpha-category-10: #CC841F; + --alpha-category-11: #D1A234; + --alpha-category-12: #6950E5; + + /* overrides */ --fc-border-color: var(--alpha-border); @@ -117,6 +132,19 @@ http://ionicframework.com/docs/theming/ */ --secondaryOrangeSoda: var(--alpha-red); --secondaryYellowGreen: var(--alpha-yellow-green); --success: var(--alpha-green); + /** category colors **/ + --category1: var(--alpha-category-1); + --category2: var(--alpha-category-2); + --category3: var(--alpha-category-3); + --category4: var(--alpha-category-4); + --category5: var(--alpha-category-5); + --category6: var(--alpha-category-6); + --category7: var(--alpha-category-7); + --category8: var(--alpha-category-8); + --category9: var(--alpha-category-9); + --category10: var(--alpha-category-10); + --category11: var(--alpha-category-11); + --category12: var(--alpha-category-12); } /* diff --git a/packages/ui-kit/src/globalStyles/colors.ts b/packages/ui-kit/src/globalStyles/colors.ts index 1449918b..cc8ec07c 100644 --- a/packages/ui-kit/src/globalStyles/colors.ts +++ b/packages/ui-kit/src/globalStyles/colors.ts @@ -25,4 +25,17 @@ export const darkColors = { secondaryOrangeSoda: "var(--alpha-red)", // #F45532 -- downward market secondaryYellowGreen: "var(--alpha-yellow-green)", // #B9E187 -- upward market success: "var(--alpha-green)", // #6DD230 + // category colors + categoryOne: "var(--alpha-category-1)", + categoryTwo: "var(--alpha-category-2)", + categoryThree: "var(--alpha-category-3)", + categoryFour: "var(--alpha-category-4)", + categoryFive: "var(--alpha-category-5)", + categorySix: "var(--alpha-category-6)", + categorySeven: "var(--alpha-category-7)", + categoryEight: "var(--alpha-category-8)", + categoryNine: "var(--alpha-category-9)", + categoryTen: "var(--alpha-category-10)", + categoryEleven: "var(--alpha-category-11)", + categoryTwelve: "var(--alpha-category-12)", };