diff --git a/features/issues/components/filters/filters.styled.ts b/features/issues/components/filters/filters.styled.ts index 7d9c604..6c70271 100644 --- a/features/issues/components/filters/filters.styled.ts +++ b/features/issues/components/filters/filters.styled.ts @@ -1,5 +1,6 @@ import styled from "styled-components"; import { breakpoint } from "@styles/theme"; +import { Select as UnstyledSelect, Input as UnstyledInput } from "@features/ui"; export const Container = styled.div` display: flex; @@ -12,7 +13,20 @@ export const Container = styled.div` flex-direction: row; justify-content: flex-end; order: initial; - gap: 3rem; flex-wrap: wrap; } `; + +export const Select = styled(UnstyledSelect)` + width: 100%; + @media (min-width: ${breakpoint("desktop")}) { + width: 10rem; + } +` as typeof UnstyledSelect; + +export const Input = styled(UnstyledInput)` + width: 100%; + @media (min-width: ${breakpoint("desktop")}) { + width: 17.5rem; + } +`; diff --git a/features/issues/components/filters/filters.tsx b/features/issues/components/filters/filters.tsx index 5b92d0d..02379da 100644 --- a/features/issues/components/filters/filters.tsx +++ b/features/issues/components/filters/filters.tsx @@ -1,157 +1,55 @@ -import React, { - useState, - useEffect, - useCallback, - useRef, - useContext, -} from "react"; -import { useRouter } from "next/router"; -import { useWindowSize } from "react-use"; -import { Select, Option, Input, NavigationContext } from "@features/ui"; -import { useFilters } from "../../hooks/use-filters"; +import React, { useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; import { IssueLevel, IssueStatus } from "@api/issues.types"; -import { useProjects } from "@features/projects"; +import { useFilters } from "../../hooks/use-filters"; import * as S from "./filters.styled"; -export function Filters() { - const { handleFilters, filters } = useFilters(); - const { data: projects } = useProjects(); - const router = useRouter(); - const routerQueryProjectName = - (router.query.projectName as string)?.toLowerCase() || undefined; - const [inputValue, setInputValue] = useState(""); - const projectNames = projects?.map((project) => project.name.toLowerCase()); - const isFirst = useRef(true); - const { width } = useWindowSize(); - const isMobileScreen = width <= 1023; - const { isMobileMenuOpen } = useContext(NavigationContext); - const handleChange = (input: string) => { - setInputValue(input); - - if (inputValue?.length < 2) { - handleProjectName(undefined); - return; - } +const statusOptions = [ + { value: undefined, label: "--None--" }, + { value: IssueStatus.open, label: "Unresolved" }, + { value: IssueStatus.resolved, label: "Resolved" }, +]; - const name = projectNames?.find((name) => - name?.toLowerCase().includes(inputValue.toLowerCase()) - ); +const levelOptions = [ + { value: undefined, label: "--None--" }, + { value: IssueLevel.error, label: "Error" }, + { value: IssueLevel.warning, label: "Warning" }, + { value: IssueLevel.info, label: "Info" }, +]; - if (name) { - handleProjectName(name); - } - }; +export function Filters() { + const { handleFilters, filters } = useFilters(); - const handleLevel = (level?: string) => { - if (level) { - level = level.toLowerCase(); - } - handleFilters({ level: level as IssueLevel }); - }; + const debouncedHandleFilters = useDebouncedCallback(handleFilters, 300); + const [inputValue, setInputValue] = useState(filters.project || ""); - const handleStatus = (status?: string) => { - if (status === "Unresolved") { - status = "open"; - } - if (status) { - status = status.toLowerCase(); - } - handleFilters({ status: status as IssueStatus }); + const handleChange = (project: string) => { + setInputValue(project); + debouncedHandleFilters({ project: project.toLowerCase() }); }; - const handleProjectName = useCallback( - (projectName?: string) => - handleFilters({ project: projectName?.toLowerCase() }), - [handleFilters] - ); - - useEffect(() => { - const newObj: { [key: string]: string } = { - ...filters, - }; - - Object.keys(newObj).forEach((key) => { - if (newObj[key] === undefined) { - delete newObj[key]; - } - }); - - const url = { - pathname: router.pathname, - query: { - page: router.query.page || 1, - ...newObj, - }, - }; - - if (routerQueryProjectName && isFirst) { - handleProjectName(routerQueryProjectName); - setInputValue(routerQueryProjectName || ""); - isFirst.current = false; - } - - router.push(url, undefined, { shallow: false }); - }, [filters.level, filters.status, filters.project, router.query.page]); - return ( - + value={filters.status} + options={statusOptions} + onChange={(status) => handleFilters({ status })} + /> - + value={filters.level} + options={levelOptions} + onChange={(level) => handleFilters({ level })} + /> - ); diff --git a/features/issues/context/filters-context.tsx b/features/issues/context/filters-context.tsx deleted file mode 100644 index a7472ee..0000000 --- a/features/issues/context/filters-context.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { - useState, - useMemo, - useCallback, - createContext, - ReactNode, -} from "react"; -import { IssueFilters } from "@api/issues.types"; - -export const FiltersContext = createContext<{ - filters: IssueFilters; - handleFilters: (filter: IssueFilters) => unknown; -}>({ - filters: { status: undefined, level: undefined, project: undefined }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - handleFilters: (_filter: IssueFilters) => {}, -}); - -type FiltersProviderProps = { - children: ReactNode | ReactNode[]; -}; - -export function FiltersProvider({ children }: FiltersProviderProps) { - const [filters, setFilters] = useState({ - status: undefined, - level: undefined, - project: undefined, - }); - - const handleFilters = useCallback( - (filter: any) => - setFilters((prevFilters) => ({ ...prevFilters, ...filter })), - [] - ); - - const memoizedValue = useMemo( - () => ({ filters, handleFilters }), - [filters, handleFilters] - ); - - return ( - - {children} - - ); -} diff --git a/features/issues/context/index.ts b/features/issues/context/index.ts deleted file mode 100644 index cfeccc8..0000000 --- a/features/issues/context/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./filters-context"; diff --git a/features/issues/hooks/use-filters.ts b/features/issues/hooks/use-filters.ts index 8ebce02..2256aca 100644 --- a/features/issues/hooks/use-filters.ts +++ b/features/issues/hooks/use-filters.ts @@ -1,4 +1,19 @@ -import { useContext } from "react"; -import { FiltersContext } from "../context/filters-context"; +import { useRouter } from "next/router"; +import { IssueFilters } from "@api/issues.types"; -export const useFilters = () => useContext(FiltersContext); +export const useFilters = () => { + const router = useRouter(); + + const filters = { + status: router.query.status, + level: router.query.level, + project: router.query.project, + } as IssueFilters; + + const handleFilters = (newFilters: IssueFilters) => { + const query = { ...router.query, ...newFilters }; + router.push({ query }); + }; + + return { filters, handleFilters }; +}; diff --git a/features/issues/index.ts b/features/issues/index.ts index 4818906..8e6318f 100644 --- a/features/issues/index.ts +++ b/features/issues/index.ts @@ -1,3 +1,2 @@ export * from "./api"; export * from "./components/issue-list"; -export * from "./context"; diff --git a/features/ui/input/input.styled.ts b/features/ui/input/input.styled.ts index c5ce947..a410060 100644 --- a/features/ui/input/input.styled.ts +++ b/features/ui/input/input.styled.ts @@ -17,6 +17,7 @@ export const InputContainer = styled.input<{ border-radius: 7px; width: calc(${space(20)} * 4 - ${space(6)}); padding: ${space(2, 3)}; + box-sizing: border-box; letter-spacing: 0.05rem; color: ${color("gray", 900)}; ${textFont("md", "regular")}; diff --git a/features/ui/page-container/page-container.tsx b/features/ui/page-container/page-container.tsx index d5e132d..4f97f80 100644 --- a/features/ui/page-container/page-container.tsx +++ b/features/ui/page-container/page-container.tsx @@ -3,7 +3,6 @@ import Head from "next/head"; import styled from "styled-components"; import { SidebarNavigation } from "../sidebar-navigation"; import { color, displayFont, textFont, space, breakpoint } from "@styles/theme"; -import { FiltersProvider } from "@features/issues"; type PageContainerProps = { children: React.ReactNode; @@ -58,23 +57,21 @@ export function PageContainer({ children, title, info }: PageContainerProps) { // "Warning: A title element received an array with more than 1 element as children." const documentTitle = `ProLog - ${title}`; return ( - - - - {documentTitle} - - - + + + {documentTitle} + + + - -
- - {title} - {info} - {children} - -
-
-
+ +
+ + {title} + {info} + {children} + +
+ ); } diff --git a/features/ui/select/option.styled.ts b/features/ui/select/option.styled.ts index 445036f..3da082b 100644 --- a/features/ui/select/option.styled.ts +++ b/features/ui/select/option.styled.ts @@ -1,40 +1,24 @@ -import styled, { css } from "styled-components"; +import styled from "styled-components"; -export const ListItem = styled.li.attrs(() => ({ - tabIndex: 0, -}))<{ isCurrentlySelected: boolean }>` - margin: 0; - padding: 0; +export const ListItem = styled.li<{ isSelected: boolean }>` display: flex; - width: 100%; justify-content: space-between; align-items: center; + width: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; list-style-type: none; - padding-inline: 0.75rem; - padding-block: calc(0.75rem - 0.1rem); - font-size: 1rem; - line-height: 1.5rem; - font-weight: 400; + padding: 0.625rem 0.75rem; cursor: pointer; - z-index: 100; color: #1d2939; - background-color: #fff; - box-sizing: border-box; - ${({ isCurrentlySelected }) => - isCurrentlySelected && - css` - background-color: #fcfaff; - `}; - + background-color: ${({ isSelected }) => (isSelected ? "#fcfaff" : "#ffffff")}; &:hover { - background: #f4ebff; + background-color: #f4ebff; } `; -export const ListItemIcon = styled.img<{ isCurrentlySelected: boolean }>` - display: ${({ isCurrentlySelected }) => - isCurrentlySelected ? "block" : "none"}; - padding: 0; +export const ListItemIcon = styled.img` width: 1rem; height: 1rem; `; diff --git a/features/ui/select/option.tsx b/features/ui/select/option.tsx index f21fdc7..37a3c34 100644 --- a/features/ui/select/option.tsx +++ b/features/ui/select/option.tsx @@ -1,34 +1,32 @@ import React, { ReactNode } from "react"; -import { useSelectContext } from "./select-context"; import * as S from "./option.styled"; -type OptionProps = { - children: ReactNode | ReactNode[]; - value: any; - handleCallback?: (value: any) => unknown; +type OptionProps

= { + children: ReactNode; + value: P; + isSelected: boolean; + onClick: (value: P) => void; + onKeyDown: (event: React.KeyboardEvent, value: P) => void; }; -export function Option({ children, value, handleCallback }: OptionProps) { - const { changeSelectedOption, selectedOption } = useSelectContext(); - const isCurrentlySelected = selectedOption === value; - +export function Option

({ + children, + value, + onClick, + onKeyDown, + isSelected, +}: OptionProps

) { return ( { - changeSelectedOption(value); - if (handleCallback) { - handleCallback(value); - } - }} + isSelected={isSelected} + aria-selected={isSelected} + onClick={() => onClick(value)} + onKeyDown={(event) => onKeyDown(event, value)} role="option" + tabIndex={0} > {children} - + {isSelected && } ); } diff --git a/features/ui/select/select.stories.tsx b/features/ui/select/select.stories.tsx index 9a8637c..830eb20 100644 --- a/features/ui/select/select.stories.tsx +++ b/features/ui/select/select.stories.tsx @@ -1,7 +1,6 @@ -import React from "react"; +import React, { useState } from "react"; import { ComponentStory, ComponentMeta } from "@storybook/react"; -import { Select, Option } from "./"; - +import { Select } from "./select"; export default { title: "UI/Select", component: Select, @@ -9,30 +8,29 @@ export default { layout: "fullscreen", }, } as ComponentMeta; - -const selectData = [ - "Phoenix Baker", - "Olivia Rhye", - "Lana Steiner", - "Demi Wilkinson", - "Candice Wu", - "Natali Craig", - "Drew Cano", +const options = [ + { label: "Phoenix Baker", value: 1 }, + { label: "Olivia Rhye", value: 2 }, + { label: "Lana Steiner", value: 3 }, + { label: "Demi Wilkinson", value: 4 }, + { label: "Candice Wu", value: 5 }, + { label: "Natali Craig", value: 6 }, + { label: "Drew Cano", value: 7 }, ]; - const Template: ComponentStory = (props) => { + const [selectedValue, setSelectedValue] = useState(); return ( -

- +
+