diff --git a/packages/components/src/CardButton/CardButton.stories.tsx b/packages/components/src/CardButton/CardButton.stories.tsx new file mode 100644 index 000000000..6aa0d0315 --- /dev/null +++ b/packages/components/src/CardButton/CardButton.stories.tsx @@ -0,0 +1,16 @@ +import { CardButton } from './CardButton' + +import type { Meta, StoryFn } from '@storybook/react' + +export default { + title: 'Components/CardButton', + component: CardButton, +} as Meta + +export const Basic: StoryFn = () => ( +
+ +
Basic Implementation
+
+
+) diff --git a/packages/components/src/CardButton/CardButton.tsx b/packages/components/src/CardButton/CardButton.tsx new file mode 100644 index 000000000..0711977ca --- /dev/null +++ b/packages/components/src/CardButton/CardButton.tsx @@ -0,0 +1,43 @@ +import { Card } from 'theme-ui' + +import type { BoxProps, ThemeUIStyleObject } from 'theme-ui' + +export interface IProps extends BoxProps { + children: React.ReactNode + extraSx?: ThemeUIStyleObject | undefined +} + +export const CardButton = (props: IProps) => { + const { children, extraSx } = props + + return ( + + {children} + + ) +} diff --git a/packages/components/src/CardList/CardList.tsx b/packages/components/src/CardList/CardList.tsx index e0ca5495c..0b1bb24b7 100644 --- a/packages/components/src/CardList/CardList.tsx +++ b/packages/components/src/CardList/CardList.tsx @@ -30,9 +30,7 @@ export const CardList = (props: IProps) => { const isListEmpty = displayItems.length === 0 const hasListLoaded = list - const results = `${displayItems.length} ${ - displayItems.length == 1 ? 'result' : 'results' - }` + const results = `${displayItems.length} result${displayItems.length == 1 ? '' : 's'} in view` return ( { sx={{ flexDirection: 'column', gap: 2, + padding: 2, }} > {!hasListLoaded && } @@ -49,6 +48,7 @@ export const CardList = (props: IProps) => { sx={{ justifyContent: 'space-between', paddingX: 2, + paddingTop: 2, fontSize: 2, }} > diff --git a/packages/components/src/CardListItem/CardListItem.tsx b/packages/components/src/CardListItem/CardListItem.tsx index df3eefddd..d46955183 100644 --- a/packages/components/src/CardListItem/CardListItem.tsx +++ b/packages/components/src/CardListItem/CardListItem.tsx @@ -1,5 +1,6 @@ -import { Card, Flex } from 'theme-ui' +import { Flex } from 'theme-ui' +import { CardButton } from '../CardButton/CardButton' import { InternalLink } from '../InternalLink/InternalLink' import { CardDetailsFallback } from './CardDetailsFallback' import { CardDetailsMemberProfile } from './CardDetailsMemberProfile' @@ -26,30 +27,7 @@ export const CardListItem = ({ item }: IProps) => { padding: 2, }} > - + {isMember && } @@ -59,7 +37,7 @@ export const CardListItem = ({ item }: IProps) => { {!creator && } - + ) } diff --git a/packages/components/src/DonationRequest/DonationRequest.tsx b/packages/components/src/DonationRequest/DonationRequest.tsx index 6663ee9cd..206f609ac 100644 --- a/packages/components/src/DonationRequest/DonationRequest.tsx +++ b/packages/components/src/DonationRequest/DonationRequest.tsx @@ -86,7 +86,7 @@ export const DonationRequest = (props: IProps) => { + +const availableFilters = [ + { + label: 'Workspace', + type: 'workspace' as ProfileTypeName, + }, + { + label: 'Machine Builder', + type: 'machine-builder' as ProfileTypeName, + }, + { + label: 'Collection Point', + type: 'collection-point' as ProfileTypeName, + }, + { + label: 'Want to get started', + type: 'member' as ProfileTypeName, + }, +] + +export const Basic: StoryFn = () => { + const [activeFilters, setActiveFilters] = useState([]) + + const onFilterChange = (label: string) => { + const filter = label.toLowerCase() + const isFilterPresent = !!activeFilters.find( + (existing) => existing === filter, + ) + if (isFilterPresent) { + return setActiveFilters(activeFilters.filter((f) => f !== filter)) + } + return setActiveFilters((existing) => [...existing, filter]) + } + + return ( +
+ +
+ ) +} + +export const OnlyOne: StoryFn = () => { + const [activeFilters, setActiveFilters] = useState([]) + + const onFilterChange = (label: string) => { + const filter = label.toLowerCase() + const isFilterPresent = !!activeFilters.find( + (existing) => existing === filter, + ) + if (isFilterPresent) { + return setActiveFilters(activeFilters.filter((f) => f !== filter)) + } + return setActiveFilters((existing) => [...existing, filter]) + } + + return ( +
+ + (Shouldn't see anything, only renders for two or more) +
+ ) +} + +export const OnlyTwo: StoryFn = () => { + const [activeFilters, setActiveFilters] = useState([]) + + const onFilterChange = (label: string) => { + const filter = label.toLowerCase() + const isFilterPresent = !!activeFilters.find( + (existing) => existing === filter, + ) + if (isFilterPresent) { + return setActiveFilters(activeFilters.filter((f) => f !== filter)) + } + return setActiveFilters((existing) => [...existing, filter]) + } + + return ( +
+ + (No buttons rendered) +
+ ) +} diff --git a/packages/components/src/FilterList/FilterList.tsx b/packages/components/src/FilterList/FilterList.tsx new file mode 100644 index 000000000..871b83773 --- /dev/null +++ b/packages/components/src/FilterList/FilterList.tsx @@ -0,0 +1,157 @@ +import { useEffect, useRef, useState } from 'react' +import { Flex, Text } from 'theme-ui' + +import { Button } from '../Button/Button' +import { CardButton } from '../CardButton/CardButton' +import { MemberBadge } from '../MemberBadge/MemberBadge' + +import type { ProfileTypeName } from 'oa-shared' + +type FilterOption = { + label: string + type: ProfileTypeName +} + +export interface IProps { + activeFilters: string[] + availableFilters: FilterOption[] + onFilterChange: (label: string) => void +} + +export const FilterList = (props: IProps) => { + const elementRef = useRef(null) + const [disableLeftArrow, setDisableLeftArrow] = useState(true) + const [disableRightArrow, setDisableRightArrow] = useState(false) + + const { activeFilters, availableFilters, onFilterChange } = props + + if (!availableFilters || availableFilters.length < 2) { + return null + } + + const handleHorizantalScroll = (step: number) => { + const distance = 121 + const element = elementRef.current + const speed = 10 + let scrollAmount = 0 + + const slideTimer = setInterval(() => { + if (element) { + element.scrollLeft += step + scrollAmount += Math.abs(step) + if (scrollAmount >= distance) { + clearInterval(slideTimer) + } + const { scrollLeft, scrollWidth, clientWidth } = element + switch (scrollLeft + clientWidth) { + case clientWidth: + setDisableLeftArrow(true) + // scrollWidth && setDisableRightArrow(true) + break + case scrollWidth: + setDisableRightArrow(true) + break + default: + setDisableLeftArrow(false) + setDisableRightArrow(false) + break + } + } + }, speed) + } + + useEffect(() => { + handleHorizantalScroll(0) + }, []) + + return ( + + + {availableFilters.map(({ label, type }, index) => { + const active = activeFilters.find((filter) => filter === type) + return ( + onFilterChange(type)} + extraSx={{ + backgroundColor: 'offWhite', + padding: 1, + textAlign: 'center', + width: '130px', + minWidth: '130px', + height: '75px', + flexDirection: 'column', + ...(active + ? { + borderColor: 'green', + ':hover': { borderColor: 'green' }, + } + : { + borderColor: 'offWhite', + ':hover': { borderColor: 'offWhite' }, + }), + }} + > + +
+ + {label} + +
+ ) + })} +
+ {availableFilters.length > 3 && ( + + - - Welcome to our world!{' '} - {pins && `${pins.length} members (and counting...)`} - - diff --git a/src/pages/Maps/Content/MapView/MapWithListHeader.tsx b/src/pages/Maps/Content/MapView/MapWithListHeader.tsx new file mode 100644 index 000000000..d6204ab78 --- /dev/null +++ b/src/pages/Maps/Content/MapView/MapWithListHeader.tsx @@ -0,0 +1,62 @@ +import { CardList, FilterList } from 'oa-components' +import { Flex, Heading } from 'theme-ui' + +import type { IMapPin } from 'src/models' + +interface IProps { + activePinFilters: string[] + availableFilters: any + filteredPins: IMapPin[] | null + onFilterChange: (label: string) => void + pins: IMapPin[] + viewport: 'desktop' | 'mobile' +} + +export const MapWithListHeader = (props: IProps) => { + const { + activePinFilters, + availableFilters, + filteredPins, + onFilterChange, + pins, + viewport, + } = props + const isMobile = viewport === 'mobile' + + return ( + <> + + + Welcome to our world!{' '} + {pins && `${pins.length} members (and counting...)`} + + + + + + + ) +} diff --git a/src/pages/Maps/Maps.tsx b/src/pages/Maps/Maps.tsx index 9f8e9746b..ff2232922 100644 --- a/src/pages/Maps/Maps.tsx +++ b/src/pages/Maps/Maps.tsx @@ -153,7 +153,7 @@ const MapsPage = observer(() => { return ( // the calculation for the height is kind of hacky for now, will set properly on final mockups - + {!showNewMap && ( <> diff --git a/src/pages/UserSettings/content/fields/PatreonIntegration.tsx b/src/pages/UserSettings/content/fields/PatreonIntegration.tsx index f00bc5217..40087d485 100644 --- a/src/pages/UserSettings/content/fields/PatreonIntegration.tsx +++ b/src/pages/UserSettings/content/fields/PatreonIntegration.tsx @@ -43,7 +43,7 @@ export const PatreonIntegration = ({ user }) => {