diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..f51cce5ab --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,18 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/python-3/.devcontainer/base.Dockerfile + +# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster +ARG VARIANT="3.10-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +ENV PATH="/home/vscode/.local/bin:${PATH}" + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..4c2a0068c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/python-3 +{ + "name": "Python 3", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "VARIANT": "3.7", + // Options + "NODE_VERSION": "lts/*" + } + }, + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance" + ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ + 3000 + ], + // Use 'postCreateCommand' to run commands after the container is created. + // Install NPM dependencies. + "postCreateCommand": "make", + // Use 'postStartCommand' to run 'make up' every time the container is started. + "postStartCommand": "make up", + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + "git": "latest", + "github-cli": "latest" + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..8eee74b99 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,55 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: bug, needs triage +assignees: "" +--- + +### Summary + +Type here a clear and concise description of the bug. Aim for 2-3 sentences. + +### Steps to reproduce + +Code snippet: + +``` + +``` + +Screenshot(s): + +(Please provide a code snippet and/or screenshots! These will help expedite us finding and solving the problem.) + +If applicable, please provide the steps we should take to reproduce the bug: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' + +**Expected behavior:** + +Explain what you expect to happen when you go through the steps above, assuming there were no bugs. + +**Actual behavior:** + +Explain the buggy behavior you experience when you go through the steps above. +If applicable, add screenshots to help explain your problem. + +### Is this a regression? + +That is, did this use to work the way you expected in the past? +yes / no + +### Debug info + +- Docs page link: (do you have a link to the docs page containing the bug?) +- npm version: (get it with `npm -v`) +- node version: (get it with `node -v`) +- OS version: +- Browser version: + +### Additional information + +If needed, add any other context about the problem here. For example, did this bug come from or another site? Link the original source here! diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..e59b20e1c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Streamlit community support + url: https://discuss.streamlit.io/ + about: Please ask and answer questions here. + - name: Streamlit documentation + url: https://docs.streamlit.io/ + about: To learn more about how Streamlit works. + - name: Streamlit docs README + url: https://github.com/streamlit/docs/blob/main/README.md + about: To learn how Streamlit's documentation works. diff --git a/.github/ISSUE_TEMPLATE/doc_improvement.md b/.github/ISSUE_TEMPLATE/doc_improvement.md new file mode 100644 index 000000000..1d89f4d9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc_improvement.md @@ -0,0 +1,13 @@ +--- +name: Documentation improvement request +about: Let us know how our docs could be better +title: "" +labels: docs, needs triage +assignees: "" +--- + +**Link to doc page in question (if any):** + +**Name of the Streamlit feature whose docs need improvement:** + +**What you think the docs should say:** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..b7c049f43 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: enhancement, needs triage +assignees: "" +--- + +_(Note, you don't have to fill out every section here. They're just here for guidance. That said, nicely detailed feature requests are more likely to get eng attention sooner)_ + +### Problem + +Is your feature request related to a problem? Please describe the problem here. Ex. I'm always frustrated when [...] + +### Solution + +**MVP:** What's the smallest possible solution that would get 80% of the problem out of the way? + +**Possible additions:** What are other things that could be added to the MVP over time to make it better? + +**Preferred solution:** If you don't like the MVP above, tell us why, and what you'd like done instead. + +### Additional context + +Add any other context or screenshots about the feature request here. For example, did this FR come from or another site? Link the original source here! diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..11b9d55b9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,38 @@ +## 📚 Context + + + +## 🧠 Description of Changes + + + + +**Revised:** + +_Insert screenshot of your updated UI/code here_ + +**Current:** + +_Insert screenshot of existing UI/code here_ + +## 💥 Impact + + + +Size: + +- [ ] Small +- [ ] Not small + +## 🌐 References + + + + +- [ ] [Notion](...) + + + +**Contribution License Agreement** + +By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license. diff --git a/.github/workflows/postbuild-scripts.yml b/.github/workflows/postbuild-scripts.yml new file mode 100644 index 000000000..0b484b899 --- /dev/null +++ b/.github/workflows/postbuild-scripts.yml @@ -0,0 +1,53 @@ +name: postbuild-scripts +run-name: Build the site, update Algolia's search index, and regenerate the sitemap +on: + push: + branches: + - "main" + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + build-search-index: + runs-on: ubuntu-latest + name: Build Algolia search index + + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + + - name: Setup node, build the site and search index afterwards + uses: actions/setup-node@v3 + with: + node-version: "14" + - run: npm install + - run: npm run build + - run: node ./scripts/build-search-index.js + build-sitemap: + runs-on: ubuntu-latest + name: Build sitemap + + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + + - name: Setup node, build the site, and generate the sitemap + id: sitemap + uses: actions/setup-node@v3 + with: + node-version: "14" + - run: npm install + - run: npm run build + - run: npm run sitemap + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + title: "Automated sitemap update" + body: > + Sitemap updated by the [generate-sitemap](https://github.com/cicirello/generate-sitemap) + GitHub action. Automated pull-request generated by the + [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action. diff --git a/.gitignore b/.gitignore index 67045665d..39a9fe299 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,9 @@ typings/ .rts2_cache_es/ .rts2_cache_umd/ +# Python cache +python/__pycache__/ + # Optional REPL history .node_repl_history @@ -102,3 +105,11 @@ dist # TernJS port file .tern-port + +.next/ +node_modules/ +/npm-debug.log +.DS_Store +out/ +public/pdf/pages/ +.idea/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..35d691878 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx pretty-quick --staged diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..3b0ca9db6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v15.14.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..005290e61 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,35 @@ +# Just copied straight from .gitignore + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +.env + +# Sitemap and other public files +/public \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICES b/NOTICES new file mode 100644 index 000000000..802099cdf --- /dev/null +++ b/NOTICES @@ -0,0 +1,405 @@ +THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE THAT MAY BE CONTAINED IN PORTIONS OF THE STREAMLIT BROWSER PRODUCT. + +----- + +The following software may be included in this product: Source Code Pro. This software contains the following license and notice below: + +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +----- + +The following software may be included in this product: Inter. This software contains the following license and notice below: + +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +----- + +The following software may be included in this product: Material Icons. This software contains the following license and notice below: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +----- \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..066c3eae9 --- /dev/null +++ b/README.md @@ -0,0 +1,359 @@ +# Streamlit Docs + +[![Netlify Status](https://api.netlify.com/api/v1/badges/1ddc1b5a-ec21-4b66-987d-feeb68854c28/deploy-status?branch=main)](https://app.netlify.com/sites/streamlit-docs/deploys) + +We use Next.js and Netlify to build our [documentation site](https://docs.streamlit.io/). + +## Building + +To build the docs, clone this repo, install the NPM dependencies, and start the development server. + +### 1. Set up your base environment + +#### MacOS + +```bash +# Some Apple dev tools (developer.apple.com/downloads) +$ xcode-select --install + +# Install Homebrew +$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +# Install the Protobuf compiler, graphviz, database, and virtual environment +$ brew install protobuf graphviz gawk mysql mysql-client pkg-config unixodbc postgresql pre-commit +``` + +**Installing Node JS and yarn** + +We recommend that you [manage your nodejs installation with nvm](https://github.com/nvm-sh/nvm#install--update-script). +After following the instructions linked above to install `nvm`, use the following command to install the latest supported node version + +```bash +# Install node +nvm install node +``` + +**Note:** Node has added Corepack which is a manager of package managers 😱. It supports yarn! You can enable it by running the following: + +```bash +corepack enable +``` + +You may need to `brew install corepack` depending on how you installed node. + +**ARM based Macs** + +If you are running a MacOS computer running on the new chipsets (e.g. M1), you may hit some trouble installing pyodbc This can be solved by installing `unixodbc` and setting some flags. See [this comment](https://github.com/mkleehammer/pyodbc/issues/846#issuecomment-816166371) for help. We found that it just needs to be set once. Be sure to make sure you have the correct version of unixodbc in the commands. + +#### Ubuntu + +```bash + +# Set up the Yarn repo +$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + +$ sudo apt-get update + +# Install Pyenv for testing multiple Python versions +$ sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \ +libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \ +xz-utils tk-dev libffi-dev liblzma-dev python-openssl mysql-client libmysqlclient-dev unixodbc-dev +$ curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash + +# Install some other deps +$ sudo apt install graphviz python3-distutils pre-commit + +# Install Yarn, pip, Protobuf, npm +$ sudo apt install yarn npm python-pip protobuf-compiler libgconf-2-4 +# (libgconf is for our e2e tests in Cypress) + +``` + +Probably not needed, but in case you want to update your Node installation: + +```bash +curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +``` + +#### Windows + +Streamlit's development setup is pretty Mac- and Linux-centric. If you're doing Streamlit development on Windows, we suggest spinning up a Linux VM (you can do this easily with [VirtualBox](https://www.virtualbox.org/), which is free); or working in a Linux Docker image. + +Alternately, you can try using Microsoft's WSL ("Windows Subsystem for Linux"), which may work fine, or may result in you slowly dying from thousands of tiny papercuts (these steps were last tested on 2019-11-18): + +- [Install WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10), using the Ubuntu image from the Microsoft Store +- [Make sure your WSL supports chmod/chown](https://devblogs.microsoft.com/commandline/chmod-chown-wsl-improvements/). +- [Make sure you have pre-commit installed and it is available on your path](https://pre-commit.com/#install). +- If you have Windows versions of Streamlit build dependencies installed (e.g. Python, nodejs), you may want to consider removing the Windows path from your Linux $PATH, to prevent conflicts. + - [See the WSL Release Notes](https://docs.microsoft.com/en-us/windows/wsl/release-notes#build-17713) for instructions on editing your wsl.conf file. You'll want to set `appendWindowsPath=false`. +- Follow the [Ubuntu setup instructions above](#Ubuntu), with the following modifications: + +```bash +# Install a Python version using pyenv with the `CONFIGURE_OPTS=--enable-shared` flag set: +$ CONFIGURE_OPTS=--enable-shared pyenv install 3.7.5 && pyenv global 3.7.5 + +$ python -m venv venv +``` + +### 2. Clone this repo: + +```bash +git clone https://github.com/streamlit/docs.git +cd docs/ +``` + +### 3. Install the NPM dependencies + +```bash +make +``` + +### 4. Start the development server: + +```bash +make up +``` + +The docs will be viewable at [http://localhost:3000](http://localhost:3000). Note that any time you modify the source files in the `content/` folder, you'll need to refresh your browser tab to view the changes. You **do not** need to restart the development server. + +## File and folder structure + +This repo follows a typical Next.js project structure. To contribute, you'll only edit Markdown files within the `content/` folder. + +- `components/` Contains JS and MDX files. +- `content/` This is where all the Markdown files live. This is the only folder you'll edit. +- `lib/` Contains JS files. +- `pages/` You'll never have to edit this folder. It contains JSX files that handle the complex index page, mapping of URL slugs, and rendering of Markdown pages in `content/`. +- `public/` Contains all the images used in our docs. +- `python/` Contains Python code to generate the docstrings for the API Reference. +- `scripts/` Contains JS files. +- `styles/` Contains CSS files for styling and layout. + +## Contributing + +To add, edit, or delete content in our documentation, you have to modify Markdown (`.md`) files within folders and sub-folders in `content/` : + +- `kb/` Contains `.md` files that populate the Knowledge Base. +- `library/` Contains `.md` files that populate the Streamlit Library section. +- `streamlit-cloud/`Contains `.md` files that populate the Streamlit Community Cloud section. +- `gdpr-banner.md` You'll never have to edit this file. +- `index.md` Contains text that populates the index page. +- `menu.md` This is a special file containing only front matter that defines the docs Menu. You will need to add an entry on this file for each new page you create within the docs' site. + +The directory structure of `content/` does not matter as the files will be recursively read and will ignore the directories. However, we recommend following the directory structure to organize the files, mirroring the structure on the documentation website. + +### Add a new page + +Do you want to add a new page to the docs? + +1. First, decide which section the page should live in (Streamlit Library, Streamlit Community Cloud, or Knowledge Base). + +2. Next, navigate to the relevant folder and subfolder within `content/` and create a `.md` file whose filename mirrors the title of the page. E.g. For a page titled "Create a component", navigate to `content/library/components/` and create a file named `create-component.md`. + +### Structure of the `.md` file + +Now that you've decided where the file should live and have named the file, it's time to add your content! + +**File format**: + +Every `.md` file has front matter at the very top that defines the page title which appears in the browser tab bar, and the URL slug which appears after the slash in `docs.streamlit.io/` and `localhost:3000/`. + +E.g. For a page titled "Create a component" that should exist at `docs.streamlit.io/library/components/create`, the front matter at the top of `create-component.md` is: + +```markdown +--- +title: Create a Component +slug: /library/components/create +--- +``` + +**Headings:** + +Use standard Markdown for headings (#, ##, ###) + +**Callouts:** + +To add a callout (Note, Tip, Warning, Important), enter your Markdown text within the appropriate tags (MDX functions), making sure to add a blank line after the opening tag and another before the closing tag. E.g. + +```markdown + + +This is a **note** that links to our [website](https://docs.streamlit.io/). + + +``` + +**Embed code:** + +Enclose code within \` \` to embed it inline. E.g. + +```markdown +Create a header with `st.header`. +``` + +Embed code blocks like so: + +````markdown + ```python + import streamlit as st + + st.text("Hello world") + ``` +```` + +We support syntax highlighting for Python, Bash, TOML, SQL, and JSX. + +**Link to other pages in docs:** + +Use standard Markdown to link to other pages in the docs. E.g. Add an inline link to the "Create an app" page by including the slug defined in the front matter of the "Create an app" `.md` file: + +```markdown +Learn how to [Create an app](/library/get-started/create-an-app). +``` + +**Add images:** + +Store images you want to display in `/public/images/`. There are two ways to display images. + +1. To display a single image, use regular Markdown. Make sure to start the path of your images from `/images/` instead of `/public/images/`. E.g.: + + ```markdown + ![Secrets manager screenshot](/images/databases/edit-secrets.png) + ``` + +2. To display multiple images on the same line, add an `` tag containing the alt text and path to the image, for each image, and enclose all of them within `` `` tags. E.g. To display 3 images stored in `/public/images/databases/` : + + ```markdown + + Bigquery screenshot 7 + Bigquery screenshot 8 + Bigquery screenshot 9 + + ``` + +**Discoverability:** + +All it takes for a new page to be available at a URL is to create a Markdown file in the format described above, containing the title and slug in the front matter. + +However, a user has to know the URL to visit the page. The page is therefore _reachable but not discoverable_. The next section describes how to add pages to the docs Menu so that users can find your page. + +### Add pages to the Menu + +How do you make the page you created appear in the Menu? Edit the special markdown file `content/menu.md`. All it has is front matter in YAML. + +Suppose you have created an "Installation" page that is available at `docs.streamlit.io/library/get-started/installation`. You want to it to appear in the Menu within the "Streamlit Library" section, nested under the "Get Started" page. + +To do so, find the lines that define the `category`, `url` and `visible` properties for "Get Started" in `menu.md` and add three new lines below it, containing: + +```YAML +- category: Streamlit Library / Get Started / Installation + url: /library/get-started/installation + visible: true +``` + +> Important: You _always_ need to add the entry you created in `menu.md`, or otherwise the build will fail. It is important because we use the structure on this page to create the breadcrumbs for each page. If you don't want the page to show up on the menu, you _still_ need to add it, but you can set its `visible` property to `false`. + +### Edit an existing page + +To edit an existing page: + +1. Locate the `.md` file for the page +2. Edit the Markdown and save the file + +To preview your changes, refresh your browser tab and visit the edited page! + +### Add a new docstring to the API Reference + +Any time a new version of Streamlit is released, the docstrings stored in `python/streamlit.json` have to be updated by running `make docstrings` . This will build the nesscary Docker image, and update the file with the documentation for the latest release on PyPi. + +If you need to regenerate all function signatures, across all versions, delete the content in `python/streamlit.json`, leaving the file in place, and run `make docstrings`. This will systematically install each version of streamlit, and generate the necessary function signatures in `streamlit.json`. + +Suppose a new Streamlit release includes a `st.my_chart` function that you want to include in the "Chart elements" section of the API Reference: + +1. Run `make docstrings` +2. Create Markdown file (`my_chart.md`) in `content/library/api/charts/` +3. Add the following to `my_chart.md`: + + ```markdown + --- + title: st.my_chart + slug: /library/api-reference/charts/st.my_chart + --- + + + ``` + +4. Add the following under the "Chart elements" heading in `content/library/api/api-reference.md`: + 1. A RefCard MDX function containing the URL slug defined in `my_chart.md` . This is the card that will appear on the API Reference landing page. + 2. An Image MDX function containing alt text and the location of the image to be displayed on the card. + 3. A bold heading that will appear on the card (`#### Heading`). It appears below the card image. + 4. A brief description of the `st.my_chart` . It appears below the card heading. + 5. A code block illustrating how to use `st.my_chart`. It appears below the card description and has a Copy icon that when clicked copies the code block to the users' clipboard. + +````markdown + + Tux, the Linux mascot + + #### My charts + + Display a chart using the MyChart library. + + ```python + st.my_chart(my_data_frame) + ``` + + +```` + +5. Add the following 2 new lines to `menu.md` so that `st.my_chart` appears in the Menu: + + ```YAML + - category: Streamlit Library / API Reference / Chart elements / st.my_chart + url: /library/api-reference/charts/st.my_chart + ``` + +6. Save your changes and refresh the browser tab. If all went well, you should see a new entry in the Menu, a new card in the API Reference, and a new page for `st.my_chart`. + +### Add to the Knowledge Base + +The Knowledge Base (KB) is divided into five sections: + +1. **Tutorials:** Step-by-step examples of building different types of apps in Streamlit +2. **Using Streamlit:** Frequently asked questions about using Streamlit Library +3. **Deployment Issues:** Articles about deploying Streamlit apps +4. **Streamlit Components:** Articles about Streamlit components +5. **Installing Dependencies:** System and Python dependency issues while using or deploying Streamlit apps + +If you know the answer to a Streamlit user's pain point and want to add it to the KB: + +1. Decide which of the above sections your article belongs to +2. Navigate to the relevant section's folder in `kb/` and +3. Create a `.md` file in the above specified format containing your article + + - Make sure the title in the front matter and the file header in Markdown are identical. E.g. + + ```markdown + --- + title: How do I add a Component to the sidebar? + slug: /knowledge-base/components/add-component-sidebar + --- + + # How do I add a Component to the sidebar? + ``` + +4. Add a line to the existing `index.md` file in the same folder as your article. It should contain the title and URL slug specified in your article's front matter. This step ensures that users are able to discover your article in the index page of the relevant KB section. E.g. + + ```markdown + - [How do I add a Component to the sidebar?](/knowledge-base/components/add-component-sidebar) + ``` + +## Publishing + +To publish your changes to the docs site: + +1. Create a new branch containing your changes. +2. Create a Pull Request and mark Snehan as reviewers. +3. Once the checks have completed, checkout the Preview build. +4. Snehan will review your changes and merge your changes into the `main` branch. +5. Once merged, your changes will be live at [https://docs.streamlit.io/](https://docs.streamlit.io/). diff --git a/algolia/INSTRUCTIONS b/algolia/INSTRUCTIONS new file mode 100644 index 000000000..f71c1b3dd --- /dev/null +++ b/algolia/INSTRUCTIONS @@ -0,0 +1,15 @@ +If Algolia suddenly breaks, reimport this configuration file in its UI! + +How: +1. Go here https://www.algolia.com/apps/XNXFGO6BQ1/explorer/configuration/documentation/searchable-attributes + + Or, if that doesn't work: + - Open algolia.com + - Navigate to Search (sidebar) + - Navigate to App: Streamlit docs > index: documentation + +2. Open "Manage index" dropdown, choose "Import Configuration" + +3. Pick the index_config.json file that's in this folder. + +DONE! diff --git a/algolia/index_config.json b/algolia/index_config.json new file mode 100644 index 000000000..8afe5a63b --- /dev/null +++ b/algolia/index_config.json @@ -0,0 +1,40 @@ +{ + "settings": { + "minWordSizefor1Typo": 4, + "minWordSizefor2Typos": 8, + "hitsPerPage": 20, + "maxValuesPerFacet": 100, + "searchableAttributes": ["keywords", "title", "content"], + "numericAttributesToIndex": null, + "attributesToRetrieve": null, + "replaceSynonymsInHighlight": true, + "unretrievableAttributes": null, + "optionalWords": null, + "attributesForFaceting": ["filterOnly(version)"], + "attributesToSnippet": ["content:15"], + "attributesToHighlight": ["category", "content", "title"], + "paginationLimitedTo": 1000, + "attributeForDistinct": null, + "exactOnSingleWordQuery": "attribute", + "ranking": [ + "exact", + "typo", + "geo", + "words", + "filters", + "proximity", + "attribute", + "custom" + ], + "customRanking": null, + "separatorsToIndex": "", + "removeWordsIfNoResults": "none", + "queryType": "prefixLast", + "highlightPreTag": "", + "highlightPostTag": "", + "snippetEllipsisText": "", + "alternativesAsExact": ["ignorePlurals", "singleWordSynonym"] + }, + "rules": [], + "synonyms": [] +} diff --git a/components/blocks/autofunction.js b/components/blocks/autofunction.js new file mode 100644 index 000000000..66d9224ba --- /dev/null +++ b/components/blocks/autofunction.js @@ -0,0 +1,460 @@ +import React, { useEffect, useState, useRef } from "react"; +import reverse from "lodash/reverse"; +import classNames from "classnames"; +import Table from "./table"; +import { H2 } from "./headers"; +import Warning from "./warning"; +import Deprecation from "./deprecation"; +import { withRouter, useRouter } from "next/router"; +import Prism from "prismjs"; +import "prismjs/components/prism-python"; +import "prismjs/plugins/line-numbers/prism-line-numbers"; +import "prismjs/plugins/line-highlight/prism-line-highlight"; +import "prismjs/plugins/line-highlight/prism-line-highlight.css"; +import "prismjs/plugins/toolbar/prism-toolbar"; +import "prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard"; +import "prismjs/plugins/normalize-whitespace/prism-normalize-whitespace"; + +import styles from "./autofunction.module.css"; +import { name } from "file-loader"; + +const cleanHref = (name) => { + return String(name).replace(".", "").replace(" ", "-"); +}; + +const Autofunction = ({ + version, + versions, + streamlitFunction, + streamlit, + slug, + hideHeader, + deprecated, + deprecatedText, +}) => { + const blockRef = useRef(); + const router = useRouter(); + const maxVersion = versions[versions.length - 1]; + const [isHighlighted, setIsHighlighted] = useState(false); + const [currentVersion, setCurrentVersion] = useState( + version ? version : versions[versions.length - 1] + ); + + useEffect(() => { + highlightWithPrism(); + regenerateIframes(); + }, [streamlitFunction]); + + // Code to destroy and regenerate iframes on each new autofunction render. + const regenerateIframes = () => { + const iframes = Array.prototype.slice.call( + blockRef.current.getElementsByTagName("iframe") + ); + if (!iframes) return; + + iframes.forEach((iframe) => { + const parent = iframe.parentElement; + const newFrame = iframe.cloneNode(); + + newFrame.src = ""; + newFrame.classList.add("new"); + newFrame.src = iframe.src; + + parent.replaceChild(newFrame, iframe); + }); + }; + + const highlightWithPrism = () => { + if (isHighlighted) { + return; + } + if (!blockRef.current) { + return; + } + + const pres = Array.prototype.slice.call( + blockRef.current.getElementsByTagName("pre") + ); + + pres.forEach((ele) => { + const codeText = ele.innerHTML; + const preTag = ele.cloneNode(true); + const codeWrap = document.createElement("div"); + codeWrap.setAttribute("class", styles.CodeBlockContainer); + const codeTag = document.createElement("code"); + codeTag.setAttribute("class", "language-python"); + preTag.classList.add("line-numbers"); + codeTag.innerHTML = codeText; + preTag.textContent = null; + preTag.appendChild(codeTag); + codeWrap.appendChild(preTag); + ele.replaceWith(codeWrap); + }); + + Prism.highlightAllUnder(blockRef.current); + + setIsHighlighted(true); + }; + + const VersionSelector = ({ + versionList, + currentVersion, + handleSelectVersion, + }) => { + const selectClass = + currentVersion !== versionList[0] + ? "version-select old-version" + : "version-select"; + + return ( +
+ +
+ ); + }; + + const handleSelectVersion = (event) => { + const functionObject = streamlit[streamlitFunction]; + const slicedSlug = slug.slice(); + + if (event.target.value !== currentVersion) { + setCurrentVersion(event.target.value); + if (event.target.value !== maxVersion) { + let isnum = /^[\d\.]+$/.test(slicedSlug[0]); + if (isnum) { + slicedSlug[0] = event.target.value; + } else { + slicedSlug.unshift(event.target.value); + } + slug.unshift(event.target.value); + } + } + + if (!functionObject) { + router.push(`/${slicedSlug.join("/")}`); + } else { + const name = cleanHref(`st.${functionObject.name}`); + router.push(`/${slicedSlug.join("/")}#${name} `); + } + }; + + const footers = []; + const args = []; + const returns = []; + const versionList = reverse(versions.slice()); + let functionObject; + let functionDescription; + let header; + let body; + let isClass; + let methods = []; + let properties = []; + + if (streamlitFunction in streamlit) { + functionObject = streamlit[streamlitFunction]; + isClass = functionObject.is_class; + if ( + functionObject.description !== undefined && + functionObject.description + ) { + functionDescription = { __html: functionObject.description }; + } + } else { + return ( +
+
+

+ {streamlitFunction.replace("streamlit", "st")} +

+ +
+ +

+ This method did not exist in version {currentVersion}{" "} + of Streamlit. +

+
+
+ ); + } + + if ("methods" in functionObject) { + methods = functionObject.methods; + } + + if ("properties" in functionObject) { + properties = functionObject.properties; + } + + if (hideHeader !== undefined && hideHeader) { + header = ""; + } else { + const functionName = functionObject.signature + ? `${functionObject.signature}`.split("(")[0].replace("streamlit", "st") + : ""; + const name = + String(functionObject.name).startsWith("html") || + String(functionObject.name).startsWith("iframe") + ? `st.components.v1.${functionObject.name}` + : functionName; + header = ( +
+
+

{name}

+ +
+ {deprecated === true ? ( + +

+ + ) : ( + "" + )} +

+
+ ); + } + + if ("example" in functionObject) { + footers.push({ title: "Example", body: functionObject.example }); + } + + if ("examples" in functionObject) { + footers.push({ title: "Examples", body: functionObject.examples }); + } + + if ("notes" in functionObject) { + footers.push({ title: "Notes", body: functionObject.notes }); + } + + if ("warning" in functionObject) { + footers.push({ title: "Warning", body: functionObject.warning }); + } + + for (const index in functionObject.args) { + const row = {}; + const param = functionObject.args[index]; + const isDeprecated = + param.deprecated && param.deprecated.deprecated === true; + const deprecatedMarkup = isDeprecated + ? ` +
+ + delete + + ${param.deprecated.deprecatedText} +
` + : ""; + const description = param.description + ? param.description + : `

No description

`; + + if (param.is_optional) { + row["title"] = ` +

+ ${param.name} + (${param.type_name}) +

`; + row["body"] = ` + ${deprecatedMarkup} + ${description} + `; + } else { + row["title"] = ` +

+ ${param.name} + (${param.type_name}) +

`; + row["body"] = ` + ${deprecatedMarkup} + ${description} + `; + } + + args.push(row); + } + + let methodRows = []; + + for (const index in methods) { + const row = {}; + const method = methods[index]; + const slicedSlug = slug.slice().join("/"); + const hrefName = `${functionObject.name}.${method.name}` + .toLowerCase() + .replace("streamlit", "st") + .replace(/[.,\/#!$%\^&\*;:{}=\-`~()]/g, ""); + const type_name = method.signature + ? method.signature.match(/\(([^)]*)\)/)[1] + : ""; + const isDeprecated = + method.deprecated && method.deprecated.deprecated === true; + const deprecatedMarkup = isDeprecated + ? ` +
+ + delete + + ${method.deprecated.deprecatedText} +
` + : ""; + const description = method.description + ? method.description + : `

No description

`; + // Add a link to the method by appending the method name to the current URL using slug.slice(); + row["title"] = ` +

+ ${ + method.name + }(${type_name}) +

`; + row["body"] = ` + ${deprecatedMarkup} + ${description} + `; + + methodRows.push(row); + } + + let propertiesRows = []; + + for (const index in properties) { + const row = {}; + const property = properties[index]; + const slicedSlug = slug.slice().join("/"); + const hrefName = `${functionObject.name}.${property.name}` + .toLowerCase() + .replace("streamlit", "st") + .replace(/[.,\/#!$%\^&\*;:{}=\-`~()]/g, ""); + const isDeprecated = + property.deprecated && property.deprecated.deprecated === true; + const deprecatedMarkup = isDeprecated + ? ` +
+ + delete + + ${property.deprecated.deprecatedText} +
` + : ""; + const description = property.description + ? property.description + : `

No description

`; + // Add a link to the method by appending the method name to the current URL using slug.slice(); + row["title"] = ` +

+ ${ + property.name + } +

`; + row["body"] = ` + ${deprecatedMarkup} + ${description} + `; + propertiesRows.push(row); + } + + for (const index in functionObject.returns) { + const row = {}; + const param = functionObject.returns[index]; + const description = param.description + ? param.description + : `

No description

`; + + row[ + "title" + ] = `

(${param.type_name})

`; + row["body"] = `${description} `; + + returns.push(row); + } + + const footTitles = []; + const footRowsContent = []; + + if (methods.length) { + footTitles.push({ title: "Methods" }); + footRowsContent.push(methodRows); + } + + if (returns.length) { + footTitles.push({ title: "Returns" }); + footRowsContent.push(returns); + } + + if (properties.length) { + footTitles.push({ title: "Properties" }); + footRowsContent.push(propertiesRows); + } + + body = ( + + {isClass ? "Class description" : "Function signature"} + + [source] + + + ), + content: `

${functionObject.signature}

`, + }} + body={args.length ? { title: "Parameters" } : null} + bodyRows={args.length ? args : null} + foot={[ + methods.length ? { title: "Methods" } : null, + returns.length ? { title: "Returns" } : null, + properties.length ? { title: "Attributes" } : null, + ].filter((section) => section !== null)} + footRows={[ + methods.length ? methodRows : null, + returns.length ? returns : null, + properties.length ? propertiesRows : null, + ].filter((rows) => rows !== null)} + additionalClass="full-width" + footers={footers} + /> + ); + + return ( +
+ {header} + {body} +
+ ); +}; + +export default withRouter(Autofunction); diff --git a/components/blocks/autofunction.module.css b/components/blocks/autofunction.module.css new file mode 100644 index 000000000..9b91ffe06 --- /dev/null +++ b/components/blocks/autofunction.module.css @@ -0,0 +1,146 @@ +.Container { + @apply mt-6; +} + +.HeaderContainer { + @apply mb-6; +} + +.TitleContainer { + @apply flex flex-col items-start md:flex-row md:items-baseline mt-4 mb-2; +} + +.Title { + @apply mt-6 mb-2 text-4xl; +} + +.Form { + @apply relative my-4 md:my-0 ml-0 md:ml-auto max-w-fit; +} + +.Form::after { + @apply block absolute bottom-1/4 right-0 opacity-70 pointer-events-none w-3 h-2; + content: ""; + background-image: url("data:image/svg+xml,%0A%3Csvg width='11px' height='8px' viewBox='0 0 11 8' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='select_arrow_down_disabled' fill='%23000000' fill-rule='nonzero'%3E%3Cpath d='M10.8703474,1.8762017 L5.80707196,6.93171246 C5.72063689,7.01813145 5.61827957,7.06134094 5.5,7.06134094 C5.38172043,7.06134094 5.27936311,7.01813145 5.19292804,6.93171246 L0.129652605,1.8762017 C0.0432175352,1.78978271 0,1.68630735 0,1.5657756 C0,1.44524385 0.0432175352,1.34176849 0.129652605,1.2553495 L1.26240695,0.129628481 C1.34884202,0.0432094937 1.45119934,0 1.56947891,0 C1.68775848,0 1.7901158,0.0432094937 1.87655087,0.129628481 L5.5,3.7524034 L9.12344913,0.129628481 C9.2098842,0.0432094937 9.31224152,0 9.43052109,0 C9.54880066,0 9.65115798,0.0432094937 9.73759305,0.129628481 L10.8703474,1.2553495 C10.9567825,1.34176849 11,1.44524385 11,1.5657756 C11,1.68630735 10.9567825,1.78978271 10.8703474,1.8762017 Z' id='Path'%3E%3C/path%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); +} + +.Select { + @apply appearance-none bg-white border-none p-0 m-0 w-full font-mono text-lg font-bold leading-none text-left md:text-right pr-2 md:pr-5; +} + +:global(.old-version) .Select { + @apply text-red-70; +} + +:global(.dark) .Select { + @apply bg-gray-100 text-white; +} + +:global(.dark) .Form::after { + background-image: url("data:image/svg+xml,%0A%3Csvg width='11px' height='8px' viewBox='0 0 11 8' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='select_arrow_down_disabled' fill='%23FFFFFF' fill-rule='nonzero'%3E%3Cpath d='M10.8703474,1.87655087 L5.80707196,6.93300248 C5.72063689,7.01943755 5.61827957,7.06265509 5.5,7.06265509 C5.38172043,7.06265509 5.27936311,7.01943755 5.19292804,6.93300248 L0.129652605,1.87655087 C0.0432175352,1.7901158 0,1.68662117 0,1.566067 C0,1.44551282 0.0432175352,1.3420182 0.129652605,1.25558313 L1.26240695,0.129652605 C1.34884202,0.0432175352 1.45119934,0 1.56947891,0 C1.68775848,0 1.7901158,0.0432175352 1.87655087,0.129652605 L5.5,3.75310174 L9.12344913,0.129652605 C9.2098842,0.0432175352 9.31224152,0 9.43052109,0 C9.54880066,0 9.65115798,0.0432175352 9.73759305,0.129652605 L10.8703474,1.25558313 C10.9567825,1.3420182 11,1.44551282 11,1.566067 C11,1.68662117 10.9567825,1.7901158 10.8703474,1.87655087 Z' id='Path'%3E%3C/path%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); +} + +.CodeBlockContainer { + @apply mb-7 relative; +} + +.CodeBlockContainer pre, +.CodeBlockContainer code { + @apply overflow-auto max-w-full whitespace-pre; +} + +.CodeBlockContainer pre { + @apply p-4 bg-gray-90 text-white font-medium rounded-xl relative py-6 px-8 leading-relaxed; +} + +.CodeBlockContainer pre code { + @apply z-10 relative; +} + +.LineHighlight { + @apply bg-gray-80 opacity-30 z-0; +} + +.CodeBlockContainer button { + @apply absolute top-2 right-2 cursor-pointer h-8 w-8 mb-0 flex items-center; +} + +.CodeBlockContainer button::before { + @apply absolute top-2 right-2 z-10 transition-all duration-75 hover:opacity-40; + content: url("/clipboard.svg"); +} + +.CodeBlockContainer button span { + @apply absolute right-10 text-white font-mono text-sm tracking-tight font-normal opacity-0; +} + +.CodeBlockContainer button:hover span { + @apply opacity-100; +} + +/* Code block adjustments */ +.CodeBlockContainer pre code :global(.decorator.annotation.punctuation) { + @apply text-red-50; +} + +.CodeBlockContainer pre code :global(.operator) { + @apply text-yellow-50; +} + +.CodeBlockContainer pre code :global(.decorator) { + @apply text-yellow-80; +} + +.CodeBlockContainer pre code :global(.keyword) { + @apply text-darkBlue-50; +} + +.CodeBlockContainer pre code :global(.builtin) { + @apply text-lightBlue-40; +} + +.CodeBlockContainer pre code :global(.string) { + @apply text-green-80; +} + +.CodeBlockContainer pre code :global(.number), +.CodeBlockContainer pre code :global(.boolean) { + @apply text-green-40; +} + +.CodeBlockContainer pre code :global(.function) { + @apply text-red-50; +} + +.CodeBlockContainer pre code :global(.punctuation) { + @apply text-gray-60; +} + +.CodeBlockContainer pre code :global(.comment) { + @apply text-gray-60 italic font-normal; +} + +.CodeBlockContainer pre code :global(.string) { + @apply text-darkBlue-30; +} + +.CodeBlockContainer pre code :global(.table) { + @apply inline; +} + +/* Styles for deprecation notice in param */ +.DeprecatedContent { + @apply bg-orange-20 text-gray-90 px-2 py-1 rounded-md text-sm mb-2 flex items-center; +} + +.DeprecatedContent i { + @apply text-base text-orange-70 mr-2; +} + +.DeprecatedContent p tt { + @apply text-sm; +} + +:global(.dark) .DeprecatedContent { + @apply bg-orange-100/30; +} diff --git a/components/blocks/button.js b/components/blocks/button.js new file mode 100644 index 000000000..91378db37 --- /dev/null +++ b/components/blocks/button.js @@ -0,0 +1,22 @@ +import Link from "next/link"; + +import styles from "./button.module.css"; + +const Button = ({ children, link, target }) => { + return target == "_blank" ? ( + + {children} + + ) : ( + + {children} + + ); +}; + +export default Button; diff --git a/components/blocks/button.module.css b/components/blocks/button.module.css new file mode 100644 index 000000000..a4b08b84a --- /dev/null +++ b/components/blocks/button.module.css @@ -0,0 +1,3 @@ +.Button { + @apply bg-red-70 transform text-white border-none p-4 rounded-md font-bold inline-block mb-4 hover:opacity-90 hover:scale-105 hover:shadow-md; +} diff --git a/components/blocks/callout.module.css b/components/blocks/callout.module.css new file mode 100644 index 000000000..31cf8f424 --- /dev/null +++ b/components/blocks/callout.module.css @@ -0,0 +1,23 @@ +.Container { + @apply p-4 md:p-8 rounded-md mt-8 mb-12; +} + +.Container p { + @apply text-base leading-relaxed tracking-tight; + @apply text-gray-90 !important; +} + +.Container p > code { + @apply border border-gray-40 text-red-70 rounded-md px-1 mx-1 break-words; +} + +.Container p:only-child, +.Container p:last-child, +.Container section:last-child, +.Container section:last-child pre { + @apply mb-0; +} + +:global(.dark) .Container p { + @apply text-white !important; +} diff --git a/components/blocks/cloud.js b/components/blocks/cloud.js new file mode 100644 index 000000000..dbb5a9502 --- /dev/null +++ b/components/blocks/cloud.js @@ -0,0 +1,45 @@ +import React, { useEffect, useRef } from "react"; +import classNames from "classnames"; + +import styles from "./cloud.module.css"; + +const Cloud = ({ src, height }) => { + const iframeRef = useRef(); + let CloudBlock; + + if (height) { + CloudBlock = ( +
+ +
+
+

{caption}

+
+ + ); + } else { + YouTubeBlock = ( +
+ +
+ ); + } + return YouTubeBlock; +}; + +export default YouTube; diff --git a/components/blocks/youtube.module.css b/components/blocks/youtube.module.css new file mode 100644 index 000000000..13b1c0ad3 --- /dev/null +++ b/components/blocks/youtube.module.css @@ -0,0 +1,19 @@ +.IframeContainer { + @apply relative w-full border border-gray-40 rounded-md; +} + +.Iframe { + @apply w-full aspect-video rounded-md max-w-full border-none inline-block; +} + +.CaptionContainer { + @apply mt-4; +} + +.Caption { + @apply italic text-gray-90 pt-2 md:pt-4 text-sm tracking-tight; +} + +:global(.dark) .Caption { + @apply text-white; +} diff --git a/components/layouts/component.js b/components/layouts/component.js new file mode 100644 index 000000000..5d2e8a9b0 --- /dev/null +++ b/components/layouts/component.js @@ -0,0 +1,18 @@ +import HeaderLink from "../utilities/headerLink"; + +import styles from "./component.module.css"; + +const Component = ({ children, label }) => { + return ( +
+
+ +

{label}

+
+
+
{children}
+
+ ); +}; + +export default Component; diff --git a/components/layouts/component.module.css b/components/layouts/component.module.css new file mode 100644 index 000000000..150e66abf --- /dev/null +++ b/components/layouts/component.module.css @@ -0,0 +1,15 @@ +.Container { + @apply flex my-12; +} + +.Label { + @apply w-1/5 text-right flex justify-end items-start mr-8; +} + +.Title { + @apply font-sans font-normal font-bold text-base leading-normal tracking-tight mb-0; +} + +.Component { + @apply w-4/5; +} diff --git a/components/layouts/container.module.css b/components/layouts/container.module.css new file mode 100644 index 000000000..5b37579ae --- /dev/null +++ b/components/layouts/container.module.css @@ -0,0 +1,34 @@ +.Container { + @apply grid grid-cols-1 lg:grid-cols-4 gap-4 container mx-auto px-2 xl:px-4; +} + +.InnerContainer { + @apply col-span-full lg:col-start-2 lg:col-end-5; +} + +.ArticleContainer { + @apply relative; +} + +/* Links inside markdown */ +.ArticleContainer p a, +.ArticleContainer ol li a, +.ArticleContainer ul li a { + @apply border-b border-b-gray-90 hover:border-red-60 hover:opacity-80; +} + +:global(.dark) .ArticleContainer p a, +:global(.dark) .ArticleContainer ol li a, +:global(.dark) .ArticleContainer ul li a { + @apply border-b-white !important; +} + +@media (min-width: 1448px) { + .ArticleContainer :global(.toc):not(.advanced-features) { + @apply block; + } + + .ArticleContainer :global(.toc):not(.advanced-features) ~ .ContentContainer { + padding-right: calc(226px + 2rem); + } +} diff --git a/components/layouts/dataSourcesContainer.js b/components/layouts/dataSourcesContainer.js new file mode 100644 index 000000000..223587bee --- /dev/null +++ b/components/layouts/dataSourcesContainer.js @@ -0,0 +1,7 @@ +import styles from "./dataSourcesContainer.module.css"; + +const DataSourcesContainer = ({ children }) => { + return
{children}
; +}; + +export default DataSourcesContainer; diff --git a/components/layouts/dataSourcesContainer.module.css b/components/layouts/dataSourcesContainer.module.css new file mode 100644 index 000000000..fc6f85257 --- /dev/null +++ b/components/layouts/dataSourcesContainer.module.css @@ -0,0 +1,3 @@ +.Container { + @apply grid grid-cols-8 gap-4 mb-12; +} diff --git a/components/layouts/flex.js b/components/layouts/flex.js new file mode 100644 index 000000000..c4d158c5f --- /dev/null +++ b/components/layouts/flex.js @@ -0,0 +1,8 @@ +import styles from "./flex.module.css"; + +// Simple horizontal flex container used for MDX. +const Flex = ({ children }) => { + return
{children}
; +}; + +export default Flex; diff --git a/components/layouts/flex.module.css b/components/layouts/flex.module.css new file mode 100644 index 000000000..3940af968 --- /dev/null +++ b/components/layouts/flex.module.css @@ -0,0 +1,3 @@ +.Container { + @apply flex flex-col lg:flex-row; +} diff --git a/components/layouts/globalTemplate.js b/components/layouts/globalTemplate.js new file mode 100644 index 000000000..62259d1ef --- /dev/null +++ b/components/layouts/globalTemplate.js @@ -0,0 +1,32 @@ +import React, { useState, useEffect } from "react"; +import Header from "../navigation/header"; + +import styles from "./globalTemplate.module.css"; + +const Layout = ({ children }) => { + const [isSticky, setIsSticky] = useState(false); + + const handleScroll = () => { + let top = window.scrollY; + top > 20 ? setIsSticky(true) : setIsSticky(false); + }; + + useEffect(() => { + window.addEventListener("scroll", handleScroll); + + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, []); + + return ( +
+
+
+ {children} +
+
+ ); +}; + +export default Layout; diff --git a/components/layouts/globalTemplate.module.css b/components/layouts/globalTemplate.module.css new file mode 100644 index 000000000..05d7813e7 --- /dev/null +++ b/components/layouts/globalTemplate.module.css @@ -0,0 +1,3 @@ +.stickyPageWrapper { + @apply pt-24; +} diff --git a/components/layouts/inlineCalloutContainer.js b/components/layouts/inlineCalloutContainer.js new file mode 100644 index 000000000..46f726ca8 --- /dev/null +++ b/components/layouts/inlineCalloutContainer.js @@ -0,0 +1,7 @@ +import styles from "./inlineCalloutContainer.module.css"; + +const InlineCalloutContainer = ({ children }) => { + return
{children}
; +}; + +export default InlineCalloutContainer; diff --git a/components/layouts/inlineCalloutContainer.module.css b/components/layouts/inlineCalloutContainer.module.css new file mode 100644 index 000000000..69a3ae240 --- /dev/null +++ b/components/layouts/inlineCalloutContainer.module.css @@ -0,0 +1,7 @@ +.Container { + @apply mb-24; +} + +.Container > section:last-child { + @apply border-none mb-8 pb-0; +} diff --git a/components/layouts/masonry.js b/components/layouts/masonry.js new file mode 100644 index 000000000..1f6d319c5 --- /dev/null +++ b/components/layouts/masonry.js @@ -0,0 +1,44 @@ +import { debounce } from "lodash"; +import React, { useState, useEffect } from "react"; + +import styles from "./masonry.module.css"; + +const Masonry = ({ children }) => { + const [height, setHeight] = useState(2000); + + const maxColumnHeight = () => { + const childrenDOMElements = document.querySelectorAll(".masonry > *"); + let columnHeights = [0, 0, 0]; + + for (let index = 0; index < childrenDOMElements.length; index++) { + let row = index % 3; + columnHeights[row] = + columnHeights[row] + childrenDOMElements[index].offsetHeight; + if (index == 0) { + childrenDOMElements[index].classList.add("top-left"); + } else if (index == 2) { + childrenDOMElements[index].classList.add("top-right"); + } + } + return Math.max(...columnHeights) + 5; + }; + + const updateMaxheight = () => { + setHeight(maxColumnHeight()); + }; + + const debouncedUpdateMaxheight = debounce(updateMaxheight, 200); + + useEffect(() => { + updateMaxheight(); + window.addEventListener("resize", debouncedUpdateMaxheight); + + return () => { + window.removeEventListener("resize", debouncedUpdateMaxheight); + }; + }, []); + + return
{children}
; +}; + +export default Masonry; diff --git a/components/layouts/masonry.module.css b/components/layouts/masonry.module.css new file mode 100644 index 000000000..bbfcabdfa --- /dev/null +++ b/components/layouts/masonry.module.css @@ -0,0 +1,7 @@ +.Container { + @apply columns-1 lg:columns-2 2xl:columns-3 gap-8 max-w-full; +} + +.Container > * { + @apply break-inside-avoid; +} diff --git a/components/layouts/newsContainer.js b/components/layouts/newsContainer.js new file mode 100644 index 000000000..3d8093622 --- /dev/null +++ b/components/layouts/newsContainer.js @@ -0,0 +1,7 @@ +import styles from "./newsContainer.module.css"; + +const NewsContainer = ({ children }) => { + return
{children}
; +}; + +export default NewsContainer; diff --git a/components/layouts/newsContainer.module.css b/components/layouts/newsContainer.module.css new file mode 100644 index 000000000..bfc95ce52 --- /dev/null +++ b/components/layouts/newsContainer.module.css @@ -0,0 +1,7 @@ +.Container { + @apply mb-12; +} + +.Container article:last-child { + @apply border-none mb-8 pb-0; +} diff --git a/components/layouts/tileContainer.js b/components/layouts/tileContainer.js new file mode 100644 index 000000000..77c3a5ba0 --- /dev/null +++ b/components/layouts/tileContainer.js @@ -0,0 +1,7 @@ +import styles from "./tileContainer.module.css"; + +const TilesContainer = ({ children }) => { + return
{children}
; +}; + +export default TilesContainer; diff --git a/components/layouts/tileContainer.module.css b/components/layouts/tileContainer.module.css new file mode 100644 index 000000000..767615fae --- /dev/null +++ b/components/layouts/tileContainer.module.css @@ -0,0 +1,3 @@ +.Container { + @apply grid grid-cols-6 gap-4 mb-12; +} diff --git a/components/navigation/arrowLink.js b/components/navigation/arrowLink.js new file mode 100644 index 000000000..9db0c588f --- /dev/null +++ b/components/navigation/arrowLink.js @@ -0,0 +1,72 @@ +import Link from "next/link"; + +import styles from "./arrowLink.module.css"; + +const ArrowLink = ({ children, link, type, content, target }) => { + function ArrowType() { + const href = link || "#"; + if (type == "back") { + return ( + + + + + + Previous: + {content} + + + ); + } else if (type == "next") { + return ( + + + Next: + {content} + + + + + + ); + } + } + return ; +}; + +export default ArrowLink; diff --git a/components/navigation/arrowLink.module.css b/components/navigation/arrowLink.module.css new file mode 100644 index 000000000..2a4b9eda3 --- /dev/null +++ b/components/navigation/arrowLink.module.css @@ -0,0 +1,39 @@ +.Container { + @apply flex flex-wrap flex-col md:flex-row md:flex-nowrap items-center justify-between mt-16 mb-8 gap-4; +} + +.Link { + @apply overflow-hidden border-none text-base md:text-lg tracking-tight font-sans font-normal text-gray-90 leading-7 hover:opacity-70 flex items-center transition-all; +} + +.Truncate { + @apply truncate; +} + +:global(.dark) .Link { + @apply text-white !important; +} + +.NextLink { + @apply mt-0 sm:mt-0; +} + +.Icon { + @apply relative group-hover:scale-105 fill-gray-90; +} + +.BackIcon { + @apply mr-2 group-hover:-left-1; +} + +.NextIcon { + @apply ml-2 group-hover:-left-1; +} + +:global(.dark) .Icon { + @apply fill-white !important; +} + +.Text { + @apply mr-1 font-bold; +} diff --git a/components/navigation/arrowLinkContainer.js b/components/navigation/arrowLinkContainer.js new file mode 100644 index 000000000..f7be14d3f --- /dev/null +++ b/components/navigation/arrowLinkContainer.js @@ -0,0 +1,7 @@ +import styles from "./arrowLink.module.css"; + +const ArrowLinkContainer = ({ children }) => { + return
{children}
; +}; + +export default ArrowLinkContainer; diff --git a/components/navigation/footer.js b/components/navigation/footer.js new file mode 100644 index 000000000..7e0f38b0d --- /dev/null +++ b/components/navigation/footer.js @@ -0,0 +1,313 @@ +import Link from "next/link"; + +import styles from "./footer.module.css"; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/components/navigation/footer.module.css b/components/navigation/footer.module.css new file mode 100644 index 000000000..2a9778236 --- /dev/null +++ b/components/navigation/footer.module.css @@ -0,0 +1,82 @@ +.Container { + @apply col-span-full lg:col-start-2 lg:col-end-5; +} + +.InnerContainer { + @apply flex flex-col items-center justify-between py-8; +} + +.Separator { + @apply w-full border-t-gray-30; +} + +:global(.dark) .Separator { + @apply border-t-gray-90 !important; +} + +.Navigation { + @apply flex gap-8 mt-4 mb-2; +} + +.Link { + @apply text-gray-90 text-base tracking-tight leading-7 font-bold hover:opacity-70; +} + +:global(.dark) .Link { + @apply text-white !important; +} + +.SocialNetworks { + @apply flex flex-wrap justify-center items-center gap-4; +} + +.IconLink { + @apply relative rounded-full overflow-hidden flex justify-center items-center px-2 sm:p-2 hover:opacity-100 hover:scale-105 hover:bg-gray-10; +} + +.IconContainer { + @apply text-base; +} + +.Icon { + @apply w-6 h-6 flex fill-gray-60; +} + +/* Colors for hover states */ +.IconLink:hover .GitHubIcon { + @apply fill-brand-GitHub; +} + +.IconLink:hover .RedditIcon { + @apply fill-brand-Reddit; +} + +.IconLink:hover .DiscordIcon { + @apply fill-brand-Discord; +} + +.IconLink:hover .YouTubeIcon { + @apply fill-brand-YouTube; +} + +.IconLink:hover .TwitterIcon { + @apply fill-brand-Twitter; +} + +.IconLink:hover .LinkedInIcon { + @apply fill-brand-LinkedIn; +} + +.IconLink:hover .InstagramIcon, +.IconLink:hover .MediumIcon { + @apply fill-brand-mono; +} + +.IconLink:hover .NewsletterIcon path { + @apply stroke-red-70; +} + +.Copyright { + @apply text-base tracking-tight leading-7 mt-2; + @apply text-gray-60 !important; +} diff --git a/components/navigation/header.js b/components/navigation/header.js new file mode 100644 index 000000000..3a55794d9 --- /dev/null +++ b/components/navigation/header.js @@ -0,0 +1,64 @@ +import { debounce } from "lodash"; +import React, { useState, useEffect } from "react"; +import classNames from "classnames"; +import Link from "next/link"; +import dynamic from "next/dynamic"; + +// import Navigation from +import MobileNav from "./mobileNav"; + +import styles from "./header.module.css"; + +const ThemeToggle = dynamic(() => import("../utilities/themeToggle"), { + ssr: false, +}); +import Search from "../utilities/search"; + +const Header = ({ isSticky }) => { + const [windowWidth, setWindowWidth] = useState(); + + const handleResize = () => { + setWindowWidth(window.innerWidth); + }; + + const debouncedHandleResize = debounce(handleResize, 200); + + useEffect(() => { + handleResize(); + window.addEventListener("resize", debouncedHandleResize); + + return () => { + window.removeEventListener("resize", debouncedHandleResize); + }; + }, []); + + let mobileNav; + if (windowWidth <= 1024) { + mobileNav = ; + } + + return ( +
+ +
+ ); +}; + +export default Header; diff --git a/components/navigation/header.module.css b/components/navigation/header.module.css new file mode 100644 index 000000000..3da2ffb04 --- /dev/null +++ b/components/navigation/header.module.css @@ -0,0 +1,49 @@ +.Container { + @apply relative w-screen z-30 border-b; + @apply bg-white !important; +} + +.stickyContainer { + @apply fixed h-12 border-b-gray-30 top-0; +} + +.standardContainer { + @apply border-b-white h-24; +} + +:global(.dark) .Container { + @apply bg-gray-100 !important; +} + +:global(.dark) .stickyContainer { + @apply border-b-gray-80 !important; +} + +:global(.dark) .standardContainer { + @apply border-b-gray-100 !important; +} + +.Navigation { + @apply container px-2 xl:px-4 flex items-center justify-between relative transition-all mx-auto h-full left-auto; +} + +.LogoContainer { + @apply flex items-center m-0 text-base tracking-tight md:ml-0 lg:ml-2 xl:-ml-1 hover:opacity-70; +} + +.LogoText { + @apply hidden xl:block m-0 font-normal ml-4 relative top-px text-lg; + @apply text-gray-90 !important; +} + +:global(.dark) .LogoText { + @apply text-white !important; +} + +.NavigationContainer { + @apply flex items-center flex-auto justify-self-end gap-4 lg:gap-6; +} + +.Container nav section i { + @apply text-gray-70; +} diff --git a/components/navigation/mobileNav.js b/components/navigation/mobileNav.js new file mode 100644 index 000000000..2e5c1f97e --- /dev/null +++ b/components/navigation/mobileNav.js @@ -0,0 +1,47 @@ +import React, { useState, useEffect } from "react"; +import bus from "../../lib/bus"; +import router from "next/router"; + +import styles from "./mobileNav.module.css"; + +const MobileNav = () => { + const [nav, setNav] = useState(false); + + const toggleMobileNav = () => { + bus.emit(nav ? "streamlit_nav_closed" : "streamlit_nav_open"); + if (nav) { + document.documentElement.classList.remove("nav-open"); + } else { + document.documentElement.classList.add("nav-open"); + } + setNav(!nav); + }; + + const handleRouteChange = () => { + if (nav) { + bus.emit(nav ? "streamlit_nav_closed" : "streamlit_nav_open"); + if (nav) { + document.documentElement.classList.remove("nav-open"); + } else { + document.documentElement.classList.add("nav-open"); + } + setNav(false); + } + }; + + useEffect(() => { + router.events.on("routeChangeComplete", handleRouteChange); + }, []); + + let mobileNav; + + mobileNav = ( + + ); + + return mobileNav; +}; + +export default MobileNav; diff --git a/components/navigation/mobileNav.module.css b/components/navigation/mobileNav.module.css new file mode 100644 index 000000000..9024694fd --- /dev/null +++ b/components/navigation/mobileNav.module.css @@ -0,0 +1,16 @@ +.MobileNav { + @apply relative flex items-center justify-center w-8 h-8 mb-0 rounded-md; + @apply bg-gray-20 !important; +} + +:global(.dark) .MobileNav { + @apply bg-gray-90 !important; +} + +.Icon { + @apply transition-all; +} + +:global(.dark) .Icon { + @apply text-white; +} diff --git a/components/navigation/navChild.js b/components/navigation/navChild.js new file mode 100644 index 000000000..55a349734 --- /dev/null +++ b/components/navigation/navChild.js @@ -0,0 +1,127 @@ +import React, { useState } from "react"; +import Link from "next/link"; +import classNames from "classnames"; + +import useVersion from "../../lib/useVersion.js"; + +import styles from "./navChild.module.css"; + +const NavChild = ({ slug, page, color, className }) => { + const [manualState, setManualState] = useState(null); + const version = useVersion(); + + const isNum = /^[\d\.]+$/.test(slug[0]); + + if (isNum) { + slug.shift(); + } + + const slugStr = `/${slug.join("/")}`; + const active = slugStr === page.url ? true : false; + const shouldAutoOpen = slugStr.startsWith(page.url); + const opened = manualState ?? shouldAutoOpen; + + let subNav; + + const toggleAccordion = () => { + setManualState(!opened); + }; + + const visibleItems = page.children.filter((child) => child.visible !== false); + if (page.children?.length > 0 && visibleItems.length > 0 && opened) { + subNav = ( +
    + {page.children + .filter((child) => child.visible !== false) + .map((child) => ( + + ))} +
+ ); + } + + let accordion; + + if (page.children?.length > 0 && visibleItems.length > 0) { + accordion = ( +
+ + {opened ? "remove" : "add"} + +
+ ); + } + + let link; + let icon; + let target; + let url = page.url; + + const isLocalPage = page.url.startsWith("/"); + + if (!isLocalPage) { + icon = open_in_new; + target = "_blank"; + } + + if (page.isVersioned && version && isLocalPage) { + // We need to version this URL, check if the URL has a version for this version + const newSlug = page.url.split("/"); + newSlug[0] = version; + url = `/${newSlug.join("/")}`; + } + + link = ( + + + + + + {page.name} + + {page.isDeprecated === true ? ( + + {"delete"} + + ) : ( + icon + )} + + + {accordion} + + ); + + return ( +
  • + {link} + {subNav} +
  • + ); +}; + +export default NavChild; diff --git a/components/navigation/navChild.module.css b/components/navigation/navChild.module.css new file mode 100644 index 000000000..32dc22458 --- /dev/null +++ b/components/navigation/navChild.module.css @@ -0,0 +1,75 @@ +.Container { + @apply text-sm tracking-tight dark:text-white mb-4; +} + +.List { + @apply list-none mt-4; +} + +.Accordion { + @apply border rounded-md transition-all ml-2 flex items-center justify-center hover:opacity-50 h-4 w-4; +} + +.AccordionIcon { + @apply relative z-10 cursor-pointer text-sm; + @apply text-gray-80 !important; +} + +.ExternalIcon { + @apply relative z-10 cursor-pointer text-sm ml-1; + @apply text-gray-80 !important; +} + +:global(.dark) .AccordionIcon, +:global(.dark) .ExternalIcon, +:global(.dark) .ActivePage, +:global(.dark) .ActivePage ~ .Icon { + @apply text-white !important; +} + +.LinkContainer { + @apply flex items-center; +} + +.Link { + @apply relative flex items-center hover:opacity-70; + @apply text-gray-80 !important; +} + +.Icon { + @apply text-base relative left-1 -top-[1px]; +} + +.Circle { + @apply absolute w-2 h-2 -left-4 rounded-full hidden; +} + +.ActiveCircle { + @apply block; +} + +:global(.dark) .PageName:not(.ActivePage) { + @apply text-gray-40 !important; +} + +.ActivePage { + @apply font-bold; + @apply text-gray-90 !important; +} + +.ActivePage ~ .Icon { + @apply text-gray-90; +} + +/* Colors for circles */ +.LibraryCircle { + @apply bg-indigo-70; +} + +.CloudCircle { + @apply bg-lightBlue-70; +} + +.KBCircle { + @apply bg-orange-70; +} diff --git a/components/navigation/navItem.js b/components/navigation/navItem.js new file mode 100644 index 000000000..ac0141b82 --- /dev/null +++ b/components/navigation/navItem.js @@ -0,0 +1,109 @@ +import React from "react"; +import Link from "next/link"; +import classNames from "classnames"; + +import { urlInChildren } from "../../lib/utils.js"; +import NavChild from "./navChild"; + +import styles from "./navItem.module.css"; + +const NavItem = ({ page, slug, condensed, className }) => { + let subNav; + let navItem; + let navBox; + let active = urlInChildren(page, `/${slug.join("/")}`); + let isCondensed = condensed ? condensed : false; + + // We only want the color to show when we're either active, or the menu is condensed. + let color = + page.color === "violet-70" + ? styles.LibraryCategory + : page.color === "l-blue-70" + ? styles.CloudCategory + : styles.KBCategory; + color = isCondensed || active ? color : ""; + + navBox = ( +
    +
    + {page.icon} +
    +

    + {page.name} +

    +
    + ); + + if (page.children && page.children.length > 0) { + subNav = ( +
      + {page.children.map((child) => ( + + ))} +
    + ); + } + + if (page.url.startsWith("/")) { + navItem = ( +
  • + {page.url === "/library" ? ( + + {navBox} + + ) : ( + + {navBox} + + )} + {subNav} +
  • + ); + } else { + navItem = ( +
  • + + {navBox} + + {subNav} +
  • + ); + } + + return navItem; +}; + +export default NavItem; diff --git a/components/navigation/navItem.module.css b/components/navigation/navItem.module.css new file mode 100644 index 000000000..5e8f4dcb5 --- /dev/null +++ b/components/navigation/navItem.module.css @@ -0,0 +1,75 @@ +.HeadingContainer { + @apply relative flex items-center xl:justify-start xl:flex-nowrap xl:text-left xl:w-auto transition-all left-auto lg:left-2 xl:left-auto; +} + +.HeadingIconContainer { + @apply flex items-center p-2 mr-4 xl:mr-4 mb-0 rounded-md; +} + +.CondensedHeadingContainer { + @apply lg:flex-wrap lg:justify-center lg:text-center lg:w-min; +} + +.CondensedHeadingIconContainer { + @apply lg:mr-0; +} + +.Icon { + @apply text-white !important; +} + +/* Colors for heading icons */ +.LibraryIcon { + @apply bg-indigo-70; +} + +.CloudIcon { + @apply bg-lightBlue-70; +} + +.KBIcon { + @apply bg-orange-70; +} + +.CategoryName { + @apply m-0 xl:my-0 font-bold font-sans xl:text-base xl:leading-6; + @apply text-gray-80 !important; +} + +:global(.dark) .CategoryName { + @apply text-white !important; +} + +.CondensedCategoryName { + @apply lg:my-1 lg:text-xs lg:leading-tight lg:tracking-tight; +} + +/* Colors for active categories */ +.LibraryCategory { + @apply text-indigo-70 !important; +} + +.CloudCategory { + @apply text-lightBlue-70 !important; +} + +.KBCategory { + @apply text-orange-70 !important; +} + +/* Subnav styles */ +.SubNav { + @apply block pl-14 m-0 mt-4 list-none; +} + +.CondensedSubNav { + @apply lg:hidden; +} + +.ExpandedSubNav { + @apply lg:block; +} + +.NavItem { + @apply mb-8; +} diff --git a/components/navigation/sideBar.js b/components/navigation/sideBar.js new file mode 100644 index 000000000..e1829b6ec --- /dev/null +++ b/components/navigation/sideBar.js @@ -0,0 +1,118 @@ +import { debounce } from "lodash"; +import React, { useState, useEffect } from "react"; +import classNames from "classnames"; + +import bus from "../../lib/bus"; +import NavItem from "../navigation/navItem"; + +import styles from "./sideBar.module.css"; + +const SideBar = ({ menu, slug }) => { + const [isCondensed, setIsCondensed] = useState(false); + const [isSticky, setIsSticky] = useState("none"); + const [isOver, setIsOver] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [theme, setTheme] = useState("light-mode"); + const [hasSlug, setHasSlug] = useState(""); + + const handleTheme = () => { + setTheme(document.body.dataset.theme); + }; + + const handleMouseEnter = () => { + if (window.innerWidth < 1250 && window.innerWidth >= 1024) { + setIsCondensed(false); + setIsOver(true); + } + }; + + const handleMouseLeave = () => { + if (window.innerWidth < 1250 && window.innerWidth >= 1024) { + setIsCondensed(true); + setIsOver(false); + } + }; + + const checkExpanded = () => { + if (window.innerWidth < 1250 && window.innerWidth >= 1024) { + setIsCondensed(true); + } else { + setIsCondensed(false); + } + }; + + const handleScroll = (e) => { + // We check if the user scrolled on the whole page + const windowScroll = window.scrollY; + // But we also check if the user hasn't scrolled on the page, but scrolled inside the sidebar + const sideBarScroll = e.target.scrollTop; + + if (windowScroll > 20) { + setIsSticky("window"); + } else if (sideBarScroll > 5) { + setIsSticky("scrollbar"); + } else { + setIsSticky("none"); + } + }; + + const debouncedCheckExpanded = debounce(checkExpanded, 200); + + useEffect(() => { + window.addEventListener("resize", debouncedCheckExpanded); + window.addEventListener("scroll", handleScroll); + window.addEventListener("ChangeTheme", handleTheme); + + bus.on("streamlit_nav_open", () => setIsOpen(true)); + bus.on("streamlit_nav_closed", () => setIsOpen(false)); + + checkExpanded(); + setHasSlug(window.location.href); + + return () => { + window.removeEventListener("resize", debouncedCheckExpanded); + window.removeEventListener("scroll", handleScroll); + window.removeEventListener("ChangeTheme", handleTheme); + }; + }, []); + + let navItems; + navItems = menu.map((page) => ( + + )); + + return ( +
    handleScroll(e)} + > +
    + +
    + ); +}; + +export default SideBar; diff --git a/components/navigation/sideBar.module.css b/components/navigation/sideBar.module.css new file mode 100644 index 000000000..2068ece5e --- /dev/null +++ b/components/navigation/sideBar.module.css @@ -0,0 +1,59 @@ +.Container { + @apply fixed lg:sticky top-auto lg:top-12 left-0 xl:left-auto px-0 h-screen z-20 w-10/12 md:w-9/12 xl:w-screen max-w-xs md:max-w-none overflow-y-auto shadow-lg lg:shadow-none transition-all; + @apply bg-white !important; +} + +:global(.dark) .Container { + @apply bg-gray-100 !important; +} + +.OpenNav { + @apply block md:max-w-xs; + @apply md:w-full !important; +} + +.ClosedNav { + @apply hidden lg:block; +} + +.CollapsedNav { + @apply w-full md:w-36 lg:w-full; +} + +.OverNav { + @apply md:w-full; +} + +.OverNavItem { + @apply lg:hidden xl:block; +} + +.NavList { + @apply list-none overscroll-contain m-0 px-2 lg:px-0; +} + +/* Top gradient styles when scrolling */ +.TopGradient { + @apply hidden lg:block fixed top-0 w-full bg-gradient-to-b from-white z-10 max-w-[280px] lg:max-w-[240px] xl:max-w-[280px]; +} + +:global(.dark) .TopGradient { + @apply from-gray-100; +} + +/* Sticky styles */ +.WindowStickyNav { + @apply top-0 lg:top-12; +} + +.WindowStickyNav nav { + @apply pt-16 lg:pt-4; +} + +.WindowStickyGradient { + @apply top-12 h-4; +} + +.ScrollBarStickyGradient { + @apply top-24 h-4; +} diff --git a/components/summaryTiles.js b/components/summaryTiles.js new file mode 100644 index 000000000..ddfdf5e71 --- /dev/null +++ b/components/summaryTiles.js @@ -0,0 +1,34 @@ +import TileContainer from "../components/layouts/tileContainer"; +import Tile from "../components/blocks/tile"; + +const SummaryTiles = () => { + return ( + + + + + + + + ); +}; + +export default SummaryTiles; diff --git a/components/utilities/breadCrumbs.js b/components/utilities/breadCrumbs.js new file mode 100644 index 000000000..221750d82 --- /dev/null +++ b/components/utilities/breadCrumbs.js @@ -0,0 +1,115 @@ +import React from "react"; +import { breadcrumbsForSlug } from "../../lib/utils.js"; +import Link from "next/link"; + +import styles from "./breadCrumbs.module.css"; + +const BreadCrumbs = ({ slug, menu }) => { + const formatedTitle = (title) => { + return `${title}`.replace(/\-/g, " ").replace(/\bapi\b/, "API"); + }; + + const createCrumb = (crumb, index, slug) => { + let formatedCrumb; + if (index == slug.length) { + formatedCrumb = ( + + + {crumb.title} + + + ); + } else { + formatedCrumb = ( + <> + + {crumb.title} + + / + + ); + } + return formatedCrumb; + }; + + const breadcrumbs = []; + if (slug === undefined) { + return ""; + } + let paths = slug.join("/"); + breadcrumbs.push({ + link: "/", + title: "Home", + }); + + const isnum = /^[\d\.]+$/.test(slug[0]); + if (isnum) { + paths = slug.slice(1).join("/"); + breadcrumbs.push({ + link: "#", + title: slug[0], + }); + } + + // Find the menu with the current slug + const location = `/${paths}`; + const path = breadcrumbsForSlug(menu, location, []); + + // If there's a missing entry in menu.md, throw a build error. + // But first, we should exclude files that aren't pages. + const filesToExclude = []; + + // Let's start with files that start with a dot, such as .keep and .DS_Store + if (slug.slice()[0].startsWith(".")) { + filesToExclude.push(slug.slice().join("/")); + } + + // Then, we add a couple pages that don't need breadcrumbs, such as /menu, /index, etc. + filesToExclude.push("index", "gdpr-banner", "menu"); + + // Now, we throw the error if any page that's not on the filesToExclude array is missing in menu.md + if (path.length === 0 && !filesToExclude.includes(slug[0])) { + throw new Error( + `This slug: ${slug + .slice() + .join( + "/" + )} doesn't have a corresponding entry in menu.md. Please add it, and if you don't want this entry to show up in the sidebar, add the "visible: false" property to the entry.` + ); + } + + path.forEach((obj) => { + if (obj.url === location) { + breadcrumbs.push({ + link: location, + title: formatedTitle(obj.name), + }); + } else { + breadcrumbs.push({ + link: obj.url, + title: formatedTitle(obj.name), + }); + } + }); + + if (breadcrumbs.length === 1) { + breadcrumbs.push({ + link: location, + title: paths, + }); + } + + return ( + + ); +}; + +export default BreadCrumbs; diff --git a/components/utilities/breadCrumbs.module.css b/components/utilities/breadCrumbs.module.css new file mode 100644 index 000000000..4e912c5f7 --- /dev/null +++ b/components/utilities/breadCrumbs.module.css @@ -0,0 +1,30 @@ +/* TODO: Remove !important when done moving to Tailwind */ +.Container { + @apply flex flex-wrap list-none ml-0 pl-0 mt-0 mb-1; +} + +.InnerContainer { + @apply text-sm mr-2 mb-0; +} + +.Link { + @apply hover:opacity-70; + @apply text-gray-70 !important; +} + +.ActiveLink { + @apply font-bold; +} + +:global(.dark) .Link { + @apply text-gray-50 !important; +} + +.Separator { + @apply ml-2; + @apply text-gray-50 !important; +} + +:global(.dark) .Separator { + @apply text-gray-70 !important; +} diff --git a/components/utilities/download.js b/components/utilities/download.js new file mode 100644 index 000000000..a2b2dddda --- /dev/null +++ b/components/utilities/download.js @@ -0,0 +1,12 @@ +import styles from "./download.module.css"; + +// Simple horizontal flex container used for MDX. +const Download = ({ href, children }) => { + return ( + + {children} + + ); +}; + +export default Download; diff --git a/components/utilities/download.module.css b/components/utilities/download.module.css new file mode 100644 index 000000000..b2886b9b5 --- /dev/null +++ b/components/utilities/download.module.css @@ -0,0 +1,8 @@ +.Link { + @apply font-sans text-base tracking-tight border-b border-b-gray-90 dark:border-b-white dark:hover:border-b-red-70 hover:border-b-red-70 hover:opacity-70; + @apply text-gray-90 !important; +} + +:global(.dark) .Link { + @apply text-white border-b-white !important; +} diff --git a/components/utilities/floatingNav.js b/components/utilities/floatingNav.js new file mode 100644 index 000000000..03c892933 --- /dev/null +++ b/components/utilities/floatingNav.js @@ -0,0 +1,185 @@ +import React, { useState, useEffect, useRef } from "react"; +import { withRouter } from "next/router"; + +import styles from "./floatingNav.module.css"; + +const useHeadingsData = (slug) => { + const [nestedHeadings, setNestedHeadings] = useState([]); + + useEffect(() => { + const headingElements = Array.from( + document.querySelectorAll( + "article.leaf-page h1, article.leaf-page h2, article.leaf-page h3, article.leaf-page h4, article.leaf-page h5, article.leaf-page h6" + ) + ); + + // Remove the first heading here, since we don't want to show the main title on the TOC + headingElements.shift(); + + const newNestedHeadings = getNestedHeadings(headingElements); + setNestedHeadings(newNestedHeadings); + }, [slug]); + + return { nestedHeadings }; +}; + +const getNestedHeadings = (headingElements) => { + const nestedHeadings = []; + + for (const index in headingElements) { + const ele = headingElements[index]; + + // Check if the heading belongs to a component. If so, let's skip it to avoid cluttering the side nav + const parentElem = ele.parentElement; + const isParentExternal = + (parentElem.href && parentElem.href.includes("github")) || + (parentElem.href && parentElem.href.includes("streamlit.app")); + + if (ele.getElementsByTagName === undefined || isParentExternal === true) { + continue; + } + const hrefs = ele.getElementsByTagName("a"); + if (hrefs.length > 0) { + const target = hrefs[0].getAttribute("href"); + nestedHeadings.push({ + label: ele.innerText, + target: target, + level: ele.tagName, + }); + } + } + + return nestedHeadings; +}; + +// This is the function that initializes the intersection observer, and attaches it to the elements we want to track, our page headings +const useIntersectionObserver = (slug) => { + const [activeId, setActiveId] = useState(); + + useEffect(() => { + // Get all links inside the headers we care about. + const headingLinks = Array.from( + document.querySelectorAll( + [ + "article.leaf-page h1 a:first-of-type", + "article.leaf-page h2 a:first-of-type", + "article.leaf-page h3 a:first-of-type", + "article.leaf-page h4 a:first-of-type", + "article.leaf-page h5 a:first-of-type", + "article.leaf-page h6 a:first-of-type", + ].join(",") + ) + ); + + // Function that will be called when the links enter/leave the screen. + const callback = (headings) => { + // Traverse backwards through all elements to find the bottom-most visible element. + // Set that as the active one. + for (let i = 0; i < headings.length; i++) { + if (headings[i].isIntersecting) { + setActiveId(headings[i].target.getAttribute("href")); + break; + } + } + }; + + // Create an intersection observer, to track when the links enter/leave. + const observer = new IntersectionObserver(callback, { + threshold: 1.0, + rootMargin: "0px 0px -200px 0px", + }); + + headingLinks.forEach((element) => observer.observe(element)); + return () => observer.disconnect(); + }, [slug]); + + return activeId; +}; + +const FloatingNav = ({ menu, slug }) => { + const { nestedHeadings } = useHeadingsData(slug); + const activeId = useIntersectionObserver(slug); + + return nestedHeadings.length > 1 ? ( +
    +
    +
      +
    1. + Contents +
    2. + +
    +
    + ) : ( + "" + ); +}; + +const Headings = ({ headings, activeId }) => { + // Function to get the unique hierarchies for the headings. + // For example, we could have [H1, H2, H3] but also [H1, H4], + // and we want the indentation to acommodate for these situations. + const uniqueHierarchies = [...new Set(headings.map((item) => item.level))]; + + const sortedHeadings = uniqueHierarchies.map((hierarchy, index) => + headings.filter((heading) => { + if (heading.level === hierarchy) { + heading.hierarchy = index; + } + return heading; + }) + ); + + return ( + <> + {sortedHeadings[0].map((heading, index) => ( + + ))} + + ); +}; + +const Heading = ({ heading, index, activeId }) => { + return ( +
  • + + {heading.label} + +
  • + ); +}; + +export default withRouter(FloatingNav); diff --git a/components/utilities/floatingNav.module.css b/components/utilities/floatingNav.module.css new file mode 100644 index 000000000..54cead235 --- /dev/null +++ b/components/utilities/floatingNav.module.css @@ -0,0 +1,69 @@ +.ListContainer { + @apply absolute right-0 top-0 h-full w-56 z-10 hidden; +} + +.List { + @apply sticky top-24 m-0 pl-6 list-none overflow-y-auto overflow-x-hidden; + max-height: calc(100vh - (6em * 2)); +} + +.ListItem { + @apply m-0 text-xs pt-1; +} + +.TopGradient { + @apply absolute h-6 w-full bg-gradient-to-b from-white z-10; +} + +:global(.dark) .TopGradient { + @apply from-gray-100; +} + +/* Contents Title */ +.ListTitle { + @apply pt-6 uppercase font-semibold tracking-widest; + @apply text-gray-60 !important; +} + +/* Styles for the active item on the floating nav */ +:global(.dark) .activeLink { + @apply text-white !important; +} + +.activeLink { + @apply text-gray-90 !important; +} + +.Link { + @apply border-none inline-block pr-2 w-40 leading-4 hover:opacity-70 hover:no-underline; + @apply text-gray-70 !important; +} + +/* Paddings for different title hierarchies */ +.List li[data-hierarchy="0"] { + @apply pl-0; +} + +.List li[data-hierarchy="1"] { + @apply pl-6; +} + +.List li[data-hierarchy="2"] { + @apply pl-10; +} + +.List li[data-hierarchy="3"] { + @apply pl-12; +} + +.List li[data-hierarchy="4"] { + @apply pl-14; +} + +.List li[data-hierarchy="5"] { + @apply pl-16; +} + +.List li[data-hierarchy="6"] { + @apply pl-20; +} diff --git a/components/utilities/gdpr.js b/components/utilities/gdpr.js new file mode 100644 index 000000000..250c4f9da --- /dev/null +++ b/components/utilities/gdpr.js @@ -0,0 +1,160 @@ +// Global Imports +import React, { useState, useEffect } from "react"; +import { useRouter } from "next/router"; +import classNames from "classnames"; +import { MDXRemote } from "next-mdx-remote"; +import { ReactComponent as CookieEmoji } from "../../images/icons/cookie.svg"; + +import styles from "./gdpr.module.css"; + +const KEY = "InsertAnalyticsCode"; + +const GDPRBanner = (gdprData) => { + const title = gdprData.title; + const content = gdprData.content; + const data = gdprData.data; + + const router = useRouter(); + const path = router.asPath; + + const userIsInEurope = Intl.DateTimeFormat() + .resolvedOptions() + .timeZone.startsWith("Europe"); + + if (typeof window === "undefined") return null; + + const localStorageIsSetUp = localStorage.getItem(KEY) != null; + + // Default to use cookies when outside Europe. + if (!localStorageIsSetUp && !userIsInEurope) { + localStorage.setItem(KEY, true); + } + + // Only show banner if not in europe and banner wasn't already shown. + const showBanner = userIsInEurope && !localStorageIsSetUp; + + const [isVisible, setIsVisible] = useState(showBanner); + const [insertAnalyticsCode, setInsertAnalyticsCode] = useState( + localStorage.getItem(KEY) == "true" + ); + + const AllowAndCloseBanner = (e) => { + // Update state and set the decision into localStorage + setIsVisible(false); + const allow = true; + setInsertAnalyticsCode(allow); + localStorage.setItem(KEY, allow); + }; + + const DeclineAndCloseBanner = (e) => { + // Update state and set the decision into localStorage + setIsVisible(false); + const allow = false; + setInsertAnalyticsCode(allow); + localStorage.setItem(KEY, allow); + }; + + useEffect(() => { + if (insertAnalyticsCode) { + insertAnalytics(); + } + }, [insertAnalyticsCode]); + + if (!isVisible) { + return ""; + } + + return ( +
    +
    +
    + +
    +
    +

    {title}

    + +
    + + +
    +
    +
    +
    + ); +}; + +function insertAnalytics() { + (function () { + var analytics = (window.analytics = window.analytics || []); + if (!analytics.initialize) + if (analytics.invoked) + window.console && + console.error && + console.error("Segment snippet included twice."); + else { + analytics.invoked = !0; + analytics.methods = [ + "trackSubmit", + "trackClick", + "trackLink", + "trackForm", + "pageview", + "identify", + "reset", + "group", + "track", + "ready", + "alias", + "debug", + "page", + "once", + "off", + "on", + "addSourceMiddleware", + "addIntegrationMiddleware", + "setAnonymousId", + "addDestinationMiddleware", + ]; + analytics.factory = function (e) { + return function () { + var t = Array.prototype.slice.call(arguments); + t.unshift(e); + analytics.push(t); + return analytics; + }; + }; + for (var e = 0; e < analytics.methods.length; e++) { + var key = analytics.methods[e]; + analytics[key] = analytics.factory(key); + } + analytics.load = function (key, e) { + var t = document.createElement("script"); + t.type = "text/javascript"; + t.async = !0; + t.src = + "https://cdn.segment.com/analytics.js/v1/" + + key + + "/analytics.min.js"; + var n = document.getElementsByTagName("script")[0]; + n.parentNode.insertBefore(t, n); + analytics._loadOptions = e; + }; + analytics._writeKey = "pUoB6ihRTAFLDtLp2NWEuJvBNtiooQwE"; + analytics.SNIPPET_VERSION = "4.13.2"; + analytics.load("pUoB6ihRTAFLDtLp2NWEuJvBNtiooQwE"); + analytics.page(); + } + })(); +} + +export default GDPRBanner; diff --git a/components/utilities/gdpr.module.css b/components/utilities/gdpr.module.css new file mode 100644 index 000000000..21e0504fe --- /dev/null +++ b/components/utilities/gdpr.module.css @@ -0,0 +1,63 @@ +.Container { + @apply fixed bottom-0 md:bottom-4 md:right-4 w-full z-30 md:max-w-2xl; +} + +.BannerBackground { + @apply border-t border-t-gray-50 bg-gray-20 md:border md:border-gray-30 p-2 md:p-6 md:rounded-md flex flex-col md:flex-row; +} + +.Title { + @apply mt-0 mb-2 pt-2 md:pt-0 font-sans font-bold text-2xl tracking-tight leading-tight; + @apply text-gray-90; +} + +.ImageContainer { + @apply hidden md:block mb-4 md:mb-0 md:mt-2 mr-4; +} + +.CtasContainer { + @apply flex mt-4 md:mt-6; +} + +.Button { + @apply rounded-md px-8 py-1 text-red-70 text-sm font-semibold tracking-normal block cursor-pointer mb-2 hover:opacity-90 hover:scale-105; +} + +.DeclineButton { + @apply border border-solid bg-gray-20 border-gray-70 text-gray-90 mr-4 hover:opacity-80; +} + +.AllowButton { + @apply bg-gray-90 text-white hover:opacity-80; +} + +/* MDX styles */ +.Container div p, +.Container div a { + @apply text-gray-90; +} + +.Container div a { + @apply text-gray-70; +} + +.Container div p { + @apply m-0; +} + +/* Dark mode modifiers */ +:global(.dark) .BannerBackground { + @apply bg-gray-90 border-t-gray-80 md:border-gray-80; +} + +:global(.dark) .Container div p { + @apply text-white; +} + +:global(.dark) .DeclineButton { + @apply bg-gray-90 text-white border-gray-80; +} + +:global(.dark) .AllowButton { + @apply bg-white text-gray-90; +} diff --git a/components/utilities/headerLink.js b/components/utilities/headerLink.js new file mode 100644 index 000000000..299e6a74b --- /dev/null +++ b/components/utilities/headerLink.js @@ -0,0 +1,77 @@ +import React, { useState, useEffect } from "react"; +import slugify from "slugify"; +import classNames from "classnames"; + +import styles from "./headerLink.module.css"; + +const HeaderLink = ({ name, level, className, children }) => { + const hash = name + ? slugify(name, { remove: /[^A-Za-z0-9_\s]/g, lower: true }) + : ""; + const [copied, setCopied] = useState(false); + + useEffect(() => { + if (window.location.hash !== `#${hash}`) { + return; + } + + const el = document.querySelector(`[name=${hash}]`); + if (el) { + el.scrollIntoView(true); + } + }, []); + + const copyLinkUnbound = async () => { + const link = `${window.location.host}${window.location.pathname}#${hash}`; + await navigator.clipboard.writeText(link); + window.location.hash = hash; + + setCopied(true); + window.setTimeout(() => setCopied(false), 2000); + }; + + // Check if there's a tag in the heading, so we can style it properly. + // If we're getting something back, that means there is one, so we add the special class + const withInlineCode = Array.from(children).find( + (child) => child && child.props && child.props.mdxType === "inlineCode" + ); + + const Header = `h${level}`; + return ( + <> + + +
    + {withInlineCode ? {children} : children} + + {copied ? ( +
    + done + Copied +
    + ) : ( +
    + + + +
    + )} +
    + + ); +}; + +export default HeaderLink; diff --git a/components/utilities/headerLink.module.css b/components/utilities/headerLink.module.css new file mode 100644 index 000000000..4ed6b0334 --- /dev/null +++ b/components/utilities/headerLink.module.css @@ -0,0 +1,34 @@ +.HashLink { + @apply no-underline block h-0 w-0 overflow-hidden invisible pointer-events-none; +} + +.HeaderContainer { + @apply flex items-center font-bold text-gray-90 leading-snug; +} + +.HeaderContainer strong { + @apply font-bold; +} + +h1.HeaderContainer { + @apply leading-tight; +} + +.CopiedMessage { + @apply ml-4 text-xs font-normal opacity-70 flex items-center tracking-tight; + @apply text-gray-90 !important; +} + +.CopiedIcon { + @apply text-gray-90 !important; +} + +:global(.dark) .HeaderContainer, +:global(.dark) .CopiedMessage, +:global(.dark) .CopiedIcon { + @apply text-white !important; +} + +.CopyLink { + @apply cursor-pointer ml-4 pt-1 opacity-0 flex items-center hover:opacity-90 group-hover:opacity-90; +} diff --git a/components/utilities/helpful.js b/components/utilities/helpful.js new file mode 100644 index 000000000..eae4228d4 --- /dev/null +++ b/components/utilities/helpful.js @@ -0,0 +1,235 @@ +import React, { useState, useEffect, useRef } from "react"; +import pull from "lodash/pull"; +import router, { withRouter } from "next/router"; + +import styles from "./helpful.module.css"; + +import SuggestEdits from "./suggestEdits"; + +const Helpful = ({ slug, sourcefile }) => { + const formRef = useRef(); + const [step, setStep] = useState(0); + const [isHelpful, setIsHelpful] = useState(true); + const [feedback, setFeedback] = useState({ + other: false, + improvements: [], + notes: "", + improvementsString: "", + moreExamples: false, + clearerSteps: false, + moreInformation: false, + other: false, + }); + + const handleStep = (newStep) => { + setStep(newStep); + if (newStep == 1) { + setStep(1); + setIsHelpful(false); + } + if (newStep == 2) { + submitForm(); + } + }; + + const submitForm = () => { + if (formRef && formRef.current) { + const data = new FormData(formRef.current); + fetch("/", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams(data).toString(), + }); + } + }; + + const handleOther = () => { + setFeedback({ ...feedback, other: !feedback.other }); + }; + + const handleImprovement = (e) => { + const improvements = feedback.improvements.slice(); + const target = e.target; + const value = target.type === "checkbox" ? target.checked : target.value; + const name = target.name; + + if (value && !improvements.includes(name)) { + improvements.push(name); + } + + if (!value && improvements.includes(name)) { + pull(improvements, name); + } + + setFeedback({ + ...feedback, + [name]: value, + improvements, + improvementsString: improvements.join(","), + }); + }; + + const handleNoteChange = (event) => { + setFeedback({ ...feedback, notes: event.target.value }); + }; + + const handleRouteChange = () => { + setFeedback({ + ...feedback, + improvements: [], + improvementsString: "", + }); + setStep(0); + setIsHelpful(true); + }; + + // Perform the route change cleanup function for the Helpful component inside a useEffect call, + // instead of using router.events.on("routeChangeComplete", handleRouteChange), because that + // adds new events progressively as you keep browsing the website, thus eventually leading to a memory leak. + useEffect(() => { + return () => { + handleRouteChange(); + }; + }, [sourcefile]); + + let joinedSlug = "/"; + if (slug) { + joinedSlug = `/${slug.join("/")}`; + } + + let otherText; + if (feedback.other) { + otherText = ( +