Repository: JedWatson/react-select Branch: master Commit: 052e864b4990 Files: 302 Total size: 885.2 KB Directory structure: gitextract_m207rt_u/ ├── .browserslistrc ├── .changeset/ │ ├── README.md │ ├── config.json │ └── getChangelogEntry.js ├── .circleci/ │ └── config.yml ├── .codesandbox/ │ └── ci.json ├── .coveralls.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── config.yml │ ├── dependabot.yml │ └── workflows/ │ └── release.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── cypress/ │ ├── fixtures/ │ │ └── selectors.json │ ├── integration/ │ │ ├── multi-select.spec.ts │ │ └── single-select.spec.ts │ └── tsconfig.json ├── cypress.json ├── docs/ │ ├── App/ │ │ ├── Footer.tsx │ │ ├── GitHubButton.tsx │ │ ├── Header.tsx │ │ ├── PageNav.tsx │ │ ├── ScrollSpy.tsx │ │ ├── Section.tsx │ │ ├── Sticky.tsx │ │ ├── TwitterButton.tsx │ │ ├── components.tsx │ │ ├── index.tsx │ │ └── routes.ts │ ├── CHANGELOG.md │ ├── ExampleWrapper.tsx │ ├── NoMatch.tsx │ ├── PropTypes/ │ │ ├── Async.ts │ │ ├── Creatable.ts │ │ ├── Select.ts │ │ ├── components/ │ │ │ ├── ClearIndicator.ts │ │ │ ├── Control.ts │ │ │ ├── DropdownIndicator.ts │ │ │ ├── Group.ts │ │ │ ├── IndicatorsContainer.ts │ │ │ ├── IndicatorsSeparator.ts │ │ │ ├── Input.ts │ │ │ ├── LoadingIndicator.ts │ │ │ ├── LoadingMessage.ts │ │ │ ├── Menu.ts │ │ │ ├── MenuList.ts │ │ │ ├── MultiValue.ts │ │ │ ├── MultiValueContainer.ts │ │ │ ├── MultiValueLabel.ts │ │ │ ├── MultiValueRemove.ts │ │ │ ├── NoOptionsMessage.ts │ │ │ ├── Option.ts │ │ │ ├── Placeholder.ts │ │ │ ├── SelectContainer.ts │ │ │ ├── SingleValue.ts │ │ │ └── ValueContainer.ts │ │ └── stateManager.ts │ ├── Svg.tsx │ ├── Table.tsx │ ├── Tests.tsx │ ├── _redirects │ ├── data.ts │ ├── examples/ │ │ ├── AccessingInternals.tsx │ │ ├── AnimatedMulti.tsx │ │ ├── AsyncCallbacks.tsx │ │ ├── AsyncCreatable.tsx │ │ ├── AsyncMulti.tsx │ │ ├── AsyncPromises.tsx │ │ ├── BasicGrouped.tsx │ │ ├── BasicMulti.tsx │ │ ├── BasicSingle.tsx │ │ ├── ControlledMenu.tsx │ │ ├── CreatableAdvanced.tsx │ │ ├── CreatableInputOnly.tsx │ │ ├── CreatableMulti.tsx │ │ ├── CreatableSingle.tsx │ │ ├── CreateFilter.tsx │ │ ├── CustomAriaLive.tsx │ │ ├── CustomClearIndicator.tsx │ │ ├── CustomControl.tsx │ │ ├── CustomDropdownIndicator.tsx │ │ ├── CustomFilterOptions.tsx │ │ ├── CustomGetOptionLabel.tsx │ │ ├── CustomGetOptionValue.tsx │ │ ├── CustomGroup.tsx │ │ ├── CustomGroupHeading.tsx │ │ ├── CustomIndicatorSeparator.tsx │ │ ├── CustomIndicatorsContainer.tsx │ │ ├── CustomInput.tsx │ │ ├── CustomIsOptionDisabled.tsx │ │ ├── CustomLoadingIndicator.tsx │ │ ├── CustomLoadingMessage.tsx │ │ ├── CustomMenu.tsx │ │ ├── CustomMenuList.tsx │ │ ├── CustomMultiValueContainer.tsx │ │ ├── CustomMultiValueLabel.tsx │ │ ├── CustomMultiValueRemove.tsx │ │ ├── CustomNoOptionsMessage.tsx │ │ ├── CustomOption.tsx │ │ ├── CustomPlaceholder.tsx │ │ ├── CustomSelectContainer.tsx │ │ ├── CustomSelectProps.tsx │ │ ├── CustomSingleValue.tsx │ │ ├── CustomValueContainer.tsx │ │ ├── DefaultOptions.tsx │ │ ├── Experimental.tsx │ │ ├── FixedOptions.tsx │ │ ├── MenuBuffer.tsx │ │ ├── MenuPortal.tsx │ │ ├── MultiSelectSort.tsx │ │ ├── OnSelectResetsInput.tsx │ │ ├── Popout.tsx │ │ ├── StyleCompositionExample.tsx │ │ ├── StyledMulti.tsx │ │ ├── StyledSingle.tsx │ │ ├── Theme.tsx │ │ └── index.tsx │ ├── generate-magical-types/ │ │ ├── generate/ │ │ │ └── package.json │ │ ├── package.json │ │ ├── serialize/ │ │ │ └── package.json │ │ └── src/ │ │ ├── generate.ts │ │ ├── serialize.ts │ │ └── types.ts │ ├── index.css │ ├── index.html │ ├── index.tsx │ ├── isArray.ts │ ├── markdown/ │ │ ├── renderer.tsx │ │ └── store.ts │ ├── package.json │ ├── pages/ │ │ ├── advanced/ │ │ │ └── index.tsx │ │ ├── async/ │ │ │ └── index.tsx │ │ ├── components/ │ │ │ └── index.tsx │ │ ├── creatable/ │ │ │ └── index.tsx │ │ ├── home/ │ │ │ └── index.tsx │ │ ├── props/ │ │ │ └── index.tsx │ │ ├── styles/ │ │ │ └── index.tsx │ │ ├── typescript/ │ │ │ └── index.tsx │ │ ├── upgrade/ │ │ │ └── index.tsx │ │ └── upgrade-to-v2/ │ │ ├── index.tsx │ │ └── props.tsx │ ├── styled-components.tsx │ ├── tsconfig.json │ ├── utils.ts │ └── webpack.config.ts ├── netlify.toml ├── package.json ├── packages/ │ └── react-select/ │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── animated/ │ │ └── package.json │ ├── async/ │ │ └── package.json │ ├── async-creatable/ │ │ └── package.json │ ├── base/ │ │ └── package.json │ ├── creatable/ │ │ └── package.json │ ├── package.json │ ├── src/ │ │ ├── Async.tsx │ │ ├── AsyncCreatable.tsx │ │ ├── Creatable.tsx │ │ ├── NonceProvider.tsx │ │ ├── Select.tsx │ │ ├── __tests__/ │ │ │ ├── Async.test.tsx │ │ │ ├── AsyncCreatable.test.tsx │ │ │ ├── Creatable.test.tsx │ │ │ ├── Select.test.tsx │ │ │ ├── StateManaged.test.tsx │ │ │ ├── __snapshots__/ │ │ │ │ ├── Async.test.tsx.snap │ │ │ │ ├── AsyncCreatable.test.tsx.snap │ │ │ │ ├── Creatable.test.tsx.snap │ │ │ │ ├── Select.test.tsx.snap │ │ │ │ └── StateManaged.test.tsx.snap │ │ │ ├── constants.ts │ │ │ └── tsconfig.json │ │ ├── accessibility/ │ │ │ ├── helpers.ts │ │ │ └── index.ts │ │ ├── animated/ │ │ │ ├── Input.tsx │ │ │ ├── MultiValue.tsx │ │ │ ├── Placeholder.tsx │ │ │ ├── SingleValue.tsx │ │ │ ├── ValueContainer.tsx │ │ │ ├── index.ts │ │ │ └── transitions.tsx │ │ ├── async/ │ │ │ └── index.ts │ │ ├── async-creatable/ │ │ │ └── index.ts │ │ ├── base/ │ │ │ └── index.ts │ │ ├── builtins.ts │ │ ├── components/ │ │ │ ├── Control.tsx │ │ │ ├── Group.tsx │ │ │ ├── Input.tsx │ │ │ ├── LiveRegion.tsx │ │ │ ├── Menu.tsx │ │ │ ├── MultiValue.tsx │ │ │ ├── Option.tsx │ │ │ ├── Placeholder.tsx │ │ │ ├── SingleValue.tsx │ │ │ ├── containers.tsx │ │ │ ├── index.ts │ │ │ └── indicators.tsx │ │ ├── creatable/ │ │ │ └── index.ts │ │ ├── diacritics.ts │ │ ├── filters.ts │ │ ├── index.ts │ │ ├── internal/ │ │ │ ├── A11yText.tsx │ │ │ ├── DummyInput.tsx │ │ │ ├── RequiredInput.tsx │ │ │ ├── ScrollManager.tsx │ │ │ ├── index.ts │ │ │ ├── useScrollCapture.ts │ │ │ └── useScrollLock.ts │ │ ├── stateManager.tsx │ │ ├── styles.ts │ │ ├── theme.ts │ │ ├── types.ts │ │ ├── useAsync.ts │ │ ├── useCreatable.ts │ │ ├── useStateManager.ts │ │ └── utils.ts │ └── tsconfig.json ├── storybook/ │ ├── .gitignore │ ├── .storybook/ │ │ ├── .babelrc │ │ ├── main.ts │ │ └── preview.tsx │ ├── components/ │ │ ├── field.tsx │ │ ├── index.ts │ │ ├── inline.tsx │ │ ├── stack.tsx │ │ └── svg.tsx │ ├── data.ts │ ├── package.json │ ├── postcss.config.js │ ├── stories/ │ │ ├── AccessingInternalsViaRef.stories.tsx │ │ ├── AnimatedMulti.stories.tsx │ │ ├── AsyncCallbacks.stories.tsx │ │ ├── AsyncCreatable.stories.tsx │ │ ├── AsyncMulti.stories.tsx │ │ ├── AsyncPromises.stories.tsx │ │ ├── AsyncSelectWithDefaultOptions.stories.tsx │ │ ├── BasicGrouped.stories.tsx │ │ ├── BasicMulti.stories.tsx │ │ ├── BasicSingle.stories.tsx │ │ ├── ClassNamesWithTailwind.stories.tsx │ │ ├── ControlledMenu.stories.tsx │ │ ├── Creatable.stories.tsx │ │ ├── CreatableAdvanced.stories.tsx │ │ ├── CreatableInputOnly.stories.tsx │ │ ├── CreateFilter.stories.tsx │ │ ├── CustomAriaLive.stories.tsx │ │ ├── CustomClearIndicator.stories.tsx │ │ ├── CustomControl.stories.tsx │ │ ├── CustomDropdownIndicator.stories.tsx │ │ ├── CustomFilterOptions.stories.tsx │ │ ├── CustomFormatOptionLabel.stories.tsx │ │ ├── CustomGetOptionLabel.stories.tsx │ │ ├── CustomGetOptionValue.stories.tsx │ │ ├── CustomGroup.stories.tsx │ │ ├── CustomGroupHeading.stories.tsx │ │ ├── CustomIndicatorSeparator.stories.tsx │ │ ├── CustomIndicatorsContainer.stories.tsx │ │ ├── CustomInput.stories.tsx │ │ ├── CustomIsOptionDisabled.stories.tsx │ │ ├── CustomLoadingIndicator.stories.tsx │ │ ├── CustomLoadingMessage.stories.tsx │ │ ├── CustomMenu.stories.tsx │ │ ├── CustomMenuList.stories.tsx │ │ ├── CustomMultiValueContainer.stories.tsx │ │ ├── CustomMultiValueLabel.stories.tsx │ │ ├── CustomMultiValueRemove.stories.tsx │ │ ├── CustomNoOptionsMessage.stories.tsx │ │ ├── CustomOption.stories.tsx │ │ ├── CustomPlaceholder.stories.tsx │ │ ├── CustomSelectContainer.stories.tsx │ │ ├── CustomSelectProps.stories.tsx │ │ ├── CustomSingleValue.stories.tsx │ │ ├── CustomValueContainer.stories.tsx │ │ ├── ExperimentalDatePicker.stories.tsx │ │ ├── FixedOptions.stories.tsx │ │ ├── Grouped.stories.tsx │ │ ├── MenuBuffer.stories.tsx │ │ ├── MenuPortal.stories.tsx │ │ ├── MultiSelectSort.stories.tsx │ │ ├── OnSelectKeepsInput.stories.tsx │ │ ├── Popout.stories.tsx │ │ ├── StyleCompositionExample.stories.tsx │ │ ├── StyledMulti.stories.tsx │ │ ├── StyledSingle.stories.tsx │ │ ├── Tailwind.stories.tsx │ │ ├── Theme.stories.tsx │ │ └── UnstyledWithTailwind.stories.tsx │ ├── styles/ │ │ └── tailwind.css │ └── tailwind.config.js ├── test-setup.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .browserslistrc ================================================ > 0.25% ie 11 not op_mini all ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with `bolt` to help you release components from a mono-repository. You can find the full documentation for it [here](https://www.npmjs.com/package/@changesets/cli) To help you get started though, here are some things you should know about this folder: ## Changesets are automatically generated Changesets are generated by the `yarn changeset` or `npx changeset` command. As long as you are following a changeset release flow, you shouldn't have any problems. ## Each changeset is its own folder We use hashes by default for these folder names to avoid collisions when generating them, but there's no harm that will come from renaming them. ## Changesets are automatically removed When `changeset bump` or equivalent command is run, all the changeset folders are removed. This is so we only ever use a changeset once. This makes this a very bad place to store any other information. ## Changesets come in two parts You should treat these parts quite differently: - `changes.md` is a file you should feel free to edit as much as you want. It will be prepended to your changelog when you next run your version command. - `changes.json` is a file that includes information about releases, what should be versioned by the version command. We strongly recommend against editing this directly, as you may make a new changeset that puts your bolt repository into an invalid state. ## I want to edit the information in a `changes.json` - how do I do it safely? The best option is to make a new changeset using the changeset command, copy over the `changes.md`, then delete the old changeset. ## Can I rename the folder for my changeset? Absolutely! We need unique hashes to make changesets play nicely with git, but changing your folder from our hash to your own name isn't going to cause any problems. ## Can I manually delete changesets? You can, but you should be aware this will remove the intent to release communicated by the changeset, and should be done with caution. ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@0.2.1/schema.json", "changelog": "./getChangelogEntry", "commit": false, "linked": [], "access": "public" } ================================================ FILE: .changeset/getChangelogEntry.js ================================================ require('dotenv').config(); const { getInfo } = require('@changesets/get-github-info'); const getReleaseLine = async (changeset, type) => { const [firstLine, ...futureLines] = changeset.summary .split('\n') .map((l) => l.trimRight()); let { links } = await getInfo({ repo: 'JedWatson/react-select', commit: changeset.commit, }); return `- ${links.commit}${links.pull === null ? '' : ` ${links.pull}`}${ links.user === null ? '' : ` Thanks ${links.user}!` } - ${firstLine}\n${futureLines.map((l) => ` ${l}`).join('\n')}`; }; const getDependencyReleaseLine = async (changesets, dependenciesUpdated) => { if (dependenciesUpdated.length === 0) return ''; const changesetLinks = changesets.map( (changeset) => `- Updated dependencies [${changeset.commit}]:` ); const updatedDepenenciesList = dependenciesUpdated.map( (dependency) => ` - ${dependency.name}@${dependency.version}` ); return [...changesetLinks, ...updatedDepenenciesList].join('\n'); }; module.exports = { getReleaseLine, getDependencyReleaseLine, }; ================================================ FILE: .circleci/config.yml ================================================ version: 2 docker_defaults: &docker_defaults docker: - image: cypress/browsers:latest environment: TERM: xterm working_directory: ~/project/repo attach_workspace: &attach_workspace attach_workspace: at: ~/project install_steps: &install_steps steps: - checkout - restore_cache: name: Restore node_modules cache keys: - dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }} - dependency-cache-{{ .Branch }}- - dependency-cache- - cache-{{ checksum "package.json" }} - run: name: Installing Dependencies command: | yarn install --silent - save_cache: name: Save node_modules cache key: dependency-cache-{{ .Branch }}-{{ checksum "package.json" }} paths: - ~/.cache - persist_to_workspace: root: ~/project paths: - repo workflows: version: 2 build_pipeline: jobs: - build - unit_test: requires: - build - end_to_end: requires: - build jobs: build: <<: *docker_defaults <<: *install_steps unit_test: <<: *docker_defaults steps: - *attach_workspace - run: name: Running unit tests command: | yarn prettier:check yarn lint yarn type-check yarn test:jest yarn coveralls end_to_end: <<: *docker_defaults steps: - *attach_workspace - run: name: Running E2E tests command: | yarn global add cypress yarn install --silent yarn cypress install yarn e2e ================================================ FILE: .codesandbox/ci.json ================================================ { "buildCommand": "build", "packages": ["packages/*"], "sandboxes": ["nfmxw"], "node": "20" } ================================================ FILE: .coveralls.yml ================================================ service-name: travis-ci repo_token: itdMRdBNgDK8Gb5nIA63zVMEryaxTQxkR ================================================ FILE: .editorconfig ================================================ # This file is for unifying the coding style for different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false [.circleci/config.yml] indent_size = 4 ================================================ FILE: .eslintignore ================================================ coverage/* cypress/plugins/* cypress/support/* **/dist/* lib/* node_modules/* **/node_modules/* ================================================ FILE: .eslintrc.js ================================================ module.exports = { extends: ['plugin:react-hooks/recommended', 'plugin:@typescript-eslint/base'], parser: '@typescript-eslint/parser', env: { browser: true, es6: true, node: true, }, plugins: ['react', '@typescript-eslint'], rules: { '@typescript-eslint/no-unused-vars': [ 'error', { args: 'after-used', argsIgnorePattern: '^event$', ignoreRestSiblings: true, vars: 'all', varsIgnorePattern: 'jsx|emotionJSX', }, ], curly: [2, 'multi-line'], 'jsx-quotes': 1, 'no-shadow': 0, '@typescript-eslint/no-shadow': 2, 'no-trailing-spaces': 1, 'no-underscore-dangle': 1, '@typescript-eslint/no-unused-expressions': 1, 'object-curly-spacing': [1, 'always'], '@typescript-eslint/quotes': [2, 'single', 'avoid-escape'], 'react/jsx-boolean-value': 1, 'react/jsx-no-undef': 1, 'react/jsx-uses-react': 1, 'react/jsx-uses-vars': 1, 'react/jsx-wrap-multilines': 1, 'react/no-did-mount-set-state': 1, 'react/no-did-update-set-state': 1, 'react/no-unknown-property': 1, 'react/react-in-jsx-scope': 1, 'react/self-closing-comp': 1, 'react/sort-prop-types': 1, '@typescript-eslint/semi': 2, '@typescript-eslint/no-inferrable-types': 2, strict: 0, }, settings: { react: { version: 'detect', }, }, }; ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing Thanks for your interest in React-Select. All forms of contribution are welcome, from issue reports to PRs and documentation / write-ups. Before you open a PR: - In development, run `yarn start` to build (+watch) the project source, and run the [development server](http://localhost:8000). - Please ensure all the examples work correctly after your change. If you're adding a major new use-case, add a new example demonstrating its use. - Be careful to follow the code style of the project. Run `yarn lint` after your changes and ensure you do not introduce any new errors or warnings. - This repository uses TypeScript, please run `yarn type-check` after your changes to ensure that you do not introduce any new type errors. - Ensure that your effort is aligned with the project's roadmap by talking to the maintainers, especially if you are going to spend a lot of time on it. - Make sure there's an issue open for any work you take on and intend to submit as a pull request - it helps core members review your concept and direction early and is a good way to discuss what you're planning to do. - If you open an issue and are interested in working on a fix, please let us know. We'll help you get started, rather than adding it to the queue. - Make sure you do not add regressions by running `yarn test`. - Where possible, include tests with your changes, either that demonstrates the bug, or tests the new functionality. If you're not sure how to test your changes, feel free to ping @gwyneplaine or @JedWatson - Run `yarn coveralls` to check that the coverage hasn't dropped, and look at the report (under the generated `coverage` directory) to check that your changes are covered ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: File a bug report title: '' labels: [issue/bug-unconfirmed] assignees: '' --- **Thanks for using react-select!** If you are going to ask a question or want to propose a change or a new feature, then please don't file an issue for this. Questions and feature requests have their own place in our discussions section. ## Are you reporting a bug or runtime error? Please include a test case that demonstrates the issue you're reporting! This is very helpful to maintainers in order to help us see the issue you're seeing. Please note we are currently only directing our efforts towards the current major (v5) version and beyond. We understand this might be inconvenient but it is in the best interest of supporting the broader community and to sustain the `react-select` project going forward. To report bugs against react-select v5 please fork the following code-sandbox: https://codesandbox.io/s/react-select-v5-sandbox-y5jtm You may also find the [online Babel tool](https://babeljs.io/repl/) quite helpful if you wish to use ES6/ES7 syntax not yet supported by the browser you are using. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Feature request url: https://github.com/JedWatson/react-select/discussions/categories/ideas about: Got an idea for a feature or want to propose a change? Then this is the place for you. - name: Question on usage url: https://github.com/JedWatson/react-select/discussions/categories/q-a about: If you have a question regarding the usage of the library. - name: StackOverflow url: https://stackoverflow.com/questions/tagged/react-select about: Alternatively you can visit StackOverflow with the `[react-select]` tag ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: 'npm' directory: '/' schedule: interval: 'weekly' ignore: - dependency-name: '*' update-types: ['version-update:semver-minor', 'version-update:semver-patch'] ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master permissions: contents: read jobs: release: permissions: # for changesets/action contents: write pull-requests: write name: Release runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v3 with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - name: Setup Node.js 22.x uses: actions/setup-node@v4 with: node-version: 22.x - name: Install Dependencies run: yarn - name: Create Release Pull Request or Publish to npm uses: changesets/action@v1 with: publish: yarn release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ # Build lib dist docs/dist .env # Logs logs *.log # Runtime data pids *.pid *.seed # Coverage tools lib-cov coverage .nyc_output # Cypress cypress/videos cypress/screenshots cypress/support cypress/plugins # Dependency directory node_modules bower_components # Publish directory .publish # Editor artefacts .idea # Other .DS_Store .env package-lock.json # Notes .NOTES.md magical-types ================================================ FILE: .nvmrc ================================================ 22 ================================================ FILE: .prettierignore ================================================ coverage/* cypress/plugins/* cypress/support/* **/dist/* lib/* node_modules/* **/node_modules/* **/magical-types/* ================================================ FILE: .prettierrc.js ================================================ module.exports = { singleQuote: true, trailingComma: 'es5', overrides: [ { files: '.changeset/pre.json', options: { parser: 'json-stringify' }, }, ], }; ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Thanks for your interest in React-Select. All forms of contribution are welcome, from issue reports to PRs and documentation / write-ups. Before you open a PR: - In development, run `yarn start` to build (and watch) the project source, and run the [development server](http://localhost:8000). - Please ensure all the examples work correctly after your change. If you're adding a major new use-case, add a new example `/docs/examples` and subsequent documentation demonstrating its use `/docs/pages`. - Ensure that your effort is aligned with the project's roadmap by talking to the maintainers, especially if you are going to spend a lot of time on it. - Make sure there's an issue open for any work you take on and intend to submit as a pull request - it helps core members review your concept and direction early and is a good way to discuss what you're planning to do. - If you open an issue and are interested in working on a fix, please let us know. We'll help you get started, rather than inadvertently doubling up on your hard work. - Make sure you do not add regressions by running `yarn test`. - Where possible, include tests with your changes, either that demonstrates the bug, or tests the new functionality. - All new features and changes need documentation. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2022 Jed Watson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![NPM](https://img.shields.io/npm/v/react-select.svg)](https://www.npmjs.com/package/react-select) [![CircleCI](https://circleci.com/gh/JedWatson/react-select/tree/master.svg?style=shield)](https://circleci.com/gh/JedWatson/react-select/tree/master) [![Coverage Status](https://coveralls.io/repos/JedWatson/react-select/badge.svg?branch=master&service=github)](https://coveralls.io/github/JedWatson/react-select?branch=master) [![Supported by Thinkmill](https://thinkmill.github.io/badge/heart.svg)](http://thinkmill.com.au/?utm_source=github&utm_medium=badge&utm_campaign=react-select) # React-Select The Select control for [React](https://reactjs.org). Initially built for use in [KeystoneJS](https://www.keystonejs.com). See [react-select.com](https://www.react-select.com) for live demos and comprehensive docs. `react-select` is funded by [Thinkmill](https://www.thinkmill.com.au) and [Atlassian](https://atlaskit.atlassian.com). We are an open source project that is continuously supported by the community. React Select helps you develop powerful select components that _just work_ out of the box, without stopping you from customising the parts that are important to you. For the story behind this component, watch Jed's talk at React Conf 2019 - [building React Select](https://youtu.be/yS0jUnmBujE) Features include: - Flexible approach to data, with customisable functions - Extensible styling API with [emotion](https://emotion.sh) - Component Injection API for complete control over the UI behaviour - Controllable state props and modular architecture - Long-requested features like option groups, portal support, animation, and more ## Using an older version? - [v3, v4, and v5 upgrade guide](https://react-select.com/upgrade) - [v2 upgrade guide](https://react-select.com/upgrade-to-v2) - React Select v1 documentation and examples are available at [v1.react-select.com](https://v1.react-select.com) # Installation and usage The easiest way to use react-select is to install it from npm and build it into your app with Webpack. ``` yarn add react-select ``` Then use it in your app: ```js import React, { useState } from 'react'; import Select from 'react-select'; const options = [ { value: 'chocolate', label: 'Chocolate' }, { value: 'strawberry', label: 'Strawberry' }, { value: 'vanilla', label: 'Vanilla' }, ]; export default function App() { const [selectedOption, setSelectedOption] = useState(null); return ( <div className="App"> <Select defaultValue={selectedOption} onChange={setSelectedOption} options={options} /> </div> ); } ``` ## Props Common props you may want to specify include: - `autoFocus` - focus the control when it mounts - `className` - apply a className to the control - `classNamePrefix` - apply classNames to inner elements with the given prefix - `isDisabled` - disable the control - `isMulti` - allow the user to select multiple values - `isSearchable` - allow the user to search for matching options - `name` - generate an HTML input with this name, containing the current value - `onChange` - subscribe to change events - `options` - specify the options the user can select from - `placeholder` - change the text displayed when no option is selected - `noOptionsMessage` - ({ inputValue: string }) => string | null - Text to display when there are no options - `value` - control the current value See the [props documentation](https://www.react-select.com/props) for complete documentation on the props react-select supports. ## Controllable Props You can control the following props by providing values for them. If you don't, react-select will manage them for you. - `value` / `onChange` - specify the current value of the control - `menuIsOpen` / `onMenuOpen` / `onMenuClose` - control whether the menu is open - `inputValue` / `onInputChange` - control the value of the search input (changing this will update the available options) If you don't provide these props, you can set the initial value of the state they control: - `defaultValue` - set the initial value of the control - `defaultMenuIsOpen` - set the initial open value of the menu - `defaultInputValue` - set the initial value of the search input ## Methods React-select exposes two public methods: - `focus()` - focus the control programmatically - `blur()` - blur the control programmatically ## Customisation Check the docs for more information on: - [Customising the styles](https://www.react-select.com/styles) - [Using custom components](https://www.react-select.com/components) - [Using the built-in animated components](https://www.react-select.com/home#animated-components) - [Creating an async select](https://www.react-select.com/async) - [Allowing users to create new options](https://www.react-select.com/creatable) - [Advanced use-cases](https://www.react-select.com/advanced) - [TypeScript guide](https://www.react-select.com/typescript) ## TypeScript The v5 release represents a rewrite from JavaScript to TypeScript. The types for v4 and earlier releases are available at [@types](https://www.npmjs.com/package/@types/react-select). See the [TypeScript guide](https://www.react-select.com/typescript) for how to use the types starting with v5. # Thanks Thank you to everyone who has contributed to this project. It's been a wild ride. If you like React Select, you should [follow me on Twitter](https://twitter.com/jedwatson)! Shout out to [Joss Mackison](https://github.com/jossmac), [Charles Lee](https://github.com/gwyneplaine), [Ben Conolly](https://github.com/Noviny), [Tom Walker](https://github.com/bladey), [Nathan Bierema](https://github.com/Methuselah96), [Eric Bonow](https://github.com/ebonow), [Emma Hamilton](https://github.com/emmatown), [Dave Brotherstone](https://github.com/bruderstein), [Brian Vaughn](https://github.com/bvaughn), and the [Atlassian Design System](https://atlassian.design) team who along with many other contributors have made this possible ❤️ ## License MIT Licensed. Copyright (c) Jed Watson 2022. ================================================ FILE: babel.config.js ================================================ module.exports = { plugins: [ '@emotion/babel-plugin', ['@babel/plugin-proposal-class-properties', { loose: true }], ['@babel/plugin-proposal-private-methods', { loose: true }], '@babel/plugin-transform-runtime', ], presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript', ], }; ================================================ FILE: cypress/fixtures/selectors.json ================================================ { "singleBasic": "#cypress-single", "singleBasicSelect": "#basic-select-single", "singleClearable": "#cypress-single-clearable", "singleClearableSelect": "#clearable-select-single", "singleGroupedSelect": "#grouped-options-single", "checkboxDisable": ".disable-checkbox", "checkboxEscapeClearsValue": ".escape-clears-value-checkbox", "groupHeading": ".react-select__group-heading", "indicatorClear": ".react-select__clear-indicator", "indicatorDropdown": ".react-select__dropdown-indicator", "menu": ".react-select__menu", "control": ".react-select__control", "menuOption": ".react-select__option", "noOptionsValue": ".react-select__menu-notice--no-options", "placeholder": ".react-select__placeholder", "singleValue": ".react-select__single-value", "menuSingle": "#basic-select-single .react-select__menu", "singleSelectSingleInput": "#react-select-basic-select-single-input", "toggleMenuSingle": "#basic-select-single .react-select__dropdown-indicator", "firstMultiValueRemove": "#multi-select .react-select__multi-value__remove:first", "menuMulti": "#multi-select .react-select__menu", "multiSelectDefaultValues": "#multi-select .react-select__multi-value", "multiSelectInput": "#react-select-multi-select-input", "placeHolderMulti": "#multi-select .react-select__placeholder", "toggleMenuMulti": "#multi-select .react-select__dropdown-indicator", "focusedOption": ".react-select__option--is-focused" } ================================================ FILE: cypress/integration/multi-select.spec.ts ================================================ import selector from '../fixtures/selectors.json'; import cypressJson from '../../cypress.json'; const setup = [ { width: 1440, height: 900, viewport: 'macbook-15', device: 'Laptop' }, { width: 375, height: 667, viewport: 'iphone-6', device: 'Mobile' }, { width: 768, height: 1024, viewport: 'ipad-2', device: 'Tablet' }, ]; describe('Multi Select', () => { before(() => { cy.visit(cypressJson.baseUrl); cy.title().should('equal', 'React-Select'); cy.get('h1').should('contain', 'Test Page for Cypress'); }); beforeEach(() => { cy.reload(); }); for (let config of setup) { const { viewport } = config; it(`Should display several default values that can be removed in view: ${viewport}`, () => { cy.get(selector.multiSelectDefaultValues).then(function ($defaultValue) { expect($defaultValue).to.have.length(2); expect($defaultValue.eq(0)).to.contain('Purple'); expect($defaultValue.eq(1)).to.contain('Red'); }); cy.get(selector.firstMultiValueRemove) .click() .get(selector.multiSelectDefaultValues) .then(function ($defaultValue) { expect($defaultValue).to.have.length(1); expect($defaultValue.eq(0)).to.contain('Red'); }) .get(selector.menuMulti) .should('not.be.visible'); }); it(`Should be able to remove values on keyboard actions in view: ${viewport}`, () => { cy.get(selector.multiSelectInput) .click() .type('{backspace}', { force: true }) .get(selector.multiSelectDefaultValues) .then(function ($defaultValue) { expect($defaultValue).to.have.length(1); expect($defaultValue.eq(0)).to.contain('Purple'); }) .get(selector.multiSelectInput) .type('{backspace}', { force: true }) .get(selector.placeHolderMulti) .should('contain', 'Select...'); }); it(`Should select different options using - click and enter in view: ${viewport}`, () => { cy.get(selector.menuMulti) .should('not.exist') .get(selector.toggleMenuMulti) .click() .get(selector.menuMulti) .should('exist') .get(selector.menuMulti) .should('be.visible') .get(selector.menuOption) .contains('Orange') .click() .get(selector.toggleMenuMulti) .click() .get(selector.menuOption) .contains('Yellow') .click() .get(selector.multiSelectInput) .click({ force: true }) .type('Slate', { force: true }) .type('{enter}', { force: true }) .get(selector.multiSelectDefaultValues) .then(function ($defaultValue) { expect($defaultValue).to.have.length(5); expect($defaultValue.eq(0)).to.contain('Purple'); expect($defaultValue.eq(1)).to.contain('Red'); expect($defaultValue.eq(2)).to.contain('Orange'); expect($defaultValue.eq(3)).to.contain('Yellow'); expect($defaultValue.eq(4)).to.contain('Slate'); }); }); } }); ================================================ FILE: cypress/integration/single-select.spec.ts ================================================ import selector from '../fixtures/selectors.json'; import cypressJson from '../../cypress.json'; const setup = [ { width: 1440, height: 900, viewport: 'macbook-15' as const, device: 'Laptop', }, { width: 375, height: 667, viewport: 'iphone-6' as const, device: 'Mobile' }, { width: 768, height: 1024, viewport: 'ipad-2' as const, device: 'Tablet' }, ]; describe('Single Select', () => { before(() => { cy.visit(cypressJson.baseUrl); cy.title().should('equal', 'React-Select'); cy.get('h1').should('contain', 'Test Page for Cypress'); }); for (let config of setup) { const { viewport } = config; context(`Basic in view: ${viewport}`, () => { before(() => { cy.viewport(viewport); }); beforeEach(() => { cy.reload(); }); // TODO: // This test seems to fail when cypress tab is focused. // Also, manual testing does not confirm the desired behavior. it.skip(`Should not display the options menu when touched and dragged in view: ${viewport}`, () => { cy.get(selector.toggleMenuSingle) .click() .click() .get(selector.menuSingle) .should('not.be.visible') // to be sure it says focus and the menu is closed .get(selector.singleSelectSingleInput) .trigger('mousedown') .get(selector.menuSingle) .should('not.be.visible'); }); it(`Should display a default value in view: ${viewport}`, () => { cy.get(selector.singleBasicSelect) .find(selector.singleValue) .should('contain', 'Ocean'); }); it(`Should expand the menu when expand icon is clicked in view: ${viewport}`, () => { cy // Menu is not yet open .get(selector.singleBasicSelect) .find(selector.menu) .should('not.exist') // A dropdown icon is shown .get(selector.singleBasicSelect) .find(selector.indicatorDropdown) .should('be.visible') // Click the icon to open the menu .click() .get(selector.singleBasicSelect) .find(selector.menu) .should('exist') .should('be.visible') .contains('Green'); }); it(`Should close the menu after selecting an option in view: ${viewport}`, () => { cy.get(selector.singleBasicSelect) .find(selector.indicatorDropdown) .click() .get(selector.singleBasicSelect) .find(selector.menu) .should('contain', 'Green') .contains('Green') .click() // Value has updated .get(selector.singleBasicSelect) .find(selector.singleValue) .should('contain', 'Green') // Menu has closed .get(selector.singleBasicSelect) .find(selector.menu) .should('not.exist'); }); it(`Should be disabled once disabled is checked in view: ${viewport}`, () => { cy // Does not start out disabled .get(selector.singleBasicSelect) // .click() .find('input') .should('exist') .should('not.be.disabled') // Disable the select component .get(selector.singleBasic) .find(selector.checkboxDisable) .click() // Now the input should be disabled .get(selector.singleBasicSelect) .click({ force: true }) .find('input') .should('exist') .should('be.disabled') // control should have aria-disabled .get(selector.singleBasicSelect) .find(selector.control) .should('have.attr', 'aria-disabled', 'true'); }); it(`Should filter options when searching in view: ${viewport}`, () => { cy.get(selector.singleBasicSelect) .click() .find('input') .type('For', { force: true }) .get(selector.singleBasicSelect) .find(selector.menu) .should('contain', 'Forest') .find(selector.menuOption) .should('have.length', 1); }); it(`Should show "No options" if searched value is not found in view: ${viewport}`, () => { cy.get(selector.singleBasicSelect) .click() .find('input') .type('/', { force: true }) .get(selector.noOptionsValue) .should('contain', 'No options'); }); it(`Should not clear the value when backspace is pressed in view: ${viewport}`, () => { cy.get(selector.singleBasicSelect) .click() .find('input') .type('{backspace}', { force: true }) .get(selector.singleBasicSelect) .find(selector.placeholder) .should('not.be.visible'); }); }); context(`Grouped in view: ${viewport}`, () => { before(() => { cy.viewport(viewport); }); beforeEach(() => { cy.reload(); }); it(`Should display a default value in view: ${viewport}`, () => { cy.get(selector.singleGroupedSelect) .find(selector.singleValue) .should('contain', 'Blue'); }); it(`Should display group headings in the menu in view: ${viewport}`, () => { cy.get(selector.singleGroupedSelect) .find(selector.indicatorDropdown) .click() .get(selector.singleGroupedSelect) .find(selector.menu) .should('be.visible') .find(selector.groupHeading) .should('have.length', 2); }); it(`Should focus next option on down arrow key press: ${viewport}`, () => { cy.get(selector.singleGroupedSelect) .click() .find('input') .type('{downarrow}', { force: true }) .get(selector.focusedOption) .should('exist'); }); it(`Should focus next option on down arrow key press after filtering: ${viewport}`, () => { cy.get(selector.singleGroupedSelect) .click() .find('input') .type('o', { force: true }) .type('{downarrow}', { force: true }) .get(selector.focusedOption) .should('exist'); }); }); context(`Clearable in view: ${viewport}`, () => { before(() => { cy.viewport(viewport); }); beforeEach(() => { cy.reload(); }); it(`Should display a default value in view: ${viewport}`, () => { cy.get(selector.singleClearableSelect) .find(selector.singleValue) .should('contain', 'Blue'); }); it(`Should display a clear indicator in view: ${viewport}`, () => { cy.get(selector.singleClearableSelect) .find(selector.indicatorClear) .should('be.visible'); }); it(`Should clear the default value when clear is clicked in view: ${viewport}`, () => { cy.get(selector.singleClearableSelect) .find(selector.indicatorClear) .click() .get(selector.singleClearableSelect) .find(selector.placeholder) .should('be.visible') .should('contain', 'Select...'); }); // 'backspaceRemovesValue' is true by default it(`Should clear the value when backspace is pressed in view: ${viewport}`, () => { cy.get(selector.singleClearableSelect) .click() .find('input') .type('{backspace}', { force: true }) .get(selector.singleClearableSelect) .find(selector.placeholder) .should('be.visible') .should('contain', 'Select...'); }); // 'backspaceRemovesValue' is true by default, and delete is included it(`Should clear the value when delete is pressed in view: ${viewport}`, () => { cy.get(selector.singleClearableSelect) .click() .find('input') .type('{del}', { force: true }) .get(selector.singleClearableSelect) .find(selector.placeholder) .should('be.visible') .should('contain', 'Select...'); }); it(`Should not open the menu when a value is cleared with backspace in view: ${viewport}`, () => { cy.get(selector.singleClearableSelect) .click() .find('input') // Close the menu, but leave focused .type('{esc}', { force: true }) .get(selector.singleClearableSelect) .find(selector.menu) .should('not.be.visible') // Clear the value, verify menu doesn't pop .get(selector.singleClearableSelect) .find('input') .type('{backspace}', { force: true }) .get(selector.singleClearableSelect) .find(selector.menu) .should('not.be.visible'); }); it(`Should clear the value when escape is pressed if escapeClearsValue and menu is closed in view: ${viewport}`, () => { cy // nothing happens if escapeClearsValue is false .get(selector.singleClearableSelect) .click() .find('input') // Escape once to close the menu .type('{esc}', { force: true }) .get(selector.singleBasicSelect) .find(selector.menu) .should('not.be.visible') // Escape again to verify value is not cleared .get(selector.singleClearableSelect) .find('input') .type('{esc}', { force: true }) .get(selector.singleClearableSelect) .find(selector.placeholder) .should('not.be.visible') // Enable escapeClearsValue and try again, it should clear the value .get(selector.singleClearable) .find(selector.checkboxEscapeClearsValue) .click() .get(selector.singleClearableSelect) .click() .find('input') // Escape once to close the menu .type('{esc}', { force: true }) .get(selector.singleBasicSelect) .find(selector.menu) .should('not.be.visible') // Escape again to clear value .get(selector.singleClearableSelect) .find('input') .type('{esc}', { force: true }) .get(selector.singleClearableSelect) .find(selector.placeholder) .should('be.visible'); }); }); } }); ================================================ FILE: cypress/tsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs", "noEmit": true, "strict": true, "types": ["cypress"], "esModuleInterop": true, "resolveJsonModule": true } } ================================================ FILE: cypress.json ================================================ { "baseUrl": "http://localhost:8000/cypress-tests", "video": false } ================================================ FILE: docs/App/Footer.tsx ================================================ /** @jsx jsx */ import { jsx } from '@emotion/react'; // const smallDevice = '@media (max-width: 769px)'; const largeDevice = '@media (min-width: 770px)'; const Wrapper = (props: JSX.IntrinsicElements['div']) => ( <div css={{ backgroundColor: '#FAFBFC', borderTop: '1px solid #EBECF0', color: '#7A869A', fontSize: '0.85em', zIndex: 1, }} {...props} /> ); const Container = (props: JSX.IntrinsicElements['div']) => ( <div css={{ boxSizing: 'border-box', maxWidth: 800, marginLeft: 'auto', marginRight: 'auto', paddingLeft: 20, paddingRight: 20, [largeDevice]: { alignItems: 'center', display: 'flex', justifyContent: 'space-between', paddingBottom: 20, paddingTop: 20, }, }} {...props} /> ); const A = (props: JSX.IntrinsicElements['a']) => ( <a {...props} css={{ color: '#505F79', textDecoration: 'none', ':visited': { color: '#505F79', }, ':hover': { textDecoration: 'underline', }, }} target="_blank" /> ); export default function Footer() { return ( <Wrapper> <Container> <p> Copyright © <A href="https://twitter.com/JedWatson">Jed Watson</A>, 2022. MIT Licensed. </p> <p> Thanks to <A href="https://www.thinkmill.com.au">Thinkmill</A> and{' '} <A href="https://www.atlassian.com">Atlassian</A> for supporting this project. </p> </Container> </Wrapper> ); } ================================================ FILE: docs/App/GitHubButton.tsx ================================================ /** @jsx jsx */ import { jsx } from '@emotion/react'; interface Props { readonly count: number; readonly repo: string; } const StarButton = ({ count, repo }: Props) => ( <div css={{ alignItems: 'center', display: 'inline-flex', minWidth: 128 }}> <a aria-label="Star react-select on GitHub" css={{ alignItems: 'center', display: 'flex', borderRadius: 2, color: '#253858', backgroundColor: 'white', boxShadow: '0 1px 0 rgba(0, 0, 0, 0.2)', cursor: 'pointer', fontSize: 13, fontWeight: 'bold', padding: '6px 10px', position: 'relative', textDecoration: 'none', ':hover': { boxShadow: '0 1px 0 rgba(0, 0, 0, 0.2), 0 2px 5px rgba(0, 0, 0, 0.2)', color: '#091e42', }, ':active': { background: '#DFE1E5', boxShadow: '0 1px 0 rgba(0, 0, 0, 0.2)', color: '#091e42', bottom: -1, }, }} style={{ backgroundImage: 'linear-gradient(180deg, #fff 33%, #DFE1E5 100%)', }} href={repo} target="_blank" > <svg version="1.1" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" > <path fillRule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z" /> </svg> <span css={{ paddingLeft: 4 }}>Star</span> </a> <a css={{ backgroundColor: 'white', borderRadius: 2, color: '#253858', cursor: 'pointer', display: 'inline-block', fontSize: 13, fontWeight: 500, letterSpacing: '-0.01em', marginLeft: 6, opacity: count > 0 ? 1 : 0, padding: '6px 7px', position: 'relative', textDecoration: 'none', transition: 'opacity 200ms', '&:before': { border: '4px solid transparent', borderRightColor: 'white', content: '" "', height: 0, left: -8, top: '50%', marginTop: -4, position: 'absolute', width: 0, }, }} href={`${repo}/stargazers`} target="_blank" > <span>{count && count.toLocaleString()}</span> </a> </div> ); export default StarButton; ================================================ FILE: docs/App/Header.tsx ================================================ /** @jsx jsx */ import fetch from 'unfetch'; import { Component, Ref, RefCallback } from 'react'; import { jsx } from '@emotion/react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import Select, { StylesConfig } from 'react-select'; import GitHubButton from './GitHubButton'; import TwitterButton from './TwitterButton'; import isArray from '../isArray'; const smallDevice = '@media (max-width: 769px)'; const largeDevice = '@media (min-width: 770px)'; interface Change { value: string; icon: string; label: string; } const changes = [ { value: '/typescript', icon: '🛠️', label: 'Written in TypeScript', }, { value: '/props', icon: '❤️', label: 'Simpler and more extensible', }, { value: '/styles', icon: '🎨', label: 'CSS-in-JS styling API', }, { value: '/components', icon: '📦', label: 'Replaceable component architecture', }, { value: '/advanced', icon: '🔥', label: 'Lots of advanced functionality', }, { value: '/upgrade', icon: '🗺', label: 'Check out the Upgrade Guide', }, ]; function getLabel({ icon, label }: Change) { return ( <div style={{ alignItems: 'center', display: 'flex' }}> <span style={{ fontSize: 18, marginRight: '0.5em' }}>{icon}</span> <span style={{ fontSize: 14 }}>{label}</span> </div> ); } const headerSelectStyles: StylesConfig<Change, boolean> = { control: (base, { isFocused }) => ({ ...base, backgroundClip: 'padding-box', borderColor: 'rgba(0,0,0,0.1)', boxShadow: isFocused ? '0 0 0 1px #4C9AFF' : undefined, ':hover': { borderColor: 'rgba(0,0,0,0.2)', }, }), option: (base) => ({ ...base, padding: '4px 12px', }), placeholder: (base) => ({ ...base, color: 'black', }), }; const Gradient = (props: JSX.IntrinsicElements['div']) => ( <div css={{ backgroundColor: '#2684FF', backgroundImage: 'linear-gradient(135deg, #2684FF 0%, #0747A6 100%)', color: 'white', position: 'relative', zIndex: 2, [largeDevice]: { boxShadow: '0 5px 0 rgba(0, 0, 0, 0.08)', }, }} {...props} /> ); const Container = (props: JSX.IntrinsicElements['div']) => ( <div css={{ boxSizing: 'border-box', maxWidth: 800, marginLeft: 'auto', marginRight: 'auto', padding: 20, [largeDevice]: { paddingBottom: 40, paddingTop: 40, }, }} {...props} /> ); interface HeaderState { readonly stars: number; } const apiUrl = 'https://api.github.com/repos/jedwatson/react-select'; class Header extends Component<RouteComponentProps, HeaderState> { nav: HTMLElement | undefined; content!: HTMLElement; state: HeaderState = { stars: 0 }; componentDidMount() { this.getStarCount(); } getStarCount = () => { fetch(apiUrl) .then((res) => res.json()) .then((data) => { const stars = data.stargazers_count; this.setState({ stars }); }) .catch((err) => { console.error('Error retrieving data', err); }); }; isHome = (props = this.props) => { const valid = ['/', '/home']; return valid.includes(props.location.pathname); }; setContentRef: RefCallback<HTMLDivElement> = (ref) => { if (!ref) return; this.content = ref; }; getContentHeight = () => { if (!this.content) { return 'auto'; } return this.content.scrollHeight; }; render() { const { children, history } = this.props; const { stars } = this.state; return ( <Gradient> {children} <Collapse isCollapsed={!this.isHome()} height={this.getContentHeight()} innerRef={this.setContentRef} > <Container> <h1 css={{ fontSize: '2.4em', fontWeight: 'bold', lineHeight: 1, margin: 0, marginTop: '-0.2em', textShadow: '1px 1px 0 rgba(0, 82, 204, 0.33)', color: 'inherit', [largeDevice]: { fontSize: '3.6em', }, }} > React Select </h1> <Content stars={stars} onChange={(opt) => { history.push(opt.value); }} /> </Container> </Collapse> </Gradient> ); } } interface CollapseProps { readonly height: 'auto' | number; readonly isCollapsed: boolean; readonly innerRef: Ref<HTMLDivElement>; } const Collapse = ({ height, isCollapsed, innerRef, ...props }: CollapseProps & JSX.IntrinsicElements['div']) => { return ( <div ref={innerRef} css={{ height: isCollapsed ? 0 : height, overflow: isCollapsed ? 'hidden' : undefined, transition: 'height 260ms cubic-bezier(0.2, 0, 0, 1)', }} {...props} /> ); }; interface ContentProps { readonly onChange: (option: Change) => void; readonly stars: number; } const Content = ({ onChange, stars }: ContentProps) => ( <div css={{ marginTop: 16, [largeDevice]: { display: 'flex' }, }} > <div css={{ flex: 1, [largeDevice]: { paddingRight: 30 } }}> <p style={{ fontSize: '1.25em', lineHeight: 1.4, marginTop: -5, }} > A flexible and beautiful Select Input control for ReactJS with multiselect, autocomplete, async and creatable support. </p> <div css={{ flex: 1, alignItems: 'center' }}> <GitHubButton count={stars} repo="https://github.com/jedwatson/react-select" /> <TwitterButton /> </div> </div> <div css={{ color: 'black', flex: '0 1 320px', [smallDevice]: { paddingTop: 30, }, }} > <div className="animate-dropin"> <Select formatOptionLabel={getLabel} isSearchable={false} options={changes} onChange={(option) => { if (option && !isArray(option)) { onChange(option); } }} value={null} placeholder="🎉 Feature Highlights" styles={headerSelectStyles} /> </div> </div> </div> ); export default withRouter(Header); ================================================ FILE: docs/App/PageNav.tsx ================================================ /** @jsx jsx */ import { Component, FunctionComponent, MouseEvent, RefCallback } from 'react'; import { jsx } from '@emotion/react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { animatedScrollTo } from 'react-select/src/utils'; import routes from './routes'; import ScrollSpy from './ScrollSpy'; import Sticky from './Sticky'; import store, { Data } from '../markdown/store'; const navWidth = 180; const appGutter = 20; const contentGutter = 30; const smallDevice = '@media (max-width: 769px)'; const largeDevice = '@media (min-width: 770px)'; const NavSection: FunctionComponent<RouteComponentProps> = () => { const routeKeys = Object.keys(routes); return ( <Switch> {routeKeys.map((r) => ( <Route key={r} path={r} render={(p) => <PageNav {...p} />} /> ))} </Switch> ); }; interface NavState { readonly links: readonly Data[]; readonly activeId: string | null; } class PageNav extends Component<RouteComponentProps, NavState> { scrollSpy!: ScrollSpy; state: NavState = { activeId: null, links: [] }; componentDidMount() { const { match } = this.props; // eslint-disable-next-line this.setState({ links: store.getPageHeadings(match.path) }); } componentDidUpdate({ history, location }: RouteComponentProps) { const { hash } = this.props.location; // old hash const shouldRefresh = location.hash !== hash && history.action !== 'POP'; // this makes everything work, need those fresh nodes if (shouldRefresh && this.scrollSpy.buildNodeList) { this.scrollSpy.buildNodeList(); } } getSelected = (ids: readonly (string | null)[]) => { const activeId = ids[0]; if (activeId !== this.state.activeId) { this.setState({ activeId }); } }; getScrollSpy: RefCallback<ScrollSpy> = (ref) => { if (!ref) return; this.scrollSpy = ref; }; handleItemClick = ({ event, hash, }: { readonly event: MouseEvent<HTMLDivElement>; readonly hash: string; }) => { event.preventDefault(); const path = `#${hash}`; const el = document.querySelector<HTMLElement>(path); const { history } = this.props; if (el && el.offsetTop) { history.replace(path); animatedScrollTo(window, el.offsetTop); } }; render() { const { activeId, links } = this.state; const isSmallDevice = window.innerWidth <= 769; return links && links.length ? ( <Sticky preserveHeight={isSmallDevice}> <ScrollSpy ref={this.getScrollSpy} onChange={this.getSelected}> <Nav> {links.map((l) => { const hash = l.path.slice(1); const selected = hash === activeId; return l.level > 0 ? ( <NavItem data-hash={hash} key={hash} selected={selected} level={l.level} onClick={(event) => { this.handleItemClick({ event, hash }); }} > {l.label} </NavItem> ) : null; })} </Nav> </ScrollSpy> </Sticky> ) : null; } } export default NavSection; const Nav = (props: JSX.IntrinsicElements['div']) => ( <div css={{ [smallDevice]: { backgroundColor: 'rgba(255, 255, 255, 0.96)', boxShadow: 'inset 0 -1px 0 rgba(0, 0, 0, 0.1)', display: 'flex', fontSize: 13, marginLeft: -appGutter, marginRight: -appGutter, overflowX: 'auto', WebkitOverflowScrolling: 'touch', }, [largeDevice]: { display: 'block', float: 'left', paddingBottom: contentGutter, paddingTop: contentGutter, width: navWidth, zIndex: 1, }, }} {...props} /> ); interface NavItemProps { readonly level: number; readonly selected: boolean; } const NavItem = ({ level, selected, ...props }: NavItemProps & JSX.IntrinsicElements['div']) => ( <div role="button" css={{ color: selected ? '#091e42' : '#7A869A', cursor: 'pointer', display: 'inline-block', padding: `15px ${appGutter}px`, position: 'relative', textDecoration: 'none', whiteSpace: 'nowrap', ':hover, :active': { color: selected ? 'hsl(0, 0%, 10%)' : '#2684FF', }, [smallDevice]: { display: level === 3 ? 'none' : undefined, boxShadow: selected ? 'inset 0 -1px 0 black' : undefined, padding: `10px ${appGutter}px`, }, [largeDevice]: { backgroundColor: selected ? 'white' : 'transparent', display: 'block', fontSize: level === 3 ? '0.9em' : undefined, fontWeight: selected ? 800 : 'inherit', padding: '10px 20px 10px 0', paddingLeft: level === 3 ? 10 : 0, transition: 'padding-left 150ms ease-out', }, }} {...props} /> ); ================================================ FILE: docs/App/ScrollSpy.tsx ================================================ import React, { Component, ReactElement, RefCallback } from 'react'; import rafSchedule from 'raf-schd'; import NodeResolver from 'react-node-resolver'; interface Props { readonly children: ReactElement; readonly onChange: (elements: readonly (string | null)[]) => void; } interface State { readonly elements: readonly HTMLElement[]; } function getStyle(el: HTMLElement, prop: string) { const val = window.getComputedStyle(el, null).getPropertyValue(prop); return parseFloat(val); } function isInView(el: HTMLElement) { let rect = el.getBoundingClientRect(); const topOffset = (getStyle(el, 'padding-top') + getStyle(el, 'margin-top')) * -1; if (rect.top >= topOffset && rect.bottom <= window.innerHeight) { return true; } return false; } export default class ScrollSpy extends Component<Props, State> { nav: HTMLElement | undefined; allIds = []; state: State = { elements: [] }; static defaultProps = { preserveHeight: false }; componentDidMount() { window.addEventListener('scroll', this.handleScroll, false); this.buildNodeList(); } componentWillUnmount() { window.removeEventListener('scroll', this.handleScroll); } handleScroll = rafSchedule((event: Event) => { event.preventDefault(); const { onChange } = this.props; const { elements } = this.state; if (!elements.length) return; const idsInView = elements .filter(isInView) .map((i) => i.getAttribute('id')); if (idsInView.length) { onChange(idsInView); } }); getElements: RefCallback<HTMLElement> = (ref) => { if (!ref) return; this.nav = ref; }; buildNodeList = () => { if (!this.nav) return; const anchorList = this.nav.querySelectorAll<HTMLElement>('[data-hash]'); const elements = Array.from(anchorList).map( (i) => document.querySelector<HTMLElement>(`#${i.dataset.hash}`)! ); this.setState({ elements }); }; render() { return ( <NodeResolver innerRef={this.getElements}> {this.props.children} </NodeResolver> ); } } ================================================ FILE: docs/App/Section.tsx ================================================ import React, { FunctionComponent } from 'react'; import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom'; import routes from './routes'; const Section: FunctionComponent<RouteComponentProps> = () => { const routeKeys = Object.keys(routes); return ( <Switch> {routeKeys.map((r) => ( <Route key={r} path={r} render={(p) => <Content {...p} />} /> ))} <Redirect from="/" to="/home" /> </Switch> ); }; const Content = ({ location, match }: RouteComponentProps) => { const page = routes[match.path]; return ( <Route path={match.path} render={() => ( <Switch> <Route exact key={location.key} path={match.path} component={page} /> </Switch> )} /> ); }; export default Section; ================================================ FILE: docs/App/Sticky.tsx ================================================ import React, { Component, CSSProperties, RefCallback } from 'react'; import rafSchedule from 'raf-schd'; interface Props { readonly preserveHeight: boolean; } interface State { readonly height: number | 'auto'; readonly isFixed: boolean; readonly overScroll: number; readonly scrollHeight: number | null | undefined; readonly width: number | 'auto'; } export default class Sticky extends Component<Props, State> { innerEl: HTMLDivElement | undefined; outerEl: HTMLDivElement | undefined; state: State = { height: 'auto', isFixed: false, overScroll: 0, scrollHeight: null, width: 'auto', }; static defaultProps = { preserveHeight: false }; componentDidMount() { window.addEventListener('scroll', this.handleScroll, false); // this.handleScroll(); } componentWillUnmount() { window.removeEventListener('scroll', this.handleScroll); } handleScroll = rafSchedule((event: Event) => { if (!this.innerEl || !this.outerEl) return; const offsetBottom = 88; // footer height const { top: outerTop } = this.outerEl.getBoundingClientRect(); const innerTop = (this.innerEl.offsetTop && this.innerEl.offsetTop) || 0; const scrollY = window.pageYOffset; const maxScroll = document.body && document.body.scrollHeight - window.innerHeight - offsetBottom; const { isFixed, overScroll } = this.state; // check for `isFixed` before setting state to prevent thrashing if (isFixed && outerTop > 0) { this.setState({ isFixed: false }); } else if (!isFixed && scrollY >= innerTop) { this.setState({ isFixed: true }); } // handle over scroll if (maxScroll && scrollY >= maxScroll) { this.setState({ overScroll: scrollY - maxScroll }); } else if (overScroll > 0 && scrollY < maxScroll) { this.setState({ overScroll: 0 }); } }); getOuterEl: RefCallback<HTMLDivElement> = (ref) => { if (!ref) return; this.outerEl = ref; }; getInnerEl: RefCallback<HTMLDivElement> = (ref) => { if (!ref) return; this.innerEl = ref; // get dimensions once, we're not interested in resize events const firstChild = ref.firstElementChild!; const availableHeight = window.innerHeight; let { height, width } = firstChild.getBoundingClientRect(); let scrollHeight; if (typeof this.state.height !== 'number') { if (height > availableHeight) scrollHeight = availableHeight; this.setState({ height, scrollHeight, width }); } }; render() { const { preserveHeight } = this.props; const { height, isFixed, overScroll, scrollHeight, width } = this.state; const outerStyle = isFixed && preserveHeight ? { height } : undefined; const fixedStyles: CSSProperties = { position: 'fixed', top: 0, width, zIndex: 1, }; const scrollStyles = scrollHeight ? { height: overScroll ? scrollHeight - overScroll : scrollHeight, overflow: 'scroll', } : null; const innerStyle = isFixed ? { ...fixedStyles, ...scrollStyles } : undefined; return ( <div ref={this.getOuterEl} style={outerStyle}> <div ref={this.getInnerEl} style={innerStyle}> {this.props.children} </div> </div> ); } } ================================================ FILE: docs/App/TwitterButton.tsx ================================================ /** @jsx jsx */ import { jsx } from '@emotion/react'; const TwitterButton = () => ( <div css={{ alignItems: 'center', display: 'inline-flex' }}> <a aria-label="Follow @JedWatson on Twitter" css={{ alignItems: 'center', display: 'flex', borderRadius: 2, color: 'white', border: '1px solid rgba(255, 255, 255, 0.3)', background: 'rgba(255, 255, 255, 0.1)', cursor: 'pointer', fontSize: 13, fontWeight: 'bold', padding: '5px 12px 5px 8px', marginLeft: '16px', position: 'relative', textDecoration: 'none', ':hover': { border: '1px solid rgba(255, 255, 255, 0.4)', background: 'rgba(255, 255, 255, 0.15)', }, }} href="https://twitter.com/JedWatson" target="_blank" > <svg version="1.1" width="16" height="16" viewBox="0 0 2000 1625.36" fill="currentColor" aria-hidden="true" > <path d="m 1999.9999,192.4 c -73.58,32.64 -152.67,54.69 -235.66,64.61 84.7,-50.78 149.77,-131.19 180.41,-227.01 -79.29,47.03 -167.1,81.17 -260.57,99.57 C 1609.3399,49.82 1502.6999,0 1384.6799,0 c -226.6,0 -410.328,183.71 -410.328,410.31 0,32.16 3.628,63.48 10.625,93.51 -341.016,-17.11 -643.368,-180.47 -845.739,-428.72 -35.324,60.6 -55.5583,131.09 -55.5583,206.29 0,142.36 72.4373,267.95 182.5433,341.53 -67.262,-2.13 -130.535,-20.59 -185.8519,-51.32 -0.039,1.71 -0.039,3.42 -0.039,5.16 0,198.803 141.441,364.635 329.145,402.342 -34.426,9.375 -70.676,14.395 -108.098,14.395 -26.441,0 -52.145,-2.578 -77.203,-7.364 52.215,163.008 203.75,281.649 383.304,284.946 -140.429,110.062 -317.351,175.66 -509.5972,175.66 -33.1211,0 -65.7851,-1.949 -97.8828,-5.738 181.586,116.4176 397.27,184.359 628.988,184.359 754.732,0 1167.462,-625.238 1167.462,-1167.47 0,-17.79 -0.41,-35.48 -1.2,-53.08 80.1799,-57.86 149.7399,-130.12 204.7499,-212.41" /> </svg> <span css={{ paddingLeft: 8 }}>Follow</span> </a> </div> ); export default TwitterButton; ================================================ FILE: docs/App/components.tsx ================================================ /** @jsx jsx */ import { Component } from 'react'; import { Link, LinkProps, RouteComponentProps, withRouter, } from 'react-router-dom'; import { jsx } from '@emotion/react'; const navWidth = 180; const appWidth = 800; const appGutter = 20; const contentGutter = 30; const smallDevice = '@media (max-width: 769px)'; const largeDevice = '@media (min-width: 770px)'; export const AppContainer = (props: JSX.IntrinsicElements['div']) => ( <div css={{ boxSizing: 'border-box', marginLeft: 'auto', marginRight: 'auto', maxWidth: appWidth, minHeight: '100vh', padding: `0 ${appGutter}px`, }} {...props} /> ); export const PageContent = (props: JSX.IntrinsicElements['div']) => ( <div css={{ paddingBottom: contentGutter * 4, paddingTop: contentGutter, [smallDevice]: { paddingTop: contentGutter * 2, }, }} {...props} /> ); export const AppContent = (props: JSX.IntrinsicElements['div']) => ( <div css={{ flex: '1 1 auto', marginLeft: 'auto', marginRight: 'auto', [largeDevice]: { paddingLeft: navWidth + contentGutter, }, }} {...props} /> ); // ============================== // Navigation // ============================== export const PrimaryNav = (props: JSX.IntrinsicElements['div']) => ( <div css={{ backgroundColor: 'rgba(0, 0, 0, 0.11)', fontWeight: 500, overflowX: 'auto', top: 0, width: '100%', WebkitOverflowScrolling: 'touch', }} > <div css={{ boxSizing: 'border-box', display: 'flex', maxWidth: 800, marginLeft: 'auto', marginRight: 'auto', }} {...props} /> </div> ); export const PrimaryNavItem = ({ selected, ...props }: LinkProps & { readonly selected: boolean }) => ( <Link css={{ color: selected ? 'white' : '#DEEBFF', display: 'inline-block', opacity: selected ? 1 : 0.8, padding: `20px ${appGutter}px`, position: 'relative', textDecoration: 'none', whiteSpace: 'nowrap', ':hover, :active': { opacity: 1, }, [smallDevice]: { fontSize: 13, padding: `10px ${appGutter}px`, }, }} {...props} /> ); // ============================== // Scroll Restoration // ============================== // Return scroll to top on route change class ScrollToTop extends Component<RouteComponentProps> { componentDidUpdate(prevProps: RouteComponentProps) { const { history, location } = this.props; // do not influence scroll on browser back/forward if (history.action === 'POP') return; // no scroll when extending the current path const pathArr = location.pathname.split('/'); if (!prevProps.location.pathname.includes(pathArr[1])) { window.scrollTo(0, 0); } } render() { return this.props.children; } } export const ScrollRestoration = withRouter(ScrollToTop); ================================================ FILE: docs/App/index.tsx ================================================ import React, { Component, Fragment } from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { Helmet } from 'react-helmet'; import Header from './Header'; import Footer from './Footer'; // import NoMatch from '../NoMatch'; import { AppContainer, AppContent, PageContent, PrimaryNav, PrimaryNavItem, ScrollRestoration, } from './components'; import Section from './Section'; import PageNav from './PageNav'; import Tests from '../Tests'; const sections = [ { label: 'Home', path: '/home' }, { label: 'Props', path: '/props' }, { label: 'Styles', path: '/styles' }, { label: 'Components', path: '/components' }, { label: 'Async', path: '/async' }, { label: 'Creatable', path: '/creatable' }, { label: 'Advanced', path: '/advanced' }, { label: 'TypeScript', path: '/typescript' }, { label: 'Upgrading', path: '/upgrade' }, ]; export default class App extends Component { render() { return ( <BrowserRouter> <Switch> <Route exact path="/cypress-tests" component={Tests} /> <Route> <div> <Header> <Route render={({ location }) => ( <PrimaryNav> {sections.map((l) => { const selected = location.pathname.includes(l.path); return ( <PrimaryNavItem key={l.path} selected={selected} to={l.path} > {l.label} </PrimaryNavItem> ); })} </PrimaryNav> )} /> </Header> <ScrollRestoration> <AppContainer> <Helmet> <title>React Select (
)} />