diff --git a/cra-rxjs-styled-components/src/components/filter-dropdown/FilterDropdown.styles.ts b/cra-rxjs-styled-components/src/components/filter-dropdown/FilterDropdown.styles.ts new file mode 100644 index 000000000..5bf564c02 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/filter-dropdown/FilterDropdown.styles.ts @@ -0,0 +1,132 @@ +import colors from '../../constants/colors'; +import styled from 'styled-components'; + +export const DropdownContainer = styled.div` + display: flex; + position: relative; + gap: 8px; + height: 35px; + flex-grow: 1; + margin-right: 8px; +`; + +export const DropdownOptionsHeading = styled.div` + display: flex; + padding: 8px; + flex-direction: row; + justify-content: space-between; +`; + +export const DropdownOptionsHeadingText = styled.span` + font-size: 12px; + font-weight: 700; +`; + +export const DropdownBtn = styled.button` + display: flex; + flex-grow: 1; + border-radius: 8px; + align-items: center; + padding: 8px; + justify-content: space-between; + border: 1px solid ${colors.gray300}; + background-color: ${colors.gray100}; + gap: 4px; + min-width: 70px; + outline: none; + @media (min-width: 768px) { + gap: 8px; + min-width: 80px; + } +`; + +export const DropdownOption = styled.li` + display: flex; + gap: 4px; + padding: 8px; + flex-direction: row; + border-top-width: 1px; + background-color: #fff; + border-top-style: solid; + border-top-color: ${colors.gray300}; +`; + +export const DropdownBtnText = styled.span` + font-size: 14px; + font-weight: 500; +`; + +export const DropDownMenu = styled.div` + position: absolute; + right: 0px; + z-index: 10; + margin-top: 0.5rem; + width: 14rem; + transform-origin: top right; + border-radius: 0.375rem; + background-color: #fff; + box-shadow: 0px 0px 10px 1px #ccc; + top: 2rem; +`; + +export const MenuItemContainer = styled.div` + padding: 0.25rem 0; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + transition-delay: 50ms; + cursor: pointer; + border-top: 1px solid ${colors.gray200}; +`; + +export const MenuItem = styled.span` + color: ${colors.gray700}; + padding: 0.5rem 1rem; + font-size: 0.875rem; + line-height: 1.25rem; + display: flex; + align-items: center; + gap: 0.75rem; +`; + +export const NotSelected = styled.span` + margin-right: 1rem; +`; + +export const MenuItemContent = styled.span` + display: flex; + align-items: center; + gap: 0.5rem; +`; + +export const MenuItemContentColor = styled.span` + width: 1rem; + height: 1rem; + border-radius: 0.5rem; + flex-shrink: 0; + border: 1px solid ${colors.gray300}; +`; + +export const CloseWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0.25rem 0.75rem; +`; + +export const CloseBtn = styled.button` + outline: none; + border: none; + background-color: transparent; + height: 1rem; + font-size: 1rem; + cursor: pointer; +`; + +export const CloseText = styled.strong` + text-transform: capitalize; + font-size: 0.75rem; + line-height: 1rem; +`; diff --git a/cra-rxjs-styled-components/src/components/filter-dropdown/FilterDropdown.tsx b/cra-rxjs-styled-components/src/components/filter-dropdown/FilterDropdown.tsx new file mode 100644 index 000000000..f9ac6ad24 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/filter-dropdown/FilterDropdown.tsx @@ -0,0 +1,98 @@ +import { useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { CaretIcon } from '../details-dropdown/DetailsDropdown.style'; +import { + CloseBtn, + CloseText, + CloseWrapper, + DropDownMenu, + DropdownBtn, + DropdownBtnText, + DropdownContainer, + MenuItem, + MenuItemContainer, + MenuItemContent, + MenuItemContentColor, + NotSelected, +} from './FilterDropdown.styles'; +import { CloseIcon, CorrectIcon } from '../icons'; +interface FilterDropdownProps { + name: string; + items?: string[]; + selected?: string; + itemsColors?: string[]; + selectOption?: (value: string) => void; +} +export default function FilterDropdown({ + name, + items, + selected, + itemsColors, + selectOption, +}: FilterDropdownProps) { + const [showOptions, setShowOptions] = useState(false); + let ref = useRef(null); + + const toggleOption = () => setShowOptions((prev) => !prev); + + const allItems = useMemo(() => { + if (itemsColors && itemsColors.length === (items || []).length) { + return (items || []).map((item, index) => ({ + name: item, + color: itemsColors && (itemsColors[index] || 'ccc'), + })); + } else { + return (items || []).map((item) => ({ + name: item, + color: null, + })); + } + }, [items, itemsColors]); + + const handleClickOutside = (event: any) => { + if (ref.current && !ref.current.contains(event.target)) { + setShowOptions(false); + } + }; + + useLayoutEffect(() => { + document.addEventListener('click', handleClickOutside, true); + return () => { + document.removeEventListener('clic', handleClickOutside, true); + }; + }); + return ( + + + {name} + + + {showOptions && ( + + + Select {name} + setShowOptions(false)}> + + + + {allItems.map(({ name, color }, index) => ( + selectOption?.(name)}> + + {name === selected ? : } + + {color && ( + + )} + {name} + + + + ))} + + )} + + ); +} diff --git a/cra-rxjs-styled-components/src/components/icons/RepoBookIcon.tsx b/cra-rxjs-styled-components/src/components/icons/RepoBookIcon.tsx new file mode 100644 index 000000000..856d40d83 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/icons/RepoBookIcon.tsx @@ -0,0 +1,9 @@ +export const RepoBookIcon = (props: any) => ( + + + +); diff --git a/cra-rxjs-styled-components/src/components/icons/RepoIcon.tsx b/cra-rxjs-styled-components/src/components/icons/RepoIcon.tsx new file mode 100644 index 000000000..c9d3a2a60 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/icons/RepoIcon.tsx @@ -0,0 +1,16 @@ +export const RepoIcon = (props: any) => { + return ( + + + + ); +}; diff --git a/cra-rxjs-styled-components/src/components/icons/index.tsx b/cra-rxjs-styled-components/src/components/icons/index.tsx index 5d0e5cac8..5e3bf58ab 100644 --- a/cra-rxjs-styled-components/src/components/icons/index.tsx +++ b/cra-rxjs-styled-components/src/components/icons/index.tsx @@ -16,3 +16,5 @@ export { ReadmeListIcon } from './ReadmeListIcon'; export { DirectoryIcon } from './DirectoryIcon'; export { FileIcon } from './FileIcon'; export { TwitterIcon } from './TwitterIcon'; +export { RepoIcon } from './RepoIcon'; +export { RepoBookIcon } from './RepoBookIcon'; diff --git a/cra-rxjs-styled-components/src/components/layouts/ProfileLayout.tsx b/cra-rxjs-styled-components/src/components/layouts/ProfileLayout.tsx index deca59958..57c09ec9d 100644 --- a/cra-rxjs-styled-components/src/components/layouts/ProfileLayout.tsx +++ b/cra-rxjs-styled-components/src/components/layouts/ProfileLayout.tsx @@ -28,3 +28,8 @@ export const NetlifyBadgeContainer = styled.div` text-align: center; background-color: white; `; + +export const ProfileNav = styled.div` + background-color: #fff; + padding-top: 40px; +`; diff --git a/cra-rxjs-styled-components/src/components/repo-filter/RepoFilter.styles.ts b/cra-rxjs-styled-components/src/components/repo-filter/RepoFilter.styles.ts new file mode 100644 index 000000000..cbf8c3aab --- /dev/null +++ b/cra-rxjs-styled-components/src/components/repo-filter/RepoFilter.styles.ts @@ -0,0 +1,60 @@ +import colors from '../../constants/colors'; +import styled from 'styled-components'; + +export const Container = styled.div` + background-color: white; +`; + +export const RepoFilterWrapper = styled.div` + display: flex; + gap: 10px; + width: 100%; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: ${colors.gray300}; + flex-direction: column; + padding: 16px 0px; + @media (min-width: 1024px) { + flex-direction: row; + padding: 10px 0px; + } +`; + +export const FiltersWrapper = styled.div` + display: flex; + width: 100%; + flex: 0.45; + @media (min-width: 1024px) && (max-width: 1440px) { + flex: 0.6; + } +`; + +export const SearchTextInput = styled.input` + font-size: 16px; + padding: 8px 12px; + border-radius: 8px; + background-color: #fff; + color: ${colors.gray500}; + border: 1px solid ${colors.gray300}; + flex: 1; +`; + +export const RepoBtn = styled.button` + display: flex; + gap: 8px; + height: 35px; + padding: 6px 12px; + border-radius: 8px; + flex-direction: row; + align-items: center; + justify-content: center; + background-color: ${colors.primaryGreen}; + outline: none; + border: none; +`; + +export const RepoBtnText = styled.span` + color: #fff; + font-size: 14px; + font-weight: 700; +`; diff --git a/cra-rxjs-styled-components/src/components/repo-filter/Repofilter.tsx b/cra-rxjs-styled-components/src/components/repo-filter/Repofilter.tsx new file mode 100644 index 000000000..ebff84e4c --- /dev/null +++ b/cra-rxjs-styled-components/src/components/repo-filter/Repofilter.tsx @@ -0,0 +1,47 @@ +import FilterDropdown from '../filter-dropdown/FilterDropdown'; +import { RepoBookIcon } from '../icons'; +import { + Container, + FiltersWrapper, + RepoBtn, + RepoBtnText, + RepoFilterWrapper, +} from './RepoFilter.styles'; +import SearchInput from './SearchInput'; +import { FILTER_TYPE_OPTIONS, SORT_OPTIONS } from './data'; + +interface RepoFilterProps { + languages?: string[]; + filteredRepoCount?: number; + repoBtnText?: string; +} + +export default function RepoFilter({ + languages, + filteredRepoCount, + repoBtnText, +}: RepoFilterProps) { + const typeOptions = Object.values(FILTER_TYPE_OPTIONS); + const sortOptions = Object.values(SORT_OPTIONS); + const languageOptions = ['All', 'HTML', 'CSS', 'PHP']; + + return ( + + + + + + + 0 ? languages : languageOptions} + /> + + + + {repoBtnText || 'New'} + + + + ); +} diff --git a/cra-rxjs-styled-components/src/components/repo-filter/SearchInput.tsx b/cra-rxjs-styled-components/src/components/repo-filter/SearchInput.tsx new file mode 100644 index 000000000..3f01282b7 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/repo-filter/SearchInput.tsx @@ -0,0 +1,5 @@ +import { SearchTextInput } from './RepoFilter.styles'; + +export default function SearchInput() { + return ; +} diff --git a/cra-rxjs-styled-components/src/components/repo-filter/data.ts b/cra-rxjs-styled-components/src/components/repo-filter/data.ts new file mode 100644 index 000000000..197623a44 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/repo-filter/data.ts @@ -0,0 +1,14 @@ +export const defaultFilterType = 'All'; +export const defaultLanguage = 'All'; +export const defaultSortBy = 'Last updated'; + +export const FILTER_TYPE_OPTIONS = { + default: defaultFilterType, + forks: 'Forks', + archived: 'Archived', +}; +export const SORT_OPTIONS = { + default: defaultSortBy, + name: 'Name', + stars: 'Stars', +}; diff --git a/cra-rxjs-styled-components/src/components/tab-nav/TabNav.styles.ts b/cra-rxjs-styled-components/src/components/tab-nav/TabNav.styles.ts new file mode 100644 index 000000000..8c7ea4e0e --- /dev/null +++ b/cra-rxjs-styled-components/src/components/tab-nav/TabNav.styles.ts @@ -0,0 +1,50 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--gray-200); +`; + +export const TabContainer = styled.div` + flex-direction: row; + margin-bottom: -2px; + justify-content: space-between; + flex-grow: 1; + + @media (min-width: 768px) { + flex-grow: 0; + } +`; + +export const Tab = styled.button<{ isActive: boolean }>` + outline: none; + border: none; + background-color: transparent; + cursor: pointer; + gap: 4px; + padding: 6px; + margin-right: 4px; + flex-direction: row; + align-items: center; + border-bottom-width: 2px; + border-bottom-style: solid; + border-bottom-color: ${({ isActive }) => + isActive ? 'orange' : 'transparent'}; +`; +export const CountView = styled.div` + background-color: var(--gray-200); + margin-left: 8px; + padding: 4px 6px; + border-radius: 20px; +`; + +export const CountText = styled.span` + color: var(--gray-800); + font-size: 12px; +`; + +export const TabText = styled.span<{ isActive: boolean }>` + font-weight: ${({ isActive }) => (isActive ? '600' : '500')}; + margin-left: 3px; +`; diff --git a/cra-rxjs-styled-components/src/components/tab-nav/TabNav.tsx b/cra-rxjs-styled-components/src/components/tab-nav/TabNav.tsx new file mode 100644 index 000000000..2a4ba8077 --- /dev/null +++ b/cra-rxjs-styled-components/src/components/tab-nav/TabNav.tsx @@ -0,0 +1,46 @@ +import { SVGProps } from 'react'; +import { + Container, + TabContainer, + Tab, + TabText, + CountText, + CountView, +} from './TabNav.styles'; +import colors from '../../constants/colors'; + +interface TabNavigationProps { + tabs: { + title: string; + Icon: (props: SVGProps) => JSX.Element; + count?: number; + }[]; + activeTab: string; + onChange?: (title: string) => void; +} + +const TabNavigation = ({ tabs, activeTab, onChange }: TabNavigationProps) => { + return ( + + + {tabs.map(({ title, Icon, count }, index) => ( + onChange?.(title)} + > + + {title} + {typeof count === 'number' && count > 0 && ( + + {count} + + )} + + ))} + + + ); +}; + +export default TabNavigation; diff --git a/cra-rxjs-styled-components/src/constants/colors.tsx b/cra-rxjs-styled-components/src/constants/colors.tsx index e985a34f5..3ff86f2e0 100644 --- a/cra-rxjs-styled-components/src/constants/colors.tsx +++ b/cra-rxjs-styled-components/src/constants/colors.tsx @@ -20,6 +20,7 @@ const colors = { green800: 'rgb(26, 127, 55)', purple500: ' rgb(130, 80, 223)', red600: 'rgb(207, 34, 46)', + primaryGreen: '#2da44e', }; export default colors; diff --git a/cra-rxjs-styled-components/src/constants/data.ts b/cra-rxjs-styled-components/src/constants/data.ts new file mode 100644 index 000000000..081eb6037 --- /dev/null +++ b/cra-rxjs-styled-components/src/constants/data.ts @@ -0,0 +1,3 @@ +import { RepoIcon } from '../components/icons'; + +export const tabs = [{ title: 'Repositories', Icon: RepoIcon }]; diff --git a/cra-rxjs-styled-components/src/routes/profile.tsx b/cra-rxjs-styled-components/src/routes/profile.tsx index 8c891c083..33d943ce7 100644 --- a/cra-rxjs-styled-components/src/routes/profile.tsx +++ b/cra-rxjs-styled-components/src/routes/profile.tsx @@ -2,6 +2,7 @@ import UserProfileView from '../components/user-profile/UserProfile'; import { Layout, NetlifyBadgeContainer, + ProfileNav, } from '../components/layouts/ProfileLayout'; import Header from '../components/header/Header'; import { useUser } from '../context/UserProvider'; @@ -11,6 +12,9 @@ import styled from 'styled-components'; import { PaginateWrapper } from '../components/paginate-button/PaginateButton.style'; import PaginateButton from '../components/paginate-button/PaginateButton'; import LoadingRepoCard from '../components/repo-card/LoadingRepoCard'; +import TabNavigation from '../components/tab-nav/TabNav'; +import { tabs } from '../constants/data'; +import RepoFilter from '../components/repo-filter/Repofilter'; function Profile() { const context = useUser(); @@ -31,6 +35,10 @@ function Profile() { ) : ( <> + + + + {repositories.map((repo) => ( ))}