Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autoScrollonMobile): #19 #27

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 84 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,7 @@ class App extends React.Component {
render() {
const { animal } = this.state;

return (
<Select
value={animal}
onChange={this.handleChange}
options={options}
/>
);
return <Select value={animal} onChange={this.handleChange} options={options} />;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true that it's fewer lines, but I find it's more readable on multiple lines.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops sorry for that, sorry. I guess this was changed because of auto formatting (probably Prettier Extension) in my vscode settings 😅

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 OK, I understand

}
}
```
Expand All @@ -168,13 +162,7 @@ const App = () => {
setAnimal(value);
};

return (
<Select
value={animal}
onChange={handleChange}
options={options}
/>
);
return <Select value={animal} onChange={handleChange} options={options} />;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, readability

};

export default App;
Expand Down Expand Up @@ -209,12 +197,7 @@ const App = () => {
};

return (
<Select
primaryColor={"indigo"}
value={animal}
onChange={handleChange}
options={options}
/>
<Select primaryColor={"indigo"} value={animal} onChange={handleChange} options={options} />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, readability

);
};

Expand All @@ -225,25 +208,27 @@ export default App;

This table shows all the options available in react-tailwindcss-select.

| Option | Type | Default | Description |
|-----------------------------------------------|------------|--------------------|----------------------------------------------------------------------------------------|
| [`classNames`](#classNames) | `Object` | `undefined` | This prop allows you to style most of the components used by this library. |
| `isClearable` | `Boolean` | `true` | Indicates if you can empty the select field. |
| `isDisabled` | `Boolean` | `false` | Indicates if you can disable the select field. |
| `isMultiple` | `Boolean` | `false` | Indicates if you can do a multiple selection. |
| `isSearchable` | `Boolean` | `true` | Indicates if you can search the elements of the select field. |
| [`formatGroupLabel`](#formatGroupLabel) | `Function` | `null` | Allows you to use a custom rendering template for each subgroup title |
| [`formatOptionLabel`](#formatOptionLabel) | `Function` | `null` | Allows you to use a custom rendering template for each option in the list |
| `loading` | `Boolean` | `false` | Indicates if you want a loader to appear in the field. |
| `menuIsOpen` | `Boolean` | `false` | Indicates if you want the options menu to be displayed by default. |
| `noOptionsMessage` | `String` | `No results found` | Default message when there is no option in the select field. |
| [`onChange`](#onChange) | `Function` | | This callback, if present, is triggered when the select field value is modified. |
| [`onSearchInputChange`](#onSearchInputChange) | `Function` | | This callback, if present, is triggered when the search input field value is modified. |
| [`options`](#options) | `Array` | `[]` | All options or options groups available in the selection field. |
| `placeholder` | `String` | `Select...` | The placeholder shown for the select field. |
| `primaryColor` | `String` | `blue` | Default theme of the field. |
| `searchInputPlaceholder` | `String` | `Search...` | The placeholder shown for the search input field. |
| [`value`](#value) | `Object` | `null` | Current value of select field. |
| Option | Type | Default | Description |
| --------------------------------------------- | ---------- | ---------------------------------- | -------------------------------------------------------------------------------------- |
| [`classNames`](#classNames) | `Object` | `undefined` | This prop allows you to style most of the components used by this library. |
| `isClearable` | `Boolean` | `true` | Indicates if you can empty the select field. |
| `isDisabled` | `Boolean` | `false` | Indicates if you can disable the select field. |
| `isMultiple` | `Boolean` | `false` | Indicates if you can do a multiple selection. |
| `isSearchable` | `Boolean` | `true` | Indicates if you can search the elements of the select field. |
| [`formatGroupLabel`](#formatGroupLabel) | `Function` | `null` | Allows you to use a custom rendering template for each subgroup title |
| [`formatOptionLabel`](#formatOptionLabel) | `Function` | `null` | Allows you to use a custom rendering template for each option in the list |
| `loading` | `Boolean` | `false` | Indicates if you want a loader to appear in the field. |
| `menuIsOpen` | `Boolean` | `false` | Indicates if you want the options menu to be displayed by default. |
| `noOptionsMessage` | `String` | `No results found` | Default message when there is no option in the select field. |
| [`onChange`](#onChange) | `Function` | | This callback, if present, is triggered when the select field value is modified. |
| [`onSearchInputChange`](#onSearchInputChange) | `Function` | | This callback, if present, is triggered when the search input field value is modified. |
| [`options`](#options) | `Array` | `[]` | All options or options groups available in the selection field. |
| `placeholder` | `String` | `Select...` | The placeholder shown for the select field. |
| `primaryColor` | `String` | `blue` | Default theme of the field. |
| `searchInputPlaceholder` | `String` | `Search...` | The placeholder shown for the search input field. |
| [`value`](#value) | `Object` | `null` | Current value of select field. |
| [`autoScrollOnMobile`](#autoScrollOnMobile) | `Object` | `{enabled:false, scrollHeight:50}` | Allows you to use a custom options for auto scroll |
| [`noHighLigthLabel`] | `Boolean` | `false` | Indicates if you can disable the label highlight |

### onChange

Expand All @@ -259,8 +244,8 @@ currentValue => {

### onSearchInputChange

This callback, if present, is triggered when the search input field value is modified. This callback takes
as parameter a `React.ChangeEvent<HTMLInputElement>`.
This callback, if present, is triggered when the search input field value is modified. This callback
takes as parameter a `React.ChangeEvent<HTMLInputElement>`.

```js
e => {
Expand Down Expand Up @@ -369,7 +354,7 @@ const App = () => {
<div className={`py-2 text-xs flex items-center justify-between`}>
// 👉 data represents each subgroup
<span className="font-bold">{data.label}</span>
<span className="bg-gray-200 h-5 h-5 p-1.5 flex items-center justify-center rounded-full">
<span className="bg-gray-200 h-5 p-1.5 flex items-center justify-center rounded-full">
{data.options.length}
</span>
</div>
Expand Down Expand Up @@ -416,9 +401,7 @@ const App = () => {
formatOptionLabel={data => (
<li
className={`block transition duration-200 px-2 py-2 cursor-pointer select-none truncate rounded ${
!data.isSelected
? `text-white bg-blue-500`
: `bg-blue-100 text-blue-500`
!data.isSelected ? `text-white bg-blue-500` : `bg-blue-100 text-blue-500`
}`}
>
// data represents each option in the list
Expand All @@ -442,11 +425,14 @@ As of version 1.6.0 of `react-tailwindcss-select` you can now use the `className

> **Info**
>
> 👉 Note: this is not to be confused with the className prop, which will add a class to the component.
> 👉 Note: this is not to be confused with the className prop, which will add a class to the
> component.

`classNames` takes an object with keys to represent the various inner components that `react-tailwindcss-select` is made up of.
`classNames` takes an object with keys to represent the various inner components that
`react-tailwindcss-select` is made up of.

Each key takes a callback function or a string. If a key is not filled in, the default classes of the component will be used.
Each key takes a callback function or a string. If a key is not filled in, the default classes of
the component will be used.

#### All keys

Expand All @@ -456,7 +442,7 @@ interface SelectProps {
classNames?: {
menuButton?: (value?: { isDisabled?: boolean }) => string;
menu?: string;
tagItem?: (value?: { item?: Option, isDisabled?: boolean }) => string;
tagItem?: (value?: { item?: Option; isDisabled?: boolean }) => string;
tagItemText?: string;
tagItemIconContainer?: string;
tagItemIcon?: string;
Expand Down Expand Up @@ -487,34 +473,32 @@ const options = [
];

const App = () => {
const[animal, setAnimal] =useState(null);
const [animal, setAnimal] = useState(null);

const handleChange = value => {
console.log("value:", value);
setAnimal(value);
};

return(
return (
<Select
value={animal}
onChange={handleChange}
options={options}
classNames={{
menuButton: ({ isDisabled }) => (
menuButton: ({ isDisabled }) =>
`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 }) => (
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`
}`
)
}}
/>
);
Expand All @@ -523,6 +507,48 @@ const App = () => {
export default App;
```

### autoScrollOnMobile

Scroll automatically on mobile devices `< 640px` when user press `select components`

> **Info**
>
> 👉 Trigger conditions :
> `ref.current.offsetWidth < 640 && ref.current?.offsetTop / 2 >= window.scrollY`

#### 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 (
<Select
value={animal}
onChange={handleChange}
options={options}
// 👉 default : {{ enabled: false, scrollHeight: 50}}
autoScrollOnMobile={{ enabled: true, scrollHeight: 100 }}
/>
);
};

export default App;
```

## PlayGround

Clone the `master` branch and run commands:
Expand All @@ -542,7 +568,8 @@ Open a browser and navigate to `http://localhost:8888`

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)
Don't forget to see
[CONTRIBUTING.md](https://github.com/onesine/react-tailwindcss-select/blob/master/CONTRIBUTING.md)

## Thanks

Expand Down
15 changes: 9 additions & 6 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Head from "next/head";
import Select from "../src";
import Header from "../components/Header";
import Button from "../components/Button";
import SelectContainer from "../components/SelectContainer";
import { useCallback, useEffect, useState } from "react";
import TailwindColors from "../components/TailwindColors";
import Checkbox from "../components/Checkbox";

import Alert from "../components/Alert";
import Button from "../components/Button";
import Checkbox from "../components/Checkbox";
import Header from "../components/Header";
import { DarkLink, LightLink } from "../components/Link";
import SelectContainer from "../components/SelectContainer";
import TailwindColors from "../components/TailwindColors";
import Select from "../src";

const MANGAS = [
{
Expand Down Expand Up @@ -239,6 +240,8 @@ const Home = () => {
isClearable={isClearable}
isSearchable={isSearchable}
isMultiple={isMultiple}
// noHighLigthLabel={true}
// autoScrollOnMobile={{ enabled: true, scrollHeight: 100 }}
/*formatGroupLabel={(data) => (
<div className={`py-2 text-xs flex items-center justify-between`}>
<span className="font-bold">{data.label}</span>
Expand Down
17 changes: 15 additions & 2 deletions src/components/GroupItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ import { GroupOption } from "./type";
interface GroupItemProps {
item: GroupOption;
primaryColor: string;
searchInputValue?: string;
noHighLigthLabel: boolean;
}

const GroupItem: React.FC<GroupItemProps> = ({ item, primaryColor }) => {
const GroupItem: React.FC<GroupItemProps> = ({
item,
primaryColor,
searchInputValue,
noHighLigthLabel
}) => {
const { classNames, formatGroupLabel } = useSelectContext();

return (
Expand All @@ -31,7 +38,13 @@ const GroupItem: React.FC<GroupItemProps> = ({ item, primaryColor }) => {
)}

{item.options.map((item, index) => (
<Item primaryColor={primaryColor} key={index} item={item} />
<Item
primaryColor={primaryColor}
key={index}
item={item}
searchInputValue={searchInputValue}
noHighLigthLabel={noHighLigthLabel}
/>
))}
</>
)}
Expand Down
26 changes: 24 additions & 2 deletions src/components/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { Option } from "./type";
interface ItemProps {
item: Option;
primaryColor: string;
searchInputValue?: string;
noHighLigthLabel: boolean;
}

const Item: React.FC<ItemProps> = ({ item, primaryColor }) => {
const Item: React.FC<ItemProps> = ({ item, primaryColor, searchInputValue, noHighLigthLabel }) => {
const { classNames, value, handleValueChange, formatOptionLabel } = useSelectContext();

const isSelected = useMemo(() => {
Expand Down Expand Up @@ -57,6 +59,26 @@ const Item: React.FC<ItemProps> = ({ item, primaryColor }) => {
: `${baseClass} ${selectedClass}`;
}, [bgColor, bgHoverColor, classNames, isSelected, textHoverColor]);

const getLabel = useCallback(() => {
if (!noHighLigthLabel && searchInputValue) {
const start = item.label.toUpperCase().indexOf(searchInputValue.toUpperCase());
const end = start + searchInputValue.length;
return item.label.split("").map((label, idx) => {
if (idx >= start && idx < end) {
return (
<span key={idx} className={`font-bold text-${primaryColor}-500`}>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, you can't use string interpolation like text-${primaryColor}-500 as described in the Tailwind documentation https://tailwindcss.com/docs/content-configuration#dynamic-class-names

const getTextColor = useMemo(() => {
        if (COLORS.includes(primaryColor)) {
            return THEME_DATA.text[primaryColor as keyof typeof THEME_DATA.text];
        }
        return THEME_DATA.text[DEFAULT_THEME];
    }, [primaryColor]);

You can use this code to get the color of the text based on primaryColor.
The code (here) contains other examples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks !! 👍

{label}
</span>
);
} else {
return <span key={idx}>{label}</span>;
}
});
} else {
return <span>{item.label}</span>;
}
}, [item.label, noHighLigthLabel, primaryColor, searchInputValue]);

return (
<>
{formatOptionLabel ? (
Expand All @@ -74,7 +96,7 @@ const Item: React.FC<ItemProps> = ({ item, primaryColor }) => {
onClick={() => handleValueChange(item)}
className={getItemClass()}
>
{item.label}
{getLabel()}
</li>
)}
</>
Expand Down
Loading