Repository: onesine/react-tailwindcss-select Branch: master Commit: c187e4e06963 Files: 39 Total size: 84.0 KB Directory structure: gitextract_exwwbg6a/ ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── next-env.d.ts ├── next.config.js ├── package.json ├── page-components/ │ ├── Alert.jsx │ ├── Button.jsx │ ├── Checkbox.jsx │ ├── Header.jsx │ ├── Link.jsx │ ├── SelectContainer.jsx │ └── TailwindColors.jsx ├── pages/ │ ├── _app.js │ └── index.js ├── postcss.config.js ├── rollup.config.js ├── src/ │ ├── components/ │ │ ├── DisabledItem.tsx │ │ ├── GroupItem.tsx │ │ ├── Icons.tsx │ │ ├── Item.tsx │ │ ├── Options.tsx │ │ ├── SearchInput.tsx │ │ ├── Select.tsx │ │ ├── SelectProvider.tsx │ │ ├── Spinner.tsx │ │ └── type.ts │ ├── constants/ │ │ └── index.ts │ ├── hooks/ │ │ └── use-onclick-outside.ts │ ├── index.css │ └── index.tsx ├── tailwind.config.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ # Folders dist/ assets/ pages/ components/ styles/ # Files README.md ================================================ FILE: .eslintrc.json ================================================ { "root": true, "env": { "browser": true, "es2021": true, "node": true }, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:react-hooks/recommended", "next/core-web-vitals" ], "overrides": [], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "ecmaFeatures": { "jsx": true }, "sourceType": "module" }, "settings": { "react": { "version": "detect" } }, "plugins": ["react", "@typescript-eslint", "import", "prettier", "@next/eslint-plugin-next"], "rules": { "indent": ["error", 4], "linebreak-style": ["error", "unix"], "quotes": ["error", "double"], "semi": ["error", "always"], "import/order": [ "error", { "alphabetize": { "order": "asc", "caseInsensitive": true }, "newlines-between": "always" } ], "react/prop-types": "off", "react/jsx-uses-react": "off", "react/react-in-jsx-scope": "off", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "@typescript-eslint/no-var-requires": 0, "prettier/prettier": ["error", { "endOfLine": "auto" }, { "usePrettierrc": true }] } } ================================================ FILE: .gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies node_modules /.next/ /.rollup.cache/ # test coverage coverage # builds build dist .rpt2_cache .eslintcache tsconfig.tsbuildinfo # misc .DS_Store .env .env.local .env.development.local .env.test.local .env.production.local .yarn .yarnrc.yml npm-debug.log* yarn-debug.log* yarn-error.log* .vscode .idea ================================================ FILE: .npmignore ================================================ ## Folders src node_modules .vscode .idea assets .git pages page-components styles .next .rollup.cache ## Files babel.config.json tsconfig.json rollup.config.js next.config.js next-env.d.ts .gitignore .eslintignore .eslintrc.json .prettierrc .prettierignore .DS_Store npm-debug.log package-lock.json yarn.lock tailwind.config.js postcss.config.js tsconfig.tsbuildinfo ================================================ FILE: .prettierignore ================================================ # Folders dist/ assets/ .next/ .rollup.cache/ # Files README.md ================================================ FILE: .prettierrc ================================================ { "semi": true, "tabWidth": 4, "printWidth": 100, "singleQuote": false, "trailingComma": "none", "quoteProps": "as-needed", "jsxSingleQuote": false, "bracketSpacing": true, "arrowParens": "avoid", "proseWrap": "always" } ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Thanks for your interest in contributing to `react-tailwindcss-select`! Please take a moment to review this document **before submitting a pull request**. - [Pull requests](#pull-requests) - [Installation](#installation) - [Coding standards](#coding-standards) - [Running playground](#running-playgrounds) - [Before you make a Pull Request](#before-you-make-a-pull-request) ## Pull requests **Please ask first before starting work on any significant new features.** It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create [an issue](https://github.com/onesine/react-tailwindcss-select/issues) to first discuss any significant new features. ## Installation You only require a `yarn install` in the root directory to install everything you need. ```sh yarn install ``` ## Coding standards We use `prettier` for making sure that the codebase is formatted consistently. To automatically fix any style violations in your code, you can run: **Using yarn** ```sh yarn pret:fix ``` **Using npm** ```sh npm pret:fix ``` ## Running playground We currently use `next.js` as server for live testing. You can run the `dev` script and open your browser to `http://localhost:8888`. See complete `props` usage in `pages/index.js` file. **Using yarn** ```sh yarn dev ``` **Using npm** ```sh npm dev ``` ## Before you make a Pull Request We recommend to run these scripts in sequence before you make your commit message amd open a Pull Request **Let's clean the code first** ```sh yarn pret:fix ``` **Test a build of your changes** ```sh yarn build ``` ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Onesine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

📦 React tailwindcss select

React-tailwindcss-select is a simple component ready to be inserted into your project
This component inspired by React-select is a select input made with Tailwindcss and React.

MIT License

## Features - ✅ Select field for a single item - ✅ Selection field for multiple items - ✅ Optional button to clear the field - ✅ Optional search for an item - ✅ Optional deactivation of an option - ✅ TypeScript support - ✅ Group options - ✅ Customization of the select field style - ⬜ Fixed Options (multiple items select) ## Why ❔ A select with the above features is above all indispensable in many projects. On a project using tailwindcss, when I install [react-select](https://react-select.com) or other such packages, the style of the latter is affected by that of [tailwind](https://tailwindcss.com/). Rather than looking for a component that uses [tailwind](https://tailwindcss.com/), I preferred to make my own based on react-select which I like (and also because I generally like to reinvent the wheel 😅).

preview react-tailwindcss-select

## Online Demo You can find the online demo at [here](https://demo-react-tailwindcss-select.vercel.app/) ## Install You can use yarn ```bash yarn add react-tailwindcss-select ``` Or via npm ```bash npm install react-tailwindcss-select ``` make sure you have installed the peer dependencies as well with the below versions. ``` "react": "^18.2.0" ``` ## Usage This component also exports a tiny CSS file built by tailwind. All CSS classes used in designing and customizing the select component are all custom tailwind classes which ensures that an existing tailwind project would not need to include this CSS file again. ### Tailwind Project A tailwind project would only have to import the react component using `import Select from 'react-tailwindcss-select'` and specify the component in the tailwind configuration to generate the styles of the classes used by react-tailwindcss-select. Use this code to add the component to the tailwind configuration ```javascript // in your tailwind.config.js module.exports = { // ... content: [ "./src/**/*.{js,jsx,ts,tsx}", "./node_modules/react-tailwindcss-select/dist/index.esm.js" ] // ... }; ``` ### None Tailwind Project On a project that does not use tailwind, you need to import the component's CSS as well. To do this use these two codes: `import Select from 'react-tailwindcss-select'` and `import 'react-tailwindcss-select/dist/index.css'` > **Warning** > > In this case when you don't use tailwind on your project, think about isolating the component and > its style so that tailwind doesn't affect the style of the elements in your project. For this, you > can use the > [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). Then use react-tailwindcss-select in your app: #### With React Component ```javascript import React from "react"; import Select from "react-tailwindcss-select"; const options = [ { value: "fox", label: "🦊 Fox" }, { value: "Butterfly", label: "🦋 Butterfly" }, { value: "Honeybee", label: "🐝 Honeybee" } ]; class App extends React.Component { constructor(props) { super(props); this.state = { animal: null }; this.handleChange = this.handleChange.bind(this); } handleChange(value) { console.log("value:", value); this.setState({ animal: value }); } render() { const { animal } = this.state; return ( ); }; export default App; ``` ## Theming options **Supported themes** ![Theme supported](https://raw.githubusercontent.com/onesine/react-tailwindcss-datepicker/master/assets/img/Screen_Shot_2022-08-04_at_17.04.09_theme.png?raw=true) To change the default theme, simply add the `primaryColor` props to your select field with the theme value. By default, the `primaryColor` is set to `blue` ### Indigo example ```javascript import { useState } from "react"; import Select from "react-tailwindcss-select"; const options = [ { value: "fox", label: "🦊 Fox" }, { value: "Butterfly", label: "🦋 Butterfly" }, { value: "Honeybee", label: "🐝 Honeybee" } ]; const App = () => { const [animal, setAnimal] = useState(null); const handleChange = value => { console.log("value:", value); setAnimal(value); }; return ( (
// 👉 data represents each subgroup {data.label} {data.options.length}
)} /> ); }; export default App; ``` > **Info** > > 👉 data represents each subgroup. ### formatOptionLabel `formatOptionLabel` allows you to use a custom rendering template for each option in the list.
```jsx import { useState } from "react"; import Select from "react-tailwindcss-select"; const options = [ { value: "fox", label: "🦊 Fox" }, { value: "Butterfly", label: "🦋 Butterfly" }, { value: "Honeybee", label: "🐝 Honeybee" } ]; const App = () => { const [animal, setAnimal] = useState(null); const handleChange = value => { console.log("value:", value); setAnimal(value); }; return ( ( `flex text-sm text-gray-500 border border-gray-300 rounded shadow-sm transition-all duration-300 focus:outline-none ${ isDisabled ? "bg-gray-200" : "bg-white hover:border-gray-400 focus:border-blue-500 focus:ring focus:ring-blue-500/20" }` ), menu: "absolute z-10 w-full bg-white shadow-lg border rounded py-1 mt-1.5 text-sm text-gray-700", listItem: ({ isSelected }) => ( `block transition duration-200 px-2 py-2 cursor-pointer select-none truncate rounded ${ isSelected ? `text-white bg-blue-500` : `text-gray-500 hover:bg-blue-100 hover:text-blue-500` }` ) }} /> ); }; export default App; ``` ## PlayGround Clone the `master` branch and run commands: ```sh # Using npm npm install && npm dev # Using yarn yarn install && yarn dev ``` Open a browser and navigate to `http://localhost:8888` ## Contributing Got ideas on how to make this better? Open an issue! Don't forget to see [CONTRIBUTING.md](https://github.com/onesine/react-tailwindcss-select/blob/master/CONTRIBUTING.md) ## Thanks This component is inspired by the excellent [react-select](https://react-select.com/) library by Jed Watson. I thank you in advance for your contribution to this project. ## License MIT Licensed. Copyright (c) Lewhe Onesine 2022. ================================================ FILE: next-env.d.ts ================================================ /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. ================================================ FILE: next.config.js ================================================ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true }; module.exports = nextConfig; ================================================ FILE: package.json ================================================ { "name": "react-tailwindcss-select", "version": "1.8.5", "description": "A select input made with React js and Tailwind CSS", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts", "author": "onesine", "license": "MIT", "scripts": { "watch": "rollup -c -w", "clean": "rm -rf dist", "tailwind-build": "tailwindcss -i ./src/index.css -o ./dist/index.css --minify", "lint": "eslint .", "lint:fix": "eslint --fix .", "pret": "prettier -c .", "pret:fix": "prettier -w .", "format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc", "build": "npm run pret && npm run lint && npm run clean && rollup -c && npm run tailwind-build", "dev": "next dev -p 8888" }, "repository": { "type": "git", "url": "https://github.com/onesine/react-tailwindcss-select" }, "keywords": [ "combobox", "form", "input", "multiselect", "react", "react-component", "react-tailwind", "select", "tailwind", "tailwindcss", "ui" ], "peerDependencies": { "react": "^18.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^11.0.0", "@tailwindcss/forms": "^0.5.2", "@types/node": "18.14.6", "@types/react": "^18.0.21", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.13", "eslint": "^8.28.0", "eslint-config-next": "^13.2.3", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.31.11", "eslint-plugin-react-hooks": "^4.6.0", "next": "^13.2.3", "postcss": "^8.4.14", "prettier": "^2.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", "rollup": "^2.77.2", "tailwindcss": "^3.1.7", "tslib": "^2.4.0", "typescript": "^4.8.4" } } ================================================ FILE: page-components/Alert.jsx ================================================ const Alert = ({ children, title, type = "info" }) => { return (
{type === "info" && ( )}

{title}

{children}
); }; export default Alert; ================================================ FILE: page-components/Button.jsx ================================================ const Button = ({ children, icon = null, active = false, onClick }) => { return ( ); }; export default Button; ================================================ FILE: page-components/Checkbox.jsx ================================================ const Checkbox = ({ children, checked, onChange, id }) => { return ( ); }; export default Checkbox; ================================================ FILE: page-components/Header.jsx ================================================ const Header = ({ children }) => { return (
{children}
); }; export default Header; ================================================ FILE: page-components/Link.jsx ================================================ export const DarkLink = ({ children, url }) => { return ( {children} ); }; export const LightLink = ({ children, url }) => { return ( {children} ); }; ================================================ FILE: page-components/SelectContainer.jsx ================================================ const SelectContainer = ({ children }) => { return (
{children}
); }; export default SelectContainer; ================================================ FILE: page-components/TailwindColors.jsx ================================================ const TailwindColors = ({ changeColor }) => { return (
changeColor("blue")} className="h-7 px-2 text-xs font-semibold bg-blue-500 rounded-md flex items-center justify-center text-white cursor-pointer" > blue
changeColor("orange")} className="h-7 px-2 text-xs font-semibold bg-orange-500 rounded-md flex items-center justify-center text-white cursor-pointer" > orange
changeColor("yellow")} className="h-7 px-2 text-xs font-semibold bg-yellow-500 rounded-md flex items-center justify-center text-white cursor-pointer" > yellow
changeColor("red")} className="h-7 px-2 text-xs font-semibold bg-red-500 rounded-md flex items-center justify-center text-white cursor-pointer" > red
changeColor("purple")} className="h-7 px-2 text-xs font-semibold bg-purple-500 rounded-md flex items-center justify-center text-white cursor-pointer" > purple
changeColor("amber")} className="h-7 px-2 text-xs font-semibold bg-amber-500 rounded-md flex items-center justify-center text-white cursor-pointer" > amber
changeColor("lime")} className="h-7 px-2 text-xs font-semibold bg-lime-500 rounded-md flex items-center justify-center text-white cursor-pointer" > lime
changeColor("green")} className="h-7 px-2 text-xs font-semibold bg-green-500 rounded-md flex items-center justify-center text-white cursor-pointer" > green
changeColor("emerald")} className="h-7 px-2 text-xs font-semibold bg-emerald-500 rounded-md flex items-center justify-center text-white cursor-pointer" > emerald
changeColor("teal")} className="h-7 px-2 text-xs font-semibold bg-teal-500 rounded-md flex items-center justify-center text-white cursor-pointer" > teal
changeColor("cyan")} className="h-7 px-2 text-xs font-semibold bg-cyan-500 rounded-md flex items-center justify-center text-white cursor-pointer" > cyan
changeColor("sky")} className="h-7 px-2 text-xs font-semibold bg-sky-500 rounded-md flex items-center justify-center text-white cursor-pointer" > sky
changeColor("indigo")} className="h-7 px-2 text-xs font-semibold bg-indigo-500 rounded-md flex items-center justify-center text-white cursor-pointer" > indigo
changeColor("violet")} className="h-7 px-2 text-xs font-semibold bg-violet-500 rounded-md flex items-center justify-center text-white cursor-pointer" > violet
changeColor("purple")} className="h-7 px-2 text-xs font-semibold bg-purple-500 rounded-md flex items-center justify-center text-white cursor-pointer" > purple
changeColor("fuchsia")} className="h-7 px-2 text-xs font-semibold bg-fuchsia-500 rounded-md flex items-center justify-center text-white cursor-pointer" > fuchsia
changeColor("pink")} className="h-7 px-2 text-xs font-semibold bg-pink-500 rounded-md flex items-center justify-center text-white cursor-pointer" > pink
changeColor("rose")} className="h-7 px-2 text-xs font-semibold bg-rose-500 rounded-md flex items-center justify-center text-white cursor-pointer" > rose
); }; export default TailwindColors; ================================================ FILE: pages/_app.js ================================================ import "../src/index.css"; const App = ({ Component, pageProps }) => { return ; }; export default App; ================================================ FILE: pages/index.js ================================================ import Head from "next/head"; import Select from "../src"; import Header from "../page-components/Header"; import Button from "../page-components/Button"; import SelectContainer from "../page-components/SelectContainer"; import { useCallback, useEffect, useState } from "react"; import TailwindColors from "../page-components/TailwindColors"; import Checkbox from "../page-components/Checkbox"; import Alert from "../page-components/Alert"; import { DarkLink, LightLink } from "../page-components/Link"; const MANGAS = [ { label: "SHONEN", options: [ { value: "One Piece", label: "🤩 One Piece", disabled: false }, { value: "Naruto Shippûden", label: "🤭 Naruto Shippûden", disabled: false }, { value: "Hunter x Hunter", label: "🥰 Hunter x Hunter", disabled: false } ] }, { label: "SHOJO", options: [ { value: "Orange", label: "🤩 Orange", disabled: false }, { value: "Nana", label: "🤭 Nana", disabled: false }, { value: "Tonari no Kaibutsu-kun", label: "🥰 Tonari no Kaibutsu-kun", disabled: false } ] }, { label: "SEINEN", options: [ { value: "Death Note", label: "🤩 Death Note", disabled: false }, { value: "Btooom!", label: "🤭 Btooom!", disabled: false }, { value: "Black Lagoon", label: "🥰 Black Lagoon", disabled: false } ] }, { label: "JOSEI", options: [ { value: "Nodame Cantabile", label: "🤩 Nodame Cantabile", disabled: false }, { value: "Chihayafuru", label: "🤭 Chihayafuru", disabled: false }, { value: "Blue", label: "🥰 Blue", disabled: false } ] }, { value: "Naruto Shippûden", label: "🤭 Naruto Shippûden", disabled: false }, { value: "One Piece", label: "🤩 One Piece", disabled: false }, { value: "Bleach", label: "🥹 Bleach", disabled: false }, { value: "Boruto", label: "😡 Boruto", disabled: false }, { value: "Hunter x Hunter", label: "🥰 Hunter x Hunter", disabled: false }, { value: "Dragon Ball Z", label: "🥵 Dragon Ball Z", disabled: false }, { value: "Fullmetal Alchemist", label: "🫡 Fullmetal Alchemist", disabled: false }, { value: "My Hero Academia", label: "🤯 My Hero Academia", disabled: false }, { value: "Black Clover", label: "😍 Black Clover", disabled: false } ]; const SELECT_OPTIONS = [ "isClearable", "isSearchable", "isMultiple", "isDisabled", "loading", "isGroupOption" ]; const printAlertContent = (element, value) => { const printText = (text, value) => value ?

{text}

: null; switch (element) { case "isClearable": return printText("You can empty the field", value); case "isSearchable": return printText("You can search for an item in the option list", value); case "isMultiple": return printText("You can select several options", value); case "isDisabled": return printText("The field is disabled", value); case "loading": return printText("A loader appears on the field", value); case "isGroupOption": return printText("The options of the select field are grouped", value); default: return null; } }; const Home = () => { const [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); const [showCode, setShowCode] = useState(false); const [value, setValue] = useState(null); const [isClearable, setIsClearable] = useState(false); const [isMultiple, setIsMultiple] = useState(false); const [isSearchable, setIsSearchable] = useState(false); const [isDisabled, setIsDisabled] = useState(false); const [isGroupOption, setIsGroupOption] = useState(false); const [primaryColor, setPrimaryColor] = useState("purple"); useEffect(() => { setLoading(true); const timer = setTimeout(() => { setOptions(MANGAS); setLoading(false); }, 3000); return () => { clearTimeout(timer); }; }, []); const filterOptions = useCallback( data => { return data.filter(item => (isGroupOption ? "options" in item : !("options" in item))); }, [isGroupOption] ); const toggleShowCode = useCallback(() => { setShowCode(!showCode); }, [showCode]); const dispatch = useCallback( (type = null, action, valueData = null) => { switch (type) { case "isClearable": if (action === "set") setIsClearable(valueData); if (action === "get") return isClearable; break; case "isSearchable": if (action === "set") setIsSearchable(valueData); if (action === "get") return isSearchable; break; case "isMultiple": if (action === "set") { if (value !== null) { setValue(null); } setIsMultiple(valueData); } if (action === "get") return isMultiple; break; case "isDisabled": if (action === "set") setIsDisabled(valueData); if (action === "get") return isDisabled; break; case "loading": if (action === "set") setLoading(valueData); if (action === "get") return loading; break; case "isGroupOption": if (action === "set") { setIsGroupOption(valueData); } if (action === "get") return isGroupOption; break; default: break; } }, [isClearable, isDisabled, isGroupOption, isMultiple, isSearchable, loading, value] ); const handleCheck = useCallback( (value, item) => { dispatch(item, "set", value); }, [dispatch] ); return ( <> react-tailwindcss-select PlayGround

Demo react-tailwindcss-select

{showCode ? (

This part will be available soon.

You can access the source code of the demo project{" "} here .
Any contribution to the package will be welcome. You can access the package source code{" "} here
Thanks for testing{" "} react-tailwindcss-select {" "} and have a nice 👋 day.

) : (
); }); export default SearchInput; ================================================ FILE: src/components/Select.tsx ================================================ import React, { useCallback, useEffect, useRef, useState } from "react"; import { COLORS, DEFAULT_THEME, THEME_DATA } from "../constants"; import useOnClickOutside from "../hooks/use-onclick-outside"; import { ChevronIcon, CloseIcon } from "./Icons"; import Options from "./Options"; import SearchInput from "./SearchInput"; import SelectProvider from "./SelectProvider"; import Spinner from "./Spinner"; import { Option, Options as ListOption, SelectProps } from "./type"; const Select: React.FC = ({ options = [], value = null, onChange, onSearchInputChange, placeholder = "Select...", searchInputPlaceholder = "Search...", isMultiple = false, isClearable = false, isSearchable = false, isDisabled = false, loading = false, menuIsOpen = false, noOptionsMessage = "No options found", primaryColor = DEFAULT_THEME, formatGroupLabel = null, formatOptionLabel = null, classNames }) => { const [open, setOpen] = useState(menuIsOpen); const [list, setList] = useState(options); const [inputValue, setInputValue] = useState(""); const ref = useRef(null); const searchBoxRef = useRef(null); useEffect(() => { const formatItem = (item: Option) => { if ("disabled" in item) return item; return { ...item, disabled: false }; }; setList( options.map(item => { if ("options" in item) { return { label: item.label, options: item.options.map(formatItem) }; } else { return formatItem(item); } }) ); }, [options]); useEffect(() => { if (isSearchable) { if (open) { searchBoxRef.current?.select(); } else { setInputValue(""); } } }, [open, isSearchable]); const toggle = useCallback(() => { if (!isDisabled) { setOpen(!open); } }, [isDisabled, open]); const closeDropDown = useCallback(() => { if (open) setOpen(false); }, [open]); useOnClickOutside(ref, () => { closeDropDown(); }); const onPressEnterOrSpace = useCallback( (e: React.KeyboardEvent) => { e.preventDefault(); if ((e.code === "Enter" || e.code === "Space") && !isDisabled) { toggle(); } }, [isDisabled, toggle] ); const handleValueChange = useCallback( (selected: Option) => { function update() { if (!isMultiple && !Array.isArray(value)) { closeDropDown(); onChange(selected); } if (isMultiple && (Array.isArray(value) || value === null)) { onChange(value === null ? [selected] : [...value, selected]); } } if (selected !== value) { update(); } }, [closeDropDown, isMultiple, onChange, value] ); const clearValue = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); onChange(null); }, [onChange] ); const removeItem = useCallback( (e: React.MouseEvent, item: Option) => { if (isMultiple && Array.isArray(value) && value.length) { e.stopPropagation(); const result = value.filter(current => item.value !== current.value); onChange(result.length ? result : null); } }, [isMultiple, onChange, value] ); const getSelectClass = useCallback(() => { let ringColor = THEME_DATA.ring[DEFAULT_THEME]; if (COLORS.includes(primaryColor)) { ringColor = THEME_DATA.ring[primaryColor as keyof typeof THEME_DATA.ring]; } let borderFocus = THEME_DATA.borderFocus[DEFAULT_THEME]; if (COLORS.includes(primaryColor)) { borderFocus = THEME_DATA.borderFocus[primaryColor as keyof typeof THEME_DATA.borderFocus]; } const baseClass = "flex text-sm text-gray-500 border border-gray-300 rounded shadow-sm transition-all duration-300 focus:outline-none"; const defaultClass = `${baseClass} ${ isDisabled ? "bg-gray-200" : `bg-white hover:border-gray-400 ${borderFocus} focus:ring ${ringColor}` }`; return classNames && classNames.menuButton ? classNames.menuButton({ isDisabled }) : defaultClass; }, [classNames, isDisabled, primaryColor]); const getTagItemClass = useCallback( (item: Option) => { const baseClasse = "bg-gray-200 border rounded-sm flex space-x-1"; const disabledClass = isDisabled ? "border-gray-500 px-1" : "pl-1"; return classNames?.tagItem ? classNames.tagItem({ item, isDisabled }) : `${baseClasse} ${disabledClass}`; }, [classNames, isDisabled] ); return (
{!isMultiple ? (

{value && !Array.isArray(value) ? value.label : placeholder}

) : ( <> {value === null && placeholder} {Array.isArray(value) && value.map((item, index) => (

{item.label}

{!isDisabled && (
removeItem(e, item)} className={ classNames?.tagItemIconContainer ? classNames.tagItemIconContainer : "flex items-center px-1 cursor-pointer rounded-r-sm hover:bg-red-200 hover:text-red-600" } >
)}
))} )}
{loading && (
)} {isClearable && !isDisabled && value !== null && (
)}
{open && !isDisabled && (
{isSearchable && ( { if ( onSearchInputChange && typeof onSearchInputChange === "function" ) onSearchInputChange(e); setInputValue(e.target.value); }} /> )}
)}
); }; export default Select; ================================================ FILE: src/components/SelectProvider.tsx ================================================ import React, { createContext, useContext, useMemo } from "react"; import { ClassNames, GroupOption, Option } from "./type"; interface Store { value: Option | Option[] | null; handleValueChange: (selected: Option) => void; formatGroupLabel: ((data: GroupOption) => JSX.Element) | null; formatOptionLabel: ((data: Option) => JSX.Element) | null; classNames?: ClassNames; } interface Props { value: Option | Option[] | null; handleValueChange: (selected: Option) => void; children: JSX.Element; otherData: { formatGroupLabel: ((data: GroupOption) => JSX.Element) | null; formatOptionLabel: ((data: Option) => JSX.Element) | null; classNames?: ClassNames; }; } export const SelectContext = createContext({ value: null, handleValueChange: selected => { console.log("selected:", selected); }, formatGroupLabel: null, formatOptionLabel: null, classNames: undefined }); export const useSelectContext = (): Store => { return useContext(SelectContext); }; const SelectProvider: React.FC = ({ value, handleValueChange, otherData, children }) => { const store = useMemo(() => { return { value, handleValueChange, formatGroupLabel: otherData && typeof otherData.formatGroupLabel === "function" ? otherData.formatGroupLabel : null, formatOptionLabel: otherData && typeof otherData.formatOptionLabel === "function" ? otherData.formatOptionLabel : null, classNames: otherData?.classNames || undefined }; }, [handleValueChange, otherData, value]); return {children}; }; export default SelectProvider; ================================================ FILE: src/components/Spinner.tsx ================================================ import React, { useMemo } from "react"; import { COLORS, DEFAULT_THEME, THEME_DATA } from "../constants"; interface Props { primaryColor?: string; } const Spinner: React.FC = ({ primaryColor = DEFAULT_THEME }) => { const spinnerColor = useMemo(() => { if (COLORS.includes(primaryColor)) { return THEME_DATA.text[primaryColor as keyof typeof THEME_DATA.text]; } return THEME_DATA.text[DEFAULT_THEME]; }, [primaryColor]); return ( ); }; export default Spinner; ================================================ FILE: src/components/type.ts ================================================ import React from "react"; export interface Option { value: string; label: string; disabled?: boolean; isSelected?: boolean; } export interface GroupOption { label: string; options: Option[]; } export type Options = Array