├── .github ├── CONTRIBUTIONS.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .streamlit └── config.toml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── README.rst ├── examples └── app.py ├── img ├── example_img.png └── streamlit-app-2021-03-14-03-03-7.gif ├── index.rst ├── meta.yaml ├── requirements.txt ├── setup.py └── streamlit_tags ├── __init__.py ├── frontend ├── .env ├── .prettierrc ├── package.json ├── public │ ├── bootstrap.min.css │ └── index.html ├── src │ ├── index.tsx │ ├── keywords.tsx │ ├── react-app-env.d.ts │ ├── react-autocomplete-hint │ │ ├── IHintOption.ts │ │ ├── index.tsx │ │ └── utils.ts │ ├── react-tag-input-componet │ │ ├── classnames.tsx │ │ ├── index.tsx │ │ └── tag.tsx │ └── styles.css └── tsconfig.json └── meta.yaml /.github/CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gagan3012 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Fixes 2 | 3 | # Proposed Changes 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Python - https://github.com/github/gitignore/blob/master/Python.gitignore 3 | ######################################################################## 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # Distribution / packaging 10 | build/ 11 | dist/ 12 | eggs/ 13 | .eggs/ 14 | *.egg-info/ 15 | *.egg 16 | 17 | # Unit test / coverage reports 18 | .coverage 19 | .coverage\.* 20 | .pytest_cache/ 21 | .mypy_cache/ 22 | test-reports 23 | 24 | # Test fixtures 25 | cffi_bin 26 | 27 | # Pyenv Stuff 28 | .python-version 29 | 30 | ######################################################################## 31 | # OSX - https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 32 | ######################################################################## 33 | .DS_Store 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | ######################################################################## 43 | # node - https://github.com/github/gitignore/blob/master/Node.gitignore 44 | ######################################################################## 45 | # Logs 46 | npm-debug.log* 47 | yarn-debug.log* 48 | yarn-error.log* 49 | 50 | # Dependency directories 51 | node_modules/ 52 | 53 | # Coverage directory used by tools like istanbul 54 | coverage/ 55 | 56 | # Lockfiles 57 | yarn.lock 58 | package-lock.json 59 | 60 | ######################################################################## 61 | # JetBrains 62 | ######################################################################## 63 | .idea 64 | 65 | ######################################################################## 66 | # VSCode 67 | ######################################################################## 68 | .vscode/ 69 | -------------------------------------------------------------------------------- /.streamlit/config.toml: -------------------------------------------------------------------------------- 1 | [theme] 2 | primaryColor="#2c8a13" 3 | backgroundColor="#0e1117" 4 | secondaryBackgroundColor="#31333F" 5 | textColor="#fafafa" 6 | font="sans serif" 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | @gagan3012. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include streamlit_tags/frontend/build * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Streamlit-tags 2 | [![pypi Version](https://img.shields.io/pypi/v/streamlit-tags.svg?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/streamlit-tags/) 3 | [![conda Version](https://img.shields.io/conda/vn/conda-forge/streamlit_tags.svg?style=flat-square&logo=conda-forge&logoColor=white)](https://anaconda.org/conda-forge/streamlit_tags) 4 | [![Downloads](https://static.pepy.tech/personalized-badge/streamlit-tags?period=total&units=none&left_color=grey&right_color=orange&left_text=Pip%20Downloads)](https://pepy.tech/project/streamlit-tags) 5 | [![Conda downloads](https://img.shields.io/conda/dn/conda-forge/streamlit_tags?label=conda%20downloads)](https://anaconda.org/gagan3012/streamlit-tags) 6 | [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py) 7 | [![Documentation Status](https://readthedocs.org/projects/streamlit-tags/badge/?version=latest)](https://streamlit-tags.readthedocs.io/en/latest/) 8 | 9 | 10 | ![streamlit-tags](https://socialify.git.ci/gagan3012/streamlit-tags/image?descriptionEditable=Tags%20in%20Streamlit&language=1&logo=https%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F1366779897423810562%2Fkn7ucNPv.png&owner=1&stargazers=1&theme=Light) 11 | 12 | A custom component to add Tags in Streamlit. 13 | 14 | [![gif](https://user-images.githubusercontent.com/49101362/114277814-83cb1200-9a35-11eb-8761-9d8bb81ffadc.gif)](https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py) 15 | 16 | # Please Upgrade to 1.2.7 version 17 | 18 | ### 📢 Favour: 19 | It would be highly motivating, if you can STAR⭐ this repo if you find it helpful. 20 | 21 | 22 | Try out a demo here: [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py) 23 | 24 | Check out docs here: https://streamlit-tags.readthedocs.io/en/latest/ 25 | ## Install 26 | ### PyPi 27 | ``` 28 | pip install streamlit-tags 29 | ``` 30 | The installation can also be found on [**PyPi**](https://pypi.org/project/streamlit-tags/) 31 | ### Anaconda 32 | ``` 33 | conda install -c conda-forge streamlit_tags 34 | ``` 35 | The installation can also be found on [**Anaconda**](https://anaconda.org/conda-forge/streamlit_tags) 36 | ## Usage 37 | This library has two main functions to display and use tags: 38 | - `st_tags` to display the tags feature 39 | - `st_tags_sidebar` to display the tags in the sidebar 40 | Check the [`examples/`](https://github.com/gagan3012/streamlit-tags/tree/master/examples) folder of the project a quick start. 41 | Check out demo here: https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py 42 | ## Definition 43 | ```python 44 | def st_tags(value: list, 45 | suggestions: list, 46 | label: str, 47 | text: str, 48 | maxtags: int, 49 | key=None) -> list: 50 | ''' 51 | :param maxtags: Maximum number of tags allowed maxtags = -1 for unlimited entries 52 | :param suggestions: (List) List of possible suggestions (optional) 53 | :param label: (Str) Label of the Function 54 | :param text: (Str) Instructions for entry 55 | :param value: (List) Initial Value (optional) 56 | :param key: (Str) 57 | An optional string to use as the unique key for the widget. 58 | Assign a key so the component is not remount every time the script is rerun. 59 | :return: (List) Tags 60 | 61 | Note: usage also supports keywords = st_tags() 62 | ''' 63 | ``` 64 | Note: the suggestion and value fields are optional 65 | #### Note: 66 | - The suggestion and value fields are optional 67 | - Usage also supports `keywords = st_tags()` 68 | - Upgrade to 1.1.9 for being able to control number of tags 69 | 70 | ### We also have a function now to embed the tags function to the sidebar: 71 | 72 | ```python 73 | def st_tags_sidebar(value: list, 74 | suggestions: list, 75 | label: str, 76 | text: str, 77 | maxtags: int, 78 | key=None) -> list: 79 | ''' 80 | :param maxtags: Maximum number of tags allowed maxtags = -1 for unlimited entries 81 | :param suggestions: (List) List of possible suggestions (optional) 82 | :param label: (Str) Label of the Function 83 | :param text: (Str) Instructions for entry 84 | :param value: (List) Initial Value (optional) 85 | :param key: (Str) 86 | An optional string to use as the unique key for the widget. 87 | Assign a key so the component is not remount every time the script is rerun. 88 | :return: Tags 89 | ''' 90 | ``` 91 | #### Note: 92 | - The suggestion and value fields are optional 93 | - Usage also supports `keywords = st_tags_sidebar()` 94 | - Upgrade to 1.1.9 for being able to control number of tags 95 | 96 | ## Example Usage 97 | ```python 98 | keywords = st_tags( 99 | label='# Enter Keywords:', 100 | text='Press enter to add more', 101 | value=['Zero', 'One', 'Two'], 102 | suggestions=['five', 'six', 'seven', 103 | 'eight', 'nine', 'three', 104 | 'eleven', 'ten', 'four'], 105 | maxtags = 4, 106 | key='1') 107 | 108 | keyword = st_tags_sidebar( 109 | label='# Enter Keywords:', 110 | text='Press enter to add more', 111 | value=['Zero', 'One', 'Two'], 112 | suggestions=['five', 'six', 'seven', 113 | 'eight', 'nine', 'three', 114 | 'eleven', 'ten', 'four'], 115 | maxtags = 4, 116 | key='2') 117 | ``` 118 | ## Sample Images of the UI: 119 | [![UI](https://user-images.githubusercontent.com/49101362/113942909-59494100-980a-11eb-8f4c-662f5c18d967.png)](https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py) 120 | 121 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. role:: raw-html-m2r(raw) 2 | :format: html 3 | 4 | 5 | Streamlit-tags 6 | ============== 7 | 8 | |pypi Version| |conda Version| |PyPi downloads| |Conda downloads| 9 | 10 | A custom component to add Tags in Streamlit. 11 | 12 | .. image:: https://user-images.githubusercontent.com/49101362/114277814-83cb1200-9a35-11eb-8761-9d8bb81ffadc.gif 13 | :alt: ezgif com-gif-maker (1) 14 | 15 | 16 | Please star⭐ the repo and share the usage if you liked it. 17 | 18 | Try out a demo here: |Streamlit App| 19 | 20 | Check out docs here: https://streamlit-tags.readthedocs.io/en/latest/ 21 | 22 | Install 23 | ------- 24 | 25 | PyPi 26 | 27 | .. code-block:: 28 | 29 | 30 | :: 31 | 32 | pip install streamlit-tags 33 | 34 | The installation can also be found on `PyPi`_ 35 | 36 | Anaconda 37 | ~~~~ 38 | 39 | 40 | 41 | conda install -c gagan3012 streamlit-tags 42 | 43 | The installation can also be found on `Anaconda`_ 44 | 45 | Usage 46 | ----- 47 | 48 | This library has two main functions to display and use tags: 49 | 50 | 51 | * ``st_tags`` to display the tags feature 52 | * ``st_tags_sidebar`` to display the tags in the sidebar 53 | 54 | Check the ``examples/``\ _ folder of the project a quick start. 55 | 56 | Check out demo here: 57 | https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py 58 | 59 | Definition 60 | ---------- 61 | 62 | .. code:: python 63 | 64 | def st_tags(label: str, 65 | text: str, 66 | value: list, 67 | suggestions: list, 68 | key=None) -> list: 69 | ''' 70 | 71 | :param suggestions: (List) List of possible suggestions (optional) 72 | :param label: (Str) Label of the Function 73 | :param text: (Str) Instructions for entry 74 | :param value: (List) Initial Value (optional) 75 | :param key: (Str) 76 | An optional string to use as the unique key for the widget. 77 | Assign a key so the component is not remount every time the script is rerun. 78 | :return: (List) Tags 79 | 80 | Note: usage also supports keywords = st_tags() 81 | 82 | ''' 83 | 84 | 85 | Note: 86 | ^^^^^ 87 | 88 | 89 | * The suggestion and value fields are optional 90 | * Usage also supports ``keywords = st_tags()`` 91 | 92 | We also have a function now to embed the tags function to the sidebar: 93 | :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~` 94 | 95 | .. _PyPi: https://pypi.org/project/streamlit-tags/ 96 | 97 | .. _Anaconda: https://anaconda.org/gagan3012/streamlit-tags 98 | 99 | .. _``examples/``: https://github.com/gagan3012/streamlit-tags/tree/master/examples 100 | 101 | 102 | .. |pypi Version| image:: https://img.shields.io/pypi/v/streamlit-tags.svg?style=flat-square&logo=pypi&logoColor=white 103 | :target: https://pypi.org/project/streamlit-tags/ 104 | 105 | .. |conda Version| image:: https://img.shields.io/conda/vn/gagan3012/streamlit-tags.svg?style=flat-square&logo=conda-forge&logoColor=white 106 | :target: https://anaconda.org/gagan3012/streamlit-tags 107 | 108 | .. |PyPi downloads| image:: https://static.pepy.tech/personalized-badge/streamlit-tags?period=total&units=international_system&left_color=grey&right_color=orange&left_text=pip%20downloads 109 | :target: https://pypi.org/project/streamlit-tags/ 110 | 111 | .. |Conda downloads| image:: https://img.shields.io/conda/dn/gagan3012/streamlit-tags?label=conda%20downloads 112 | :target: https://anaconda.orggagan3012/streamlit-tags 113 | 114 | .. |Streamlit App| image:: https://static.streamlit.io/badges/streamlit_badge_black_white.svg 115 | 116 | :target: https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py 117 | -------------------------------------------------------------------------------- /examples/app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_tags import st_tags, st_tags_sidebar 3 | 4 | st.write("# Code for streamlit tags") 5 | 6 | st.code(body='''keywords = st_tags( 7 | label='# Enter Keywords:', 8 | text='Press enter to add more', 9 | value=['Zero', 'One', 'Two'], 10 | suggestions=['five', 'six', 'seven', 'eight', 'nine', 'three', 'eleven', 'ten', 'four'], 11 | maxtags = 4, 12 | key='1')''', 13 | language="python") 14 | 15 | maxtags = st.slider('Number of tags allowed?', 1, 10, 3, key='jfnkerrnfvikwqejn') 16 | 17 | keywords = st_tags( 18 | label='Enter Keywords:', 19 | text='Press enter to add more', 20 | value=['Zero', 'One', 'Two'], 21 | suggestions=['five', 'six', 'seven', 'eight', 'nine', 'three', 'eleven', 'ten', 'four'], 22 | maxtags=maxtags, 23 | key="aljnf") 24 | 25 | st.write("### Results:") 26 | st.write(type(keywords)) 27 | 28 | st.sidebar.write("# Code for streamlit tags sidebar") 29 | 30 | st.sidebar.code(body='''keyword = st_tags_sidebar( 31 | label='# Enter Keywords:', 32 | text='Press enter to add more', 33 | value=['Zero', 'One', 'Two'], 34 | suggestions=['five', 'six', 'seven', 35 | 'eight', 'nine', 'three', 36 | 'eleven', 'ten', 'four'], 37 | maxtags = 4)''', 38 | language="python") 39 | 40 | maxtags_sidebar = st.sidebar.slider('Number of tags allowed?', 1, 10, 3, key='ehikwegrjifbwreuk') 41 | 42 | 43 | keyword = st_tags_sidebar(label='# Enter Keywords:', 44 | text='Press enter to add more', 45 | value=['Zero', 'One', 'Two'], 46 | suggestions=['five', 'six', 'seven', 'eight', 'nine', 'three', 'eleven', 'ten', 'four'], 47 | maxtags=maxtags_sidebar, 48 | key="afrfae") 49 | 50 | st.sidebar.write("### Results:") 51 | st.sidebar.write((keyword)) 52 | -------------------------------------------------------------------------------- /img/example_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gagan3012/streamlit-tags/981ae4de6e73691c07124cb80b7be00a01ad9590/img/example_img.png -------------------------------------------------------------------------------- /img/streamlit-app-2021-03-14-03-03-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gagan3012/streamlit-tags/981ae4de6e73691c07124cb80b7be00a01ad9590/img/streamlit-app-2021-03-14-03-03-7.gif -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | .. role:: raw-html-m2r(raw) 2 | :format: html 3 | 4 | 5 | Streamlit-tags 6 | ============== 7 | 8 | |pypi Version| |conda Version| |PyPi downloads| |Conda downloads| 9 | 10 | A custom component to add Tags in Streamlit. 11 | 12 | .. image:: https://user-images.githubusercontent.com/49101362/114277814-83cb1200-9a35-11eb-8761-9d8bb81ffadc.gif 13 | :alt: ezgif com-gif-maker (1) 14 | 15 | 16 | Please star⭐ the repo and share the usage if you liked it. 17 | 18 | Try out a demo here: |Streamlit App| 19 | 20 | Check out docs here: https://streamlit-tags.readthedocs.io/en/latest/ 21 | 22 | Install 23 | ------- 24 | 25 | PyPi 26 | 27 | .. code-block:: 28 | 29 | 30 | :: 31 | 32 | pip install streamlit-tags 33 | 34 | The installation can also be found on `PyPi`_ 35 | 36 | Anaconda 37 | ~~~~ 38 | 39 | 40 | 41 | conda install -c gagan3012 streamlit-tags 42 | 43 | The installation can also be found on `Anaconda`_ 44 | 45 | Usage 46 | ----- 47 | 48 | This library has two main functions to display and use tags: 49 | 50 | 51 | * ``st_tags`` to display the tags feature 52 | * ``st_tags_sidebar`` to display the tags in the sidebar 53 | 54 | Check the ``examples/``\ _ folder of the project a quick start. 55 | 56 | Check out demo here: 57 | https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py 58 | 59 | Definition 60 | ---------- 61 | 62 | .. code:: python 63 | 64 | def st_tags(label: str, 65 | text: str, 66 | value: list, 67 | suggestions: list, 68 | key=None) -> list: 69 | ''' 70 | 71 | :param suggestions: (List) List of possible suggestions (optional) 72 | :param label: (Str) Label of the Function 73 | :param text: (Str) Instructions for entry 74 | :param value: (List) Initial Value (optional) 75 | :param key: (Str) 76 | An optional string to use as the unique key for the widget. 77 | Assign a key so the component is not remount every time the script is rerun. 78 | :return: (List) Tags 79 | 80 | Note: usage also supports keywords = st_tags() 81 | 82 | ''' 83 | 84 | 85 | Note: 86 | ^^^^^ 87 | 88 | 89 | * The suggestion and value fields are optional 90 | * Usage also supports ``keywords = st_tags()`` 91 | 92 | We also have a function now to embed the tags function to the sidebar: 93 | :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~`\ :raw-html-m2r:`~` 94 | 95 | .. _PyPi: https://pypi.org/project/streamlit-tags/ 96 | 97 | .. _Anaconda: https://anaconda.org/gagan3012/streamlit-tags 98 | 99 | .. _``examples/``: https://github.com/gagan3012/streamlit-tags/tree/master/examples 100 | 101 | 102 | .. |pypi Version| image:: https://img.shields.io/pypi/v/streamlit-tags.svg?style=flat-square&logo=pypi&logoColor=white 103 | :target: https://pypi.org/project/streamlit-tags/ 104 | 105 | .. |conda Version| image:: https://img.shields.io/conda/vn/gagan3012/streamlit-tags.svg?style=flat-square&logo=conda-forge&logoColor=white 106 | :target: https://anaconda.org/gagan3012/streamlit-tags 107 | 108 | .. |PyPi downloads| image:: https://static.pepy.tech/personalized-badge/streamlit-tags?period=total&units=international_system&left_color=grey&right_color=orange&left_text=pip%20downloads 109 | :target: https://pypi.org/project/streamlit-tags/ 110 | 111 | .. |Conda downloads| image:: https://img.shields.io/conda/dn/gagan3012/streamlit-tags?label=conda%20downloads 112 | :target: https://anaconda.orggagan3012/streamlit-tags 113 | 114 | .. |Streamlit App| image:: https://static.streamlit.io/badges/streamlit_badge_black_white.svg 115 | 116 | :target: https://share.streamlit.io/gagan3012/streamlit-tags/examples/app.py 117 | -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "streamlit_tags" %} 2 | {% set version = "1.2.6" %} 3 | 4 | 5 | package: 6 | name: {{ name|lower }} 7 | version: {{ version }} 8 | 9 | source: 10 | url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/streamlit_tags-{{ version }}.tar.gz 11 | sha256: fe7ccd67dc6187feb09d830d170c6eedd7a765b9dfd5e037a83dc39e8eaeef03 12 | 13 | build: 14 | number: 0 15 | noarch: python 16 | script: {{ PYTHON }} -m pip install . -vv 17 | 18 | requirements: 19 | host: 20 | - pip 21 | - python >=3.6 22 | run: 23 | - python >=3.6 24 | - streamlit >=0.63 25 | 26 | test: 27 | imports: 28 | - streamlit_tags 29 | commands: 30 | - pip check 31 | requires: 32 | - pip 33 | 34 | about: 35 | home: https://github.com/gagan3012/streamlit-tags 36 | summary: Tags custom component for Streamlit 37 | license: MIT 38 | license_file: LICENSE.txt 39 | 40 | extra: 41 | recipe-maintainers: 42 | - gagan3012 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit==0.82.0 2 | streamlit_tags 3 | pyarrow 4 | 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | from os.path import join 3 | import setuptools 4 | 5 | 6 | def readme() -> str: 7 | """Utility function to read the README file. 8 | Used for the long_description. It's nice, because now 1) we have a top 9 | level README file and 2) it's easier to type in the README file than to put 10 | a raw string in below. 11 | :return: content of README.md 12 | """ 13 | return open(join(dirname(__file__), "README.md"), encoding="utf8").read() 14 | 15 | 16 | setuptools.setup( 17 | name="streamlit_tags", 18 | version="1.2.8", 19 | author="Gagan Bhatia", 20 | license='MIT', 21 | license_files=('LICENSE.txt',), 22 | author_email="gbhatia880@gmail.com", 23 | description="Tags custom component for Streamlit", 24 | long_description=readme(), 25 | long_description_content_type="text/markdown", 26 | url="https://github.com/gagan3012/streamlit-tags", 27 | packages=setuptools.find_packages(), 28 | include_package_data=True, 29 | classifiers=[], 30 | python_requires=">=3.6", 31 | install_requires=[ 32 | "streamlit >= 0.63", 33 | ] 34 | ) -------------------------------------------------------------------------------- /streamlit_tags/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import streamlit.components.v1 as components 4 | import streamlit as st 5 | import pyarrow 6 | 7 | # Create a _RELEASE constant. We'll set this to False while we're developing 8 | # the component, and True when we're ready to package and distribute it. 9 | # (This is, of course, optional - there are innumerable ways to manage your 10 | # release process.) 11 | _RELEASE = True 12 | 13 | # Declare a Streamlit component. `declare_component` returns a function 14 | # that is used to create instances of the component. We're naming this 15 | # function "_component_func", with an underscore prefix, because we don't want 16 | # to expose it directly to users. Instead, we will create a custom wrapper 17 | # function, below, that will serve as our component's public API. 18 | 19 | # It's worth noting that this call to `declare_component` is the 20 | # *only thing* you need to do to create the binding between Streamlit and 21 | # your component frontend. Everything else we do in this file is simply a 22 | # best practice. 23 | 24 | if not _RELEASE: 25 | _component_func = components.declare_component( 26 | # We give the component a simple, descriptive name ("my_component" 27 | # does not fit this bill, so please choose something better for your 28 | # own component :) 29 | "streamlit_tags", 30 | # Pass `url` here to tell Streamlit that the component will be served 31 | # by the local dev server that you run via `npm run start`. 32 | # (This is useful while your component is in development.) 33 | url="http://localhost:3001", 34 | ) 35 | else: 36 | # When we're distributing a production version of the component, we'll 37 | # replace the `url` param with `path`, and point it to to the component's 38 | # build directory: 39 | parent_dir = os.path.dirname(os.path.abspath(__file__)) 40 | build_dir = os.path.join(parent_dir, "frontend/build") 41 | _component_func = components.declare_component("streamlit_tags", path=build_dir) 42 | 43 | 44 | # Create a wrapper function for the component. This is an optional 45 | # best practice - we could simply expose the component function returned by 46 | # `declare_component` and call it done. The wrapper allows us to customize 47 | # our component's API: we can pre-process its input args, post-process its 48 | # output value, and add a docstring for users. 49 | def st_tags(value: list = [], 50 | suggestions: list = [], 51 | label: str = "# Enter Keywords", 52 | text: str = "Press enter to add more", 53 | maxtags: int = -1, 54 | key=None) -> list: 55 | ''' 56 | 57 | :param maxtags: Maximum number of tags allowed maxtags = -1 for unlimited entries 58 | :param suggestions: (List) List of possible suggestions 59 | :param label: (Str) Label of the Function 60 | :param text: (Str) Instructions for entry 61 | :param value: (List) Initial Value 62 | :param key: (Str) 63 | An optional string to use as the unique key for the widget. 64 | Assign a key so the component is not remount every time the script is rerun. 65 | :return: Tags 66 | ''' 67 | import streamlit as st 68 | 69 | st.write(label) 70 | component_value = _component_func(label=label, 71 | text=text, 72 | initialValue=value, 73 | suggestions=suggestions, 74 | maxTags=maxtags, 75 | key=key, 76 | default=value) 77 | return component_value 78 | 79 | 80 | def st_tags_sidebar(value: list = [], 81 | suggestions: list = [], 82 | label: str = "# Enter Keywords", 83 | text: str = "Press enter to add more", 84 | maxtags: int = -1, 85 | key=None) -> list: 86 | ''' 87 | 88 | :param maxtags: Maximum number of tags allowed maxtags = -1 for unlimited entries 89 | :param suggestions: (List) List of possible suggestions 90 | :param label: (Str) Label of the Function 91 | :param text: (Str) Instructions for entry 92 | :param value: (List) Initial Value 93 | :param key: (Str) 94 | An optional string to use as the unique key for the widget. 95 | Assign a key so the component is not remount every time the script is rerun. 96 | :return: Tags 97 | ''' 98 | import streamlit as st 99 | 100 | with st.sidebar: 101 | st.sidebar.write(label) 102 | component_value = _component_func(label=label, 103 | text=text, 104 | initialValue=value, 105 | suggestions=suggestions, 106 | maxTags=maxtags, 107 | key=key, 108 | default=value) 109 | return component_value 110 | 111 | 112 | # Add some test code to play with the component while it's in development. 113 | # During development, we can run this just as we would any other Streamlit 114 | # app: `$ streamlit run my_component/__init__.py` 115 | if not _RELEASE: 116 | import streamlit as st 117 | 118 | # Create a second instance of our component whose `name` arg will vary 119 | # based on a text_input widget. 120 | # 121 | # We use the special "key" argument to assign a fixed identity to this 122 | # component instance. By default, when a component's arguments change, 123 | # it is considered a new instance and will be re-mounted on the frontend 124 | # and lose its current state. In this case, we want to vary the component's 125 | # "name" argument without having it get recreated. 126 | 127 | keyword = st_tags(label='# Enter Keywords:', 128 | text='Press enter to add more', 129 | value=['Zero', 'One', 'Two'], 130 | suggestions=['five', 'six', 'seven', 'eight', 'nine', 'three', 'eleven', 'ten', 'four'], 131 | maxtags=4, 132 | key='2') 133 | 134 | st.sidebar.write("### Results:") 135 | st.sidebar.write(keyword) 136 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/.env: -------------------------------------------------------------------------------- 1 | # Run the component's dev server on :3001 2 | # (The Streamlit dev server already runs on :3000) 3 | PORT=3001 4 | 5 | # Don't automatically open the web browser on `npm run start`. 6 | BROWSER=none 7 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streamlit_tags", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.13.1", 7 | "react-dom": "^16.13.1", 8 | "streamlit-component-lib": "^2.0.0", 9 | "@celebryts/react-autocomplete-tags": "^1.0.0", 10 | "@material-ui/core": "^4.11.3", 11 | "@testing-library/jest-dom": "^4.2.4", 12 | "@testing-library/react": "^9.3.2", 13 | "@testing-library/user-event": "^7.1.2", 14 | "@types/jest": "^24.0.0", 15 | "@types/node": "^12.0.0", 16 | "@types/react": "^16.9.0", 17 | "@types/react-dom": "^16.9.0", 18 | "@types/react-tag-autocomplete": "^6.1.0", 19 | "@types/styletron-engine-atomic": "^1.1.0", 20 | "@types/styletron-react": "^5.0.2", 21 | "autocomplete-ts": "^1.1.1", 22 | "baseui": "^9.111.2", 23 | "goober": "^2.0.37", 24 | "install": "^0.13.0", 25 | "npm": "^7.6.3", 26 | "react-scripts": "^5.0.1", 27 | "styletron-engine-atomic": "^1.4.7", 28 | "styletron-react": "^6.0.1", 29 | "typescript": "~3.7.2" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build", 34 | "test": "react-scripts test", 35 | "eject": "react-scripts eject" 36 | }, 37 | "eslintConfig": { 38 | "extends": "react-app" 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | }, 52 | "homepage": "." 53 | } 54 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Streamlit Component 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import CustomKeywords from "./keywords" 4 | 5 | // Lots of import to define a Styletron engine and load the light theme of baseui 6 | // @ts-ignore 7 | import { Client as Styletron } from "styletron-engine-atomic" 8 | import { Provider as StyletronProvider } from "styletron-react" 9 | import { ThemeProvider, LightTheme } from "baseui" 10 | 11 | const engine = new Styletron() 12 | 13 | // Wrap your CustomSlider with the baseui them 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | 20 | 21 | , 22 | document.getElementById("root") 23 | ) 24 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/keywords.tsx: -------------------------------------------------------------------------------- 1 | import React,{ useEffect, useState } from "react" 2 | import { ComponentProps, Streamlit, withStreamlitConnection } from "streamlit-component-lib" 3 | import { TagsInput } from "./react-tag-input-componet"; 4 | import "./styles.css"; 5 | 6 | interface PythonArgs { 7 | label: string 8 | text: string 9 | initialValue: string[] 10 | suggestions: string[] 11 | maxTags: number 12 | } 13 | 14 | const CustomKeywords = (props: ComponentProps) => { 15 | // Destructure using Typescript interface 16 | // This ensures typing validation for received props from Python 17 | let { label, text, initialValue, suggestions, maxTags}: PythonArgs = props.args 18 | const [value, setValue] = useState(initialValue) 19 | 20 | const onSubmit = (values: string[]) => { 21 | setValue(values) 22 | Streamlit.setComponentValue((values)) 23 | } 24 | useEffect(() => Streamlit.setFrameHeight()) 25 | return ( 26 |
27 | onSubmit(value)} 30 | name={label} 31 | placeHolder={text} 32 | suggestions={suggestions} 33 | maxTags={maxTags} 34 | /> 35 |
36 | ) 37 | } 38 | 39 | export default withStreamlitConnection(CustomKeywords) 40 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-autocomplete-hint/IHintOption.ts: -------------------------------------------------------------------------------- 1 | export interface IHintOption { 2 | id: string | number; 3 | label: string; 4 | } -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-autocomplete-hint/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useState, 3 | cloneElement, 4 | useEffect, 5 | useRef, 6 | ReactElement 7 | } from 'react'; 8 | import { IHintOption } from './IHintOption'; 9 | import { 10 | mergeRefs, 11 | interpolateStyle, 12 | sortAsc, 13 | getFirstDuplicateOption 14 | } from './utils'; 15 | 16 | export interface IHintProps { 17 | options: Array | Array; 18 | disableHint?: boolean; 19 | children: ReactElement; 20 | allowTabFill?: boolean; 21 | onFill?(value: string | IHintOption): void; 22 | onHint?(value: string | IHintOption | undefined): void; 23 | valueModifier?(value: string): string; 24 | } 25 | 26 | export const Hint: React.FC = props => { 27 | const child = React.Children.only(props.children); 28 | 29 | if (child.type?.toString()?.toLowerCase() !== 'input') { 30 | throw new TypeError(`react-autocomplete-hint: 'Hint' only accepts an 'input' element as child.`); 31 | } 32 | 33 | const { 34 | options, 35 | disableHint, 36 | allowTabFill, 37 | onFill, 38 | onHint, 39 | valueModifier 40 | } = props; 41 | 42 | const childProps = child.props; 43 | 44 | let inputWrapperRef = useRef(null); 45 | let mainInputRef = useRef(null); 46 | let hintWrapperRef = useRef(null); 47 | let hintRef = useRef(null); 48 | const [unModifiedText, setUnmodifiedText] = useState(''); 49 | const [text, setText] = useState(''); 50 | const [hint, setHint] = useState(''); 51 | const [match, setMatch] = useState(); 52 | const [changeEvent, setChangeEvent] = useState>(); 53 | 54 | useEffect(() => { 55 | if (typeof options[0] === 'object') { 56 | const duplicate = getFirstDuplicateOption(options as Array); 57 | if (duplicate) { 58 | console.warn(`react-autocomplete-hint: "${duplicate}" occurs more than once and may cause errors. Options should not contain duplicate values!`); 59 | } 60 | } 61 | }, []); 62 | 63 | useEffect(() => { 64 | if (disableHint) { 65 | return; 66 | } 67 | 68 | const inputStyle = mainInputRef.current && window.getComputedStyle(mainInputRef.current); 69 | inputStyle && styleHint(inputWrapperRef, hintWrapperRef, hintRef, inputStyle); 70 | }); 71 | 72 | const getMatch = (text: string) => { 73 | if (!text || text === '') { 74 | return; 75 | } 76 | 77 | if (typeof (options[0]) === 'string') { 78 | const match = (options as Array) 79 | .filter(x => x.toLowerCase() !== text.toLowerCase() && x.toLowerCase().startsWith(text.toLowerCase())) 80 | .sort()[0]; 81 | 82 | return match; 83 | } else { 84 | const match = (options as Array) 85 | .filter(x => x.label.toLowerCase() !== text.toLowerCase() && x.label.toLowerCase().startsWith(text.toLowerCase())) 86 | .sort((a, b) => sortAsc(a.label, b.label))[0]; 87 | 88 | return match; 89 | } 90 | }; 91 | 92 | const setHintTextAndId = (text: string) => { 93 | setText(text); 94 | 95 | const match = getMatch(text); 96 | let hint: string; 97 | 98 | if (!match) { 99 | hint = ''; 100 | } 101 | else if (typeof match === 'string') { 102 | hint = match.slice(text.length); 103 | } else { 104 | hint = match.label.slice(text.length); 105 | } 106 | 107 | setHint(hint); 108 | setMatch(match); 109 | onHint && onHint(match) 110 | } 111 | 112 | const handleOnFill = () => { 113 | if (hint !== '' && changeEvent) { 114 | changeEvent.target.value = unModifiedText + hint; 115 | childProps.onChange && childProps.onChange(changeEvent); 116 | setHintTextAndId(''); 117 | 118 | onFill && onFill(match!); 119 | } 120 | }; 121 | 122 | const styleHint = ( 123 | inputWrapperRef: React.RefObject, 124 | hintWrapperRef: React.RefObject, 125 | hintRef: React.RefObject, 126 | inputStyle: CSSStyleDeclaration) => { 127 | if (inputWrapperRef?.current?.style) { 128 | inputWrapperRef.current.style.width = inputStyle.width; 129 | } 130 | 131 | if (hintWrapperRef?.current?.style) { 132 | hintWrapperRef.current.style.fontFamily = inputStyle.fontFamily; 133 | hintWrapperRef.current.style.fontSize = inputStyle.fontSize; 134 | hintWrapperRef.current.style.width = inputStyle.width; 135 | hintWrapperRef.current.style.height = inputStyle.height; 136 | hintWrapperRef.current.style.lineHeight = inputStyle.lineHeight; 137 | hintWrapperRef.current.style.boxSizing = inputStyle.boxSizing; 138 | hintWrapperRef.current.style.margin = interpolateStyle(inputStyle, 'margin'); 139 | hintWrapperRef.current.style.padding = interpolateStyle(inputStyle, 'padding'); 140 | hintWrapperRef.current.style.borderStyle = interpolateStyle(inputStyle, 'border', 'style'); 141 | hintWrapperRef.current.style.borderWidth = interpolateStyle(inputStyle, 'border', 'width'); 142 | } 143 | 144 | if (hintRef?.current?.style) { 145 | hintRef.current.style.fontFamily = inputStyle.fontFamily; 146 | hintRef.current.style.fontSize = inputStyle.fontSize; 147 | hintRef.current.style.lineHeight = inputStyle.lineHeight; 148 | } 149 | }; 150 | 151 | const onChange = (e: React.ChangeEvent) => { 152 | setChangeEvent(e); 153 | e.persist(); 154 | 155 | setUnmodifiedText(e.target.value); 156 | const modifiedValue = valueModifier ? valueModifier(e.target.value) : e.target.value; 157 | setHintTextAndId(modifiedValue); 158 | 159 | childProps.onChange && childProps.onChange(e); 160 | }; 161 | 162 | const onFocus = (e: React.FocusEvent) => { 163 | setHintTextAndId(e.target.value); 164 | childProps.onFocus && childProps.onFocus(e); 165 | }; 166 | 167 | const onBlur = (e: React.FocusEvent) => { 168 | //Only blur it if the new focus isn't the the hint input 169 | if (hintRef?.current !== e.relatedTarget) { 170 | setHintTextAndId(''); 171 | childProps.onBlur && childProps.onBlur(e); 172 | } 173 | }; 174 | 175 | const ARROWRIGHT = 'ArrowRight'; 176 | const TAB = 'Tab'; 177 | const onKeyDown = (e: React.KeyboardEvent) => { 178 | const caretIsAtTextEnd = (() => { 179 | // For selectable input types ("text", "search"), only select the hint if 180 | // it's at the end of the input value. For non-selectable types ("email", 181 | // "number"), always select the hint. 182 | 183 | const isNonSelectableType = e.currentTarget.selectionEnd === null; 184 | const caretIsAtTextEnd = isNonSelectableType || e.currentTarget.selectionEnd === e.currentTarget.value.length; 185 | 186 | return caretIsAtTextEnd; 187 | })(); 188 | 189 | if (caretIsAtTextEnd && e.key === ARROWRIGHT) { 190 | handleOnFill(); 191 | } else if (caretIsAtTextEnd && allowTabFill && e.key === TAB && hint !== '') { 192 | e.preventDefault(); 193 | handleOnFill(); 194 | } 195 | 196 | childProps.onKeyDown && childProps.onKeyDown(e); 197 | }; 198 | 199 | const onHintClick = (e: React.MouseEvent) => { 200 | const hintCaretPosition = e.currentTarget.selectionEnd || 0; 201 | 202 | // If user clicks the position before the first character of the hint, 203 | // move focus to the end of the mainInput text 204 | if (hintCaretPosition === 0) { 205 | mainInputRef.current?.focus(); 206 | return; 207 | } 208 | 209 | if (!!hint && hint !== '') { 210 | handleOnFill(); 211 | setTimeout(() => { 212 | mainInputRef.current?.focus(); 213 | const caretPosition = text.length + hintCaretPosition; 214 | mainInputRef.current?.setSelectionRange(caretPosition, caretPosition); 215 | }, 0); 216 | } 217 | }; 218 | 219 | const childRef = cloneElement(child as any).ref; 220 | const mainInput = cloneElement( 221 | child, 222 | { 223 | ...childProps, 224 | style: { 225 | ...childProps.style, 226 | boxSizing: 'border-box' 227 | }, 228 | onChange, 229 | onBlur, 230 | onFocus, 231 | onKeyDown, 232 | ref: childRef && typeof (childRef) !== 'string' 233 | ? mergeRefs(childRef, mainInputRef) 234 | : mainInputRef 235 | } 236 | ); 237 | 238 | return ( 239 |
244 | { 245 | disableHint 246 | ? child 247 | : ( 248 | <> 249 | {mainInput} 250 | 266 | 274 | {text} 275 | 276 | 295 | 296 | 297 | ) 298 | } 299 |
300 | ); 301 | } -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-autocomplete-hint/utils.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, RefCallback } from "react"; 2 | import { IHintOption } from "./IHintOption"; 3 | 4 | type MutableRef = RefCallback | MutableRefObject | null; 5 | 6 | export function mergeRefs(...refs: Array>) { 7 | const filteredRefs = refs.filter(Boolean); 8 | 9 | return (inst: HTMLElement) => { 10 | for (let ref of filteredRefs) { 11 | if (typeof ref === 'function') { 12 | ref(inst); 13 | } else if (ref) { 14 | ref.current = inst; 15 | } 16 | } 17 | }; 18 | }; 19 | 20 | // IE doesn't seem to get the composite computed value (eg: 'padding', 21 | // 'borderStyle', etc.), so generate these from the individual values. 22 | export function interpolateStyle( 23 | styles: CSSStyleDeclaration, 24 | attr: string, 25 | subattr: string = '' 26 | ): string { 27 | // Title-case the sub-attribute. 28 | if (subattr) { 29 | subattr = subattr.replace(subattr[0], subattr[0].toUpperCase()); 30 | } 31 | 32 | return ['Top', 'Right', 'Bottom', 'Left'] 33 | // @ts-ignore: (attr + dir + subattr) property cannot be determined at compile time 34 | .map((dir) => styles[attr + dir + subattr]) 35 | .join(' '); 36 | } 37 | 38 | export function sortAsc(a: T, b: T) { 39 | if (a > b) { 40 | return 1; 41 | } 42 | if (a < b) { 43 | return -1; 44 | } 45 | return 0; 46 | } 47 | 48 | export function getFirstDuplicateOption(array: Array) { 49 | let tracker: { [key: string]: boolean } = {}; 50 | 51 | for (let i = 0; i < array.length; i++) { 52 | if (tracker[array[i].label]) { 53 | return array[i].label; 54 | } 55 | 56 | tracker[array[i].label] = true; 57 | } 58 | 59 | return null; 60 | } -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-tag-input-componet/classnames.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * A minimal utility to combine classes 3 | * 4 | * @export 5 | * @param {(string[] | string)} obj 6 | * @returns {string} 7 | */ 8 | export default function cc(...obj: (string | number)[]): string { 9 | return obj.join(" "); 10 | } -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-tag-input-componet/index.tsx: -------------------------------------------------------------------------------- 1 | import { css, setup } from "goober"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | import cc from "./classnames"; 5 | import Tag from "./tag"; 6 | import {Hint} from "../react-autocomplete-hint"; 7 | 8 | 9 | 10 | export interface IHintOption { 11 | id: string | number; 12 | label: string; 13 | } 14 | 15 | export interface TagsInputProps { 16 | name?: string; 17 | placeHolder?: string; 18 | value: string[]; 19 | onChange?: (tags: string[]) => void; 20 | suggestions: Array | Array; 21 | onBlur?: any; 22 | separators?: string[]; 23 | onExisting?: (tag: string) => void; 24 | onRemoved?: (tag: string) => void; 25 | maxTags: number; 26 | } 27 | 28 | // initialize goober once 29 | setup(React.createElement); 30 | 31 | const RTIContainer = css({ 32 | "--rtiBg": "#fff", 33 | "--rtiBorder": "#ccc", 34 | "--rtiMain": "var(--primary-color)", 35 | "--rtiRadius": "0.375rem", 36 | "--rtiS": "0.5rem", 37 | "--rtiTag": "#edf2f7", 38 | "--rtiTagRemove": "#e53e3e", 39 | 40 | "*": { 41 | boxSizing: "border-box", 42 | transition: "all 0.2s ease", 43 | }, 44 | 45 | alignItems: "center", 46 | bg: "var(--rtiBg)", 47 | border: "1px solid var(--rtiBorder)", 48 | borderRadius: "var(--rtiRadius)", 49 | display: "flex", 50 | flexWrap: "wrap", 51 | gap: "var(--rtiS)", 52 | lineHeight: 1.4, 53 | padding: "var(--rtiS)", 54 | 55 | "&:focus-within": { 56 | borderColor: "var(--rtiMain)", 57 | boxShadow: "var(--rtiMain) 0px 0px 0px 1px", 58 | }, 59 | }); 60 | 61 | const RTIInput = css({ 62 | border: 0, 63 | outline: 0, 64 | fontSize: "inherit", 65 | lineHeight: "inherit", 66 | width: "200%", 67 | }); 68 | 69 | const defaultSeprators = ["Enter"]; 70 | 71 | export const TagsInput = ({ 72 | name, 73 | placeHolder, 74 | value, 75 | onChange, 76 | onBlur, 77 | separators, 78 | onExisting, 79 | onRemoved, 80 | suggestions, 81 | maxTags 82 | }: TagsInputProps) => { 83 | let [tags, setTags] = useState(value || []); 84 | 85 | useEffect(() => { 86 | onChange && onChange(tags); 87 | }, [tags]); 88 | 89 | if (maxTags >= 0) { 90 | let remainingLimit = Math.max(maxTags, 0) 91 | tags = tags.slice(0, remainingLimit) 92 | } 93 | 94 | const handleOnKeyUp = (e) => { 95 | e.stopPropagation(); 96 | 97 | const text = e.target.value; 98 | 99 | if (e.key === "Backspace" && tags.length && !text) { 100 | setTags(tags.slice(0, -1)); 101 | } 102 | 103 | 104 | if (text && (separators || defaultSeprators).includes(e.key)) { 105 | if (tags.includes(text)) { 106 | onExisting && onExisting(text); 107 | return; 108 | } 109 | setTags([...tags, text]); 110 | e.target.value = ""; 111 | e.preventDefault(); 112 | } 113 | }; 114 | 115 | const onTagRemove = (text: string) => { 116 | setTags(tags.filter(tag => tag !== text)); 117 | onRemoved && onRemoved(text); 118 | }; 119 | 120 | return ( 121 |
122 | {tags.map(tag => ( 123 | 124 | ))} 125 | 126 | 127 | 135 | 136 |
137 | ); 138 | }; 139 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/react-tag-input-componet/tag.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { css } from "goober"; 3 | import cc from "./classnames"; 4 | 5 | interface TagProps { 6 | text: string; 7 | remove: any; 8 | } 9 | 10 | const tagStyles = css({ 11 | alignItems: "center", 12 | background: "var(--rtiTag)", 13 | borderRadius: "var(--rtiRadius)", 14 | display: "inline-flex", 15 | justifyContent: "center", 16 | paddingLeft: "var(--rtiS)", 17 | 18 | button: { 19 | background: "none", 20 | border: 0, 21 | borderRadius: "50%", 22 | cursor: "pointer", 23 | lineHeight: "inherit", 24 | padding: "0 var(--rtiS)", 25 | 26 | "&:hover": { 27 | color: "var(--rti-tag-remove)", 28 | }, 29 | }, 30 | }); 31 | 32 | export default function Tag({ text, remove }: TagProps) { 33 | const handleOnRemove = (e: { stopPropagation: () => void; }) => { 34 | e.stopPropagation(); 35 | remove(text); 36 | }; 37 | 38 | return ( 39 | 40 | {text} 41 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: var(--font) !important; 3 | } 4 | 5 | .rti--container { 6 | max-width: inherit; 7 | --rtiTag: var(--primary-color) !important; 8 | background-color: var(--secondary-background-color); 9 | font: var(--font); 10 | line-height: 1.4; 11 | } 12 | body{ 13 | color: var(--text-color); 14 | background-color: inherit !important; 15 | font: var(--font); 16 | } 17 | h3{ 18 | color: var(--text-color); 19 | background-color: var(--background-color); 20 | } 21 | span{ 22 | color: #ffffff; 23 | font: var(--font); 24 | } 25 | button{ 26 | color: #ffffff 27 | } 28 | .rti--input { 29 | background-color: var(--secondary-background-color); 30 | color: var(--text-color) !important; 31 | } 32 | .rah-hint{ 33 | color: var(--text-color) !important; 34 | opacity: 0.8; 35 | } 36 | .rah-hint-wrapper{ 37 | color: var(--text-color) !important; 38 | opacity: 0.8; 39 | } 40 | -------------------------------------------------------------------------------- /streamlit_tags/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "noImplicitAny": false 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /streamlit_tags/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "streamlit_tags" %} 2 | {% set version = "1.2.6" %} 3 | 4 | 5 | package: 6 | name: {{ name|lower }} 7 | version: {{ version }} 8 | 9 | source: 10 | url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/streamlit_tags-{{ version }}.tar.gz 11 | sha256: fe7ccd67dc6187feb09d830d170c6eedd7a765b9dfd5e037a83dc39e8eaeef03 12 | 13 | build: 14 | number: 0 15 | noarch: python 16 | script: {{ PYTHON }} -m pip install . -vv 17 | 18 | requirements: 19 | host: 20 | - pip 21 | - python >=3.6 22 | run: 23 | - python >=3.6 24 | - streamlit >=0.63 25 | 26 | test: 27 | imports: 28 | - streamlit_tags 29 | commands: 30 | - pip check 31 | requires: 32 | - pip 33 | 34 | about: 35 | home: https://github.com/gagan3012/streamlit-tags 36 | summary: Tags custom component for Streamlit 37 | license: MIT 38 | license_file: LICENSE.txt 39 | 40 | extra: 41 | recipe-maintainers: 42 | - gagan3012 43 | --------------------------------------------------------------------------------