├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── app.py ├── demo.gif ├── setup.py └── streamlit_custom_slider ├── __init__.py └── frontend ├── .env ├── .gitignore ├── .prettierrc ├── package-lock.json ├── package.json ├── public ├── bootstrap.min.css └── index.html ├── src ├── CustomSlider.tsx ├── index.tsx └── react-app-env.d.ts └── tsconfig.json /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 package/frontend/build * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Streamlit Custom Slider - Blog post 2 | 3 | Source code for the [Streamlit Components tutorial](https://streamlit-components-tutorial.netlify.app). 4 | 5 | ![](./demo.gif) 6 | 7 | Checkout the branch you're interested in 8 | 9 | ``` 10 | git checkout partx 11 | ``` 12 | 13 | Install streamlit and custom library: 14 | 15 | ``` 16 | pip install streamlit 17 | pip install -e . 18 | ``` 19 | 20 | Install npm dependencies : 21 | 22 | ``` 23 | cd streamlit_custom_slider/frontend 24 | npm install 25 | ``` 26 | 27 | Run custom components web server : 28 | 29 | ``` 30 | cd streamlit_custom_slider/frontend 31 | npm run start 32 | ``` 33 | 34 | Run Streamlit code : 35 | 36 | ``` 37 | streamlit run app.py 38 | ``` 39 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_custom_slider import st_custom_slider 3 | from streamlit_custom_slider import st_range_slider 4 | 5 | v_custom = st_custom_slider('Hello world', 0, 100, 50, key="slider1") 6 | st.write(v_custom) 7 | 8 | # Add a range slider 9 | v_custom_range = st_range_slider('Hello world', 0, 100, (20, 60), key="slider2") 10 | st.write(v_custom_range) -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andfanilo/streamlit-custom-slider/6e71b3ca6d3c68ab3c26cba61cd3d08c74e02898/demo.gif -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name="streamlit-custom-slider", 5 | version="0.1.0", 6 | author="", 7 | author_email="", 8 | description="", 9 | long_description="", 10 | long_description_content_type="text/plain", 11 | url="", 12 | packages=setuptools.find_packages(), 13 | include_package_data=True, 14 | classifiers=[], 15 | python_requires=">=3.6", 16 | install_requires=[ 17 | # By definition, a Custom Component depends on Streamlit. 18 | # If your component has other Python dependencies, list 19 | # them here. 20 | "streamlit >= 0.63", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /streamlit_custom_slider/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import streamlit.components.v1 as components 3 | 4 | from typing import Tuple 5 | 6 | 7 | # Now the React interface only accepts an array of 1 or 2 elements. 8 | _component_func = components.declare_component( 9 | "custom_slider", 10 | url="http://localhost:3001", 11 | ) 12 | 13 | 14 | # Edit arguments sent and result received from React component, so the initial input is converted to an array and returned value extracted from the component 15 | def st_custom_slider(label: str, min_value: int, max_value: int, value: int = 0, key=None) -> int: 16 | component_value = _component_func(label=label, minValue=min_value, maxValue=max_value, initialValue=[value], key=key, default=[value]) 17 | return component_value[0] 18 | 19 | 20 | # Define a new public method which takes as input a tuple of numbers to define a range slider, and returns back a tuple. 21 | def st_range_slider(label: str, min_value: int, max_value: int, value: Tuple[int, int], key=None) -> Tuple[int, int]: 22 | component_value = _component_func(label=label, minValue=min_value, maxValue=max_value, initialValue=value, key=key, default=value) 23 | return tuple(component_value) -------------------------------------------------------------------------------- /streamlit_custom_slider/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_custom_slider/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | yarn.lock 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /streamlit_custom_slider/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /streamlit_custom_slider/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streamlit_custom_slider", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@types/hoist-non-react-statics": "^3.3.1", 10 | "@types/jest": "^24.0.0", 11 | "@types/node": "^12.0.0", 12 | "@types/react": "^16.9.0", 13 | "@types/react-dom": "^16.9.0", 14 | "@types/styletron-engine-atomic": "^1.1.0", 15 | "@types/styletron-react": "^5.0.2", 16 | "@types/styletron-standard": "^2.0.0", 17 | "apache-arrow": "^0.17.0", 18 | "baseui": "^9.85.0", 19 | "bootstrap": "^4.4.1", 20 | "event-target-shim": "^5.0.1", 21 | "hoist-non-react-statics": "^3.3.2", 22 | "react": "^16.13.1", 23 | "react-dom": "^16.13.1", 24 | "react-scripts": "3.4.1", 25 | "streamlit-component-lib": "^1.1.3", 26 | "styletron-engine-atomic": "^1.4.6", 27 | "styletron-react": "^5.2.7", 28 | "typescript": "~3.7.2" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": "react-app" 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | }, 51 | "homepage": "." 52 | } 53 | -------------------------------------------------------------------------------- /streamlit_custom_slider/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Streamlit Component 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /streamlit_custom_slider/frontend/src/CustomSlider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | import { ComponentProps, Streamlit, withStreamlitConnection } from "streamlit-component-lib" 3 | import { Slider } from "baseui/slider" 4 | 5 | /** 6 | * We can use a Typescript interface to destructure the arguments from Python 7 | * and validate the types of the input 8 | */ 9 | interface PythonArgs { 10 | label: string 11 | minValue?: number 12 | maxValue?: number 13 | initialValue: number[] 14 | } 15 | 16 | /** 17 | * No more props manipulation in the code. 18 | * We store props in state and pass value directly to underlying Slider 19 | * and then back to Streamlit. 20 | */ 21 | const CustomSlider = (props: ComponentProps) => { 22 | // Destructure using Typescript interface 23 | // This ensures typing validation for received props from Python 24 | const { label, minValue, maxValue, initialValue }: PythonArgs = props.args 25 | const [value, setValue] = useState(initialValue) 26 | 27 | useEffect(() => Streamlit.setFrameHeight()) 28 | 29 | return ( 30 | <> 31 |

{label}

32 | value && setValue(value)} 35 | onFinalChange={({ value }) => Streamlit.setComponentValue(value)} 36 | min={minValue} 37 | max={maxValue} 38 | /> 39 | 40 | ) 41 | } 42 | 43 | export default withStreamlitConnection(CustomSlider) 44 | -------------------------------------------------------------------------------- /streamlit_custom_slider/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import CustomSlider from "./CustomSlider" 4 | 5 | // Lots of import to define a Styletron engine and load the light theme of baseui 6 | import { Client as Styletron } from "styletron-engine-atomic" 7 | import { Provider as StyletronProvider } from "styletron-react" 8 | import { ThemeProvider, LightTheme } from "baseui" 9 | 10 | const engine = new Styletron() 11 | 12 | // Wrap your CustomSlider with the baseui them 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | , 21 | document.getElementById("root") 22 | ) 23 | -------------------------------------------------------------------------------- /streamlit_custom_slider/frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /streamlit_custom_slider/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 | }, 18 | "include": ["src"] 19 | } 20 | --------------------------------------------------------------------------------