From d2e1c32f5b87d60899473359304150521ff37c9e Mon Sep 17 00:00:00 2001 From: No0ne003 Date: Fri, 7 Jun 2024 12:33:25 +0100 Subject: [PATCH 1/3] chore: setup select component --- src/App.jsx | 2 ++ src/data/projects.js | 15 +++++++++++++++ src/pages/Select-Component/index.tsx | 8 ++++++++ 3 files changed, 25 insertions(+) create mode 100644 src/pages/Select-Component/index.tsx diff --git a/src/App.jsx b/src/App.jsx index 0a309f9..8e435b6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -54,6 +54,7 @@ const ScrollToSection = lazy( () => import("./pages/Scroll-to-section/ScrollToSection"), ); const WeatherApp = lazy(() => import("@/pages/Weather-app/index")); +const SelectComponent = lazy(() => import("@/pages/Select-Component/index")) function App() { const [cursorVariant, setCursorVariant] = useState("default"); @@ -134,6 +135,7 @@ function App() { element={} /> } /> + } /> } /> diff --git a/src/data/projects.js b/src/data/projects.js index 40691ed..65f06c8 100644 --- a/src/data/projects.js +++ b/src/data/projects.js @@ -3,6 +3,7 @@ export const projects = [ id: 1, name: "Accordion", path: "accordion", + tags: ['component'] }, { id: 2, @@ -14,11 +15,13 @@ export const projects = [ id: 3, name: "Star Rating", path: "star-rating", + tags: ['component'] }, { id: 4, name: "Image Slider", path: "image-slider", + tags: ['component'] }, { id: 5, @@ -30,6 +33,7 @@ export const projects = [ id: 6, name: "Tree View", path: "tree-view", + tags: ['component'] }, { id: 7, @@ -41,16 +45,19 @@ export const projects = [ id: 8, name: "Light Dark Mode", path: "light-dark-mode", + tags: ['component'] }, { id: 9, name: "Custom Tabs", path: "custom-tabs", + tags: ['component'] }, { id: 10, name: "Modal Popup", path: "modal-popup", + tags: ['component'] }, { id: 11, @@ -62,6 +69,7 @@ export const projects = [ id: 12, name: "Search Auto-Complete", path: "search-auto-complete", + tags: ['component'] }, { id: 13, @@ -73,6 +81,7 @@ export const projects = [ id: 14, name: "Feature Flags", path: "feature-flags", + tags: ['component'] }, { id: 15, @@ -103,5 +112,11 @@ export const projects = [ name: 'Weather App', path: 'weather-app', tags: ['project'] + }, + { + id: 20, + name: 'Select component', + path: 'select-component', + tags: ['component'] } ]; diff --git a/src/pages/Select-Component/index.tsx b/src/pages/Select-Component/index.tsx new file mode 100644 index 0000000..d7fb9a4 --- /dev/null +++ b/src/pages/Select-Component/index.tsx @@ -0,0 +1,8 @@ + +const SelectComponent = () => { + return ( +
SelectComponent
+ ) +} + +export default SelectComponent From 07198c19a2eb831f59c6120f2b06bab51f00c1cf Mon Sep 17 00:00:00 2001 From: No0ne003 Date: Sat, 8 Jun 2024 10:46:09 +0100 Subject: [PATCH 2/3] chore: some changes --- package.json | 2 +- tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1a45a44..c56febb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, diff --git a/tsconfig.json b/tsconfig.json index 7c34376..21ba8cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2020", + "target": "ESNext", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", From 5011452de6809a8b70d2b24db59e4504580ae855 Mon Sep 17 00:00:00 2001 From: No0ne003 Date: Sat, 8 Jun 2024 13:08:23 +0100 Subject: [PATCH 3/3] feat: select component project --- .../Select-Component/components/Select.tsx | 156 ++++++++++++++++++ src/pages/Select-Component/index.tsx | 28 +++- tailwind.config.js | 7 +- 3 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 src/pages/Select-Component/components/Select.tsx diff --git a/src/pages/Select-Component/components/Select.tsx b/src/pages/Select-Component/components/Select.tsx new file mode 100644 index 0000000..b155721 --- /dev/null +++ b/src/pages/Select-Component/components/Select.tsx @@ -0,0 +1,156 @@ +import { cn } from "@/lib/utils"; +import { useEffect, useRef, useState } from "react"; + +export type SelectOptions = { + label: string; + value: string | number; +}; +type MultipleSelectProps = { + multiple: true; + onChange: (value: SelectOptions[]) => void; + value: SelectOptions[]; +}; +type SingleSelectProps = { + multiple?: false; + onChange: (value: SelectOptions | undefined) => void; + value?: SelectOptions; +}; +type SelectProps = { + options: SelectOptions[]; +} & (SingleSelectProps | MultipleSelectProps); + +const Select = ({ multiple, value, onChange, options }: SelectProps) => { + const [isOpen, setIsOpen] = useState(false); + const [highlightedIndex, setHighlightedIndex] = useState(0); + const containerRef = useRef(null); + + function clearOptions() { + multiple ? onChange([]) : onChange(undefined); + } + + function selectOption(option: SelectOptions) { + if (multiple) { + if (value.includes(option)) { + onChange(value.filter((o) => o !== option)); + } else { + onChange([...value, option]); + } + } else { + if (option !== value) onChange(option); + } + } + + function isOptionSelected(option: SelectOptions) { + return multiple ? value.includes(option) : option === value; + } + + useEffect(() => { + if (isOpen) setHighlightedIndex(0); + }, [isOpen]); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.target != containerRef.current) return; + switch (e.code) { + case "Enter": + case "Space": + setIsOpen((prev) => !prev); + if (isOpen) selectOption(options[highlightedIndex]); + break; + case "ArrowUp": + case "ArrowDown": { + if (!isOpen) { + setIsOpen(true); + break; + } + + const newValue = highlightedIndex + (e.code === "ArrowDown" ? 1 : -1); + if (newValue >= 0 && newValue < options.length) { + setHighlightedIndex(newValue); + } + break; + } + case "Escape": + setIsOpen(false); + break; + } + }; + containerRef.current?.addEventListener("keydown", handler); + + return () => { + containerRef.current?.removeEventListener("keydown", handler); + }; + }, [isOpen, highlightedIndex, options]); + + return ( +
setIsOpen(false)} + onClick={() => setIsOpen((prev) => !prev)} + tabIndex={0} + className="relative w-[20em] min-h-[1.5em] border-[0.05em] border-solid border-border flex items-center gap-[0.5em] p-[0.5em] rounded-lg outline-none focus:border-primary" + > + + {multiple + ? value.map((v) => ( + + )) + : value?.label} + + +
+
+ +
+ ); +}; + +export default Select; diff --git a/src/pages/Select-Component/index.tsx b/src/pages/Select-Component/index.tsx index d7fb9a4..582c61e 100644 --- a/src/pages/Select-Component/index.tsx +++ b/src/pages/Select-Component/index.tsx @@ -1,8 +1,28 @@ +import { useState } from "react"; +import Select, { SelectOptions } from "./components/Select"; + +const options = [ + { label: "First", value: 1 }, + { label: "Second", value: 2 }, + { label: "Third", value: 3 }, + { label: "Fourth ", value: 4 }, + { label: "Fifth", value: 5 }, +]; const SelectComponent = () => { + const [value1, setValue1] = useState([options[0]]); + const [value2, setValue2] = useState(options[0]); return ( -
SelectComponent
- ) -} +
+ setValue2(o)} /> +
+ ); +}; -export default SelectComponent +export default SelectComponent; diff --git a/tailwind.config.js b/tailwind.config.js index edc7c46..e0a4e69 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -5,10 +5,9 @@ export default { darkMode: ["class"], content: [ "./index.html", - "./pages/*.{js,jsx}", - "./pages/**/*.{js,jsx}", - "./components/**/*.{js,jsx}", - "./src/**/*.{js,jsx}", + "./src/pages/**/*.{js,jsx,ts,tsx}", + "./src/components/**/*.{js,jsx,ts,tsx}", + "./src/**/*.{js,jsx,ts,tsx}", ], prefix: "", theme: {