diff --git a/.circleci/config.yml b/.circleci/config.yml index 98e3d657ae..f96ae44433 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,7 +43,7 @@ default-job: &default-job REACT_VERSION: << parameters.react-version >> TEST_GATE: << parameters.test-gate >> AWS_REGION_ARTIFACTS: eu-central-1 - working_directory: /tmp/material-ui + working_directory: /tmp/base-ui docker: - image: cimg/node:18.20 @@ -89,7 +89,7 @@ commands: corepack enable - run: name: Prepare playwright hash - command: pnpm list --json --filter playwright > /tmp/playwright_info.json + command: pnpm list --recursive --json --filter @mui-internal/tests playwright > /tmp/playwright_info.json - store_artifacts: name: Debug playwright hash path: /tmp/playwright_info.json @@ -156,7 +156,7 @@ jobs: name: Should not have any git not staged command: git add -A && git diff --exit-code --staged - run: - name: Check for duplicated packages + name: '`pnpm dedupe` was run?' command: | # #default-branch-switch if [[ $(git diff --name-status master | grep -E 'pnpm-workspace\.yaml|pnpm-lock.yaml|package\.json') == "" ]]; @@ -319,7 +319,7 @@ jobs: <<: *default-job resource_class: 'medium+' docker: - - image: mcr.microsoft.com/playwright:v1.43.1-focal + - image: mcr.microsoft.com/playwright:v1.46.1-focal environment: NODE_ENV: development # Needed if playwright is in `devDependencies` steps: @@ -350,7 +350,7 @@ jobs: test_profile: <<: *default-job docker: - - image: mcr.microsoft.com/playwright:v1.43.1-focal + - image: mcr.microsoft.com/playwright:v1.46.1-focal environment: NODE_ENV: development # Needed if playwright is in `devDependencies` steps: @@ -378,7 +378,7 @@ jobs: test_regressions: <<: *default-job docker: - - image: mcr.microsoft.com/playwright:v1.43.1-focal + - image: mcr.microsoft.com/playwright:v1.46.1-focal environment: NODE_ENV: development # Needed if playwright is in `devDependencies` steps: @@ -395,13 +395,16 @@ jobs: test_e2e: <<: *default-job docker: - - image: mcr.microsoft.com/playwright:v1.43.1-focal + - image: mcr.microsoft.com/playwright:v1.46.1-focal environment: NODE_ENV: development # Needed if playwright is in `devDependencies` steps: - checkout - install_js: browsers: true + - run: + name: install Playwright browsers + command: pnpm exec playwright install - run: name: pnpm test:e2e command: pnpm test:e2e diff --git a/.eslintignore b/.eslintignore index f7de1dac5e..a32c2b2373 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,6 +14,7 @@ /tmp .next build +build-tests node_modules .nyc_output pnpm-lock.yaml diff --git a/.eslintrc.js b/.eslintrc.js index 2244d79083..6885f32f2c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,11 @@ const baseline = require('@mui/monorepo/.eslintrc'); const path = require('path'); +const OneLevelImportMessage = [ + 'Prefer one level nested imports to avoid bundling everything in dev mode or breaking CJS/ESM split.', + 'See https://github.com/mui/material-ui/pull/24147 for the kind of win it can unlock.', +].join('\n'); + module.exports = { ...baseline, settings: { @@ -16,8 +21,30 @@ module.exports = { */ rules: { ...baseline.rules, - // TODO move to @mui/monorepo, codebase is moving away from default exports + // TODO move to @mui/monorepo, codebase is moving away from default exports https://github.com/mui/material-ui/issues/21862 'import/prefer-default-export': 'off', + 'import/export': 'off', // Mostly handled by Typescript itself. ESLint produces false positives with declaration merging. + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: [ + '@mui/*/*/*', + '@pigment-css/*/*/*', + '@base_ui/react/*/*', + '!@base_ui/react/legacy/*', + // Allow any import depth with any internal packages + '!@mui/internal-*/**', + // TODO delete, @mui/docs should be @mui/internal-docs + '!@mui/docs/**', + ], + message: OneLevelImportMessage, + }, + ], + }, + ], + '@typescript-eslint/no-redeclare': 'off', }, overrides: [ ...baseline.overrides, @@ -30,5 +57,15 @@ module.exports = { 'no-console': 'off', }, }, + { + files: ['packages/**/*.test{.tsx,.js}'], + excludedFiles: 'packages/mui-base/src/legacy/**/*.*', + extends: ['plugin:testing-library/react'], + rules: { + 'testing-library/prefer-screen-queries': 'off', // TODO: enable and fix + 'testing-library/no-container': 'off', // TODO: enable and fix + 'testing-library/render-result-naming-convention': 'off', // False positives + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..2a367f8645 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +* @atomiks @michaldudak +/docs/data/ @atomiks @colmtuite @michaldudak +/examples/ @atomiks @colmtuite @michaldudak +/packages/mui-base/ @atomiks @colmtuite @michaldudak +/scripts/ @michaldudak diff --git a/.github/ISSUE_TEMPLATE/1.bug.yml b/.github/ISSUE_TEMPLATE/1.bug.yml index cc3c360e71..d7e80d17b0 100644 --- a/.github/ISSUE_TEMPLATE/1.bug.yml +++ b/.github/ISSUE_TEMPLATE/1.bug.yml @@ -59,3 +59,9 @@ body: Output from `npx @mui/envinfo` goes here. ``` + - type: markdown + attributes: + value: | + ## :heart: Love Base UI? + + Consider donating $10 to sustain our open-source work: [https://opencollective.com/mui-org](https://opencollective.com/mui-org). diff --git a/.github/ISSUE_TEMPLATE/2.feature.yml b/.github/ISSUE_TEMPLATE/2.feature.yml index 64065b37c8..d215d51558 100644 --- a/.github/ISSUE_TEMPLATE/2.feature.yml +++ b/.github/ISSUE_TEMPLATE/2.feature.yml @@ -32,3 +32,9 @@ body: attributes: label: Motivation description: What are you trying to accomplish? Providing context helps us come up with a solution that is more useful in the real world. + - type: markdown + attributes: + value: | + ## :heart: Love Base UI? + + Consider donating $10 to sustain our open-source work: [https://opencollective.com/mui-org](https://opencollective.com/mui-org). diff --git a/.github/ISSUE_TEMPLATE/4.docs-feedback.yml b/.github/ISSUE_TEMPLATE/4.docs-feedback.yml index 0d6b1fb2c3..5c88a7b20e 100644 --- a/.github/ISSUE_TEMPLATE/4.docs-feedback.yml +++ b/.github/ISSUE_TEMPLATE/4.docs-feedback.yml @@ -42,3 +42,9 @@ body: attributes: label: Context description: What are you trying to accomplish? Providing context helps us come up with a solution that is more useful in the real world. + - type: markdown + attributes: + value: | + ## :heart: Love Base UI? + + Consider donating $10 to sustain our open-source work: [https://opencollective.com/mui-org](https://opencollective.com/mui-org). diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f901ad8cb8..9186efbffe 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ contact_links: - name: Support ❔ - url: https://mui.com/getting-started/support/ + url: https://mui.com/base-ui/getting-started/support/ about: I need support with Base UI. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abf1ff9ef5..a0a67c01b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,6 @@ on: # We don't need to run CI twice (push+pull_request) - 'renovate/**' pull_request: - paths-ignore: - # should sync with ci-check.yml as a workaround to bypass github checks - - 'docs/**' - - 'examples/**' permissions: {} @@ -25,14 +21,14 @@ jobs: os: [macos-latest, windows-latest, ubuntu-latest] steps: - run: echo "${{ github.actor }}" - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: # fetch all tags which are required for `pnpm release:changelog` fetch-depth: 0 - name: Set up pnpm uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 - name: Use Node.js 18.x - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version: 18 cache: 'pnpm' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-dependencies diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 440aea6468..71c1bc1f9d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,10 +16,10 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: languages: typescript config-file: ./.github/codeql/codeql-config.yml @@ -30,4 +30,4 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index 487e4cb502..cfc9752720 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -29,7 +29,7 @@ jobs: steps: - run: echo "${{ github.actor }}" - name: check if prs are dirty - uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 + uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2 with: dirtyLabel: 'PR: out-of-date' removeOnDirtyLabel: 'PR: ready to ship' diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 69f8a46048..d3252e8d2c 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -22,12 +22,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -43,6 +43,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: sarif_file: results.sarif diff --git a/.github/workflows/support-stackoverflow.yml b/.github/workflows/support-stackoverflow.yml index c9d8fa6bb0..381370f9ed 100644 --- a/.github/workflows/support-stackoverflow.yml +++ b/.github/workflows/support-stackoverflow.yml @@ -22,14 +22,14 @@ jobs: # Comment to post on issues marked as support requests. Add a link # to a support page, or set to `false` to disable issue-comment: | - 👋 Thanks for using MUI Core! + 👋 Thanks for using this project! We use GitHub issues exclusively as a bug and feature requests tracker, however, this issue appears to be a support request. - For support, please check out https://mui.com/getting-started/support/. Thanks! + For support with Base UI please check out https://mui.com/base-ui/getting-started/support/. Thanks! - If you have a question on Stack Overflow, you are welcome to link to it here, it might help others. + If you have a question on Stack Overflow, you are welcome to link to it here, it might help others. If your issue is subsequently confirmed as a bug, and the report follows the issue template, it can be reopened. close-issue: true lock-issue: false diff --git a/.github/workflows/vale-action.yml b/.github/workflows/vale-action.yml index 3fc7d562e1..7a76e0d2c4 100644 --- a/.github/workflows/vale-action.yml +++ b/.github/workflows/vale-action.yml @@ -12,7 +12,7 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: errata-ai/vale-action@38bf078c328061f59879b347ca344a718a736018 # v2.1.0 continue-on-error: true with: diff --git a/.gitignore b/.gitignore index 8dd0f7c555..a48dc934e1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ # created by netlify dev (to perform local debug) .netlify build +build-tests node_modules package-lock.json size-snapshot.json diff --git a/.mocharc.js b/.mocharc.js index 9425179503..6fd107f5c0 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -10,7 +10,11 @@ module.exports = { recursive: true, timeout: (process.env.CIRCLECI === 'true' ? 5 : 2) * 1000, // Circle CI has low-performance CPUs. reporter: 'dot', - require: ['@mui/internal-test-utils/setupBabel', '@mui/internal-test-utils/setupJSDOM'], + require: [ + '@mui/internal-test-utils/setupBabel', + '@mui/internal-test-utils/setupJSDOM', + './packages/mui-base/test/setup.ts', + ], 'watch-ignore': [ // default '.git', diff --git a/CHANGELOG.md b/CHANGELOG.md index a79661989f..bb7912268f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,85 @@ # Versions +## v1.0.0-alpha.2 + +_Aug 19, 2024_ + +A big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +⭐ We added many new components: AlertDialog, Dialog, Field, Menu, Popover, PreviewCard, Progress, Slider, and Tooltip. + +### `@base_ui/react@1.0.0-alpha.2` + +- [Checkbox] Fix checked change when clicking button with wrapping label (#467) @atomiks +- [Dialog] Create new component and hook (#372) @michaldudak +- [Field] Create new Field components (#477) @atomiks +- [Menu] Overhaul the component API (#468) @michaldudak +- [NumberField] Fix tests on non-English locale machines (#524) @michaldudak +- [NumberField] Rename `onChange` prop to `onValueChange` (#464) @atomiks +- [Popover] Component and Hook (#381) @atomiks +- [Popover] Fix `keepMounted` focus management (#489) @atomiks +- [Popover] Wait for focus to settle in tests (#491) @michaldudak +- [PreviewCard] Create new component (#469) @atomiks +- [PreviewCard] Fix Firefox browser hang (#490) @atomiks +- [Progress] New `Progress` components (#470) @mj12albert +- [Slider] improve `disabled` prop description (#527) @sai6855 +- [Slider] New Slider components and hook (#373) @mj12albert +- [Switch/Checkbox] Rename `onChange` prop to `onCheckedChange` (#465) @atomiks +- [Tabs] Fix indicator tests (#379) @michaldudak +- [Tooltip] Component and Hook (#264) @atomiks +- [Tooltip] Fix animations (#426) @atomiks +- [useCompoundParent] Display `displayName` only in dev (#525) @sai6855 + +### Docs + +- [docs] Add badges like in Material UI @oliviertassinari +- [docs] Add the logo to the README (#448) @danilo-leal +- [docs] Convert alpha component docs to new docs template (#392) @colmtuite +- [docs] Correct Bundlephobia links (#419) @michaldudak +- [docs] Fix page description line break @oliviertassinari +- [docs] Fix the X link (#450) @michaldudak +- [docs] Fix Vale errors (#492) @oliviertassinari +- [docs] Prepare security table for once it has its first release (#536) @oliviertassinari +- [docs] Update twitter.com to x.com @oliviertassinari +- [docs][Tooltip] Use the correct version of ComponentLinkHeader (#425) @michaldudak + +### Core + +- [code-infra] Fix pnpm version in package.json engines (#409) @Janpot +- [code-infra] Propagate API docs builder package interface changes (#478) @LukasTy +- [code-infra] Remove raw-loader (#404) @michaldudak +- [code-infra] Use shared .stylelintrc.js config (#415) @oliviertassinari +- [core] Add `trackAnchor` prop for anchor positioning (#519) @atomiks +- [core] Add `useAnchorPositioning` Hook (#461) @atomiks +- [core] Add `useTransitionStatus` and `useExecuteIfNotAnimated` Hooks (#396) @atomiks +- [core] Add codeowners file (#447) @michaldudak +- [core] Allow Renovate to update pnpm (#446) @michaldudak +- [core] Encapsulate the common rendering logic in `useComponentRenderer` (#408) @michaldudak +- [core] Fix event naming convention @oliviertassinari +- [core] Improve performance of `mergeReactProps` (#456) @marcpachecog +- [core] Improve Tooltip and Popover consistency (#463) @atomiks +- [core] Link GH issue for import/prefer-default-export @oliviertassinari +- [core] Make pnpm version permissive (#529) @atomiks +- [core] Move hooks under component directories (#405) @michaldudak +- [core] Move legacy components to a subdirectory (#410) @michaldudak +- [core] Normalize rest / other to match the most common used @oliviertassinari +- [core] Refactor animation hooks (#417) @atomiks +- [core] Remove sources of old components we don't intend to support (#474) @michaldudak +- [core] Simpler pnpm dedupe error message to act on @oliviertassinari +- [core] Upgrade to core-js v3 (#418) @atomiks +- [core] Verify types in test code (#457) @michaldudak +- [dependencies] Do not try to update eslint (#515) @michaldudak +- [docs-infra] Fix a stylelint issue (#421) @oliviertassinari +- [docs-infra] Integrate the latest @mui/docs (#378) @michaldudak +- [test] Clean up and unify test code (#532) @michaldudak +- [test] Update test-utils and remove enzyme (#473) @michaldudak +- [test] Use internal-test-utils from npm (#424) @michaldudak +- [typescript] Add `type` to export statements (#544) @michaldudak +- [website] Fix /base-ui/ code duplication (#416) @oliviertassinari- [infra] Add support donation button @oliviertassinari +- [website] Redirect to an existing page on dev (#445) @michaldudak + +All contributors of this release in alphabetical order: @atomiks, @colmtuite, @danilo-leal, @Janpot, @LukasTy, @marcpachecog, @michaldudak, @mj12albert, @oliviertassinari, @sai6855 + ## v1.0.0-alpha.1 diff --git a/CHANGELOG.old.md b/CHANGELOG.old.md index 62dd089893..141a6d803a 100644 --- a/CHANGELOG.old.md +++ b/CHANGELOG.old.md @@ -1186,7 +1186,7 @@ _Nov 16, 2021_ - [core] Rename mui/core to mui/base (#29585) @michaldudak - Based on the results of the [poll](https://twitter.com/michaldudak/status/1452630484706635779) and our internal discussions, we decided to rename the `@mui/core` package to `@mui/base`. The main rationale for this is the fact that we use the term "Core" to refer to the core components product family, the one that includes Material Design components, unstyled components, System utilities, etc. Therefore, @mui/core was effectively a subset of MUI Core. This was confusing. + Based on the results of the [poll](https://x.com/michaldudak/status/1452630484706635779) and our internal discussions, we decided to rename the `@mui/core` package to `@mui/base`. The main rationale for this is the fact that we use the term "Core" to refer to the core components product family, the one that includes Material Design components, unstyled components, System utilities, etc. Therefore, @mui/core was effectively a subset of MUI Core. This was confusing. The new name better reflects the purpose of the package: it contains unstyled components, hooks, and utilities that serve as a **base** to build on. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cbba88951..5686e6d3b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to Base UI -## Base UI vs. Material UI +## Base UI vs. MUI organization Base UI is an open-source project of the MUI organization. The repositories are part of the same codebase. `mui/base-ui` imports the code infrastructure from [`mui/material-ui`](https://github.com/mui/material-ui). diff --git a/README.md b/README.md index ec3c87be03..717b477d51 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,26 @@ +

+ + + + + Base UI + + +

-

Base UI

- -[Base UI](https://mui.com/base-ui/) is a library of unstyled React UI components and hooks. With Base UI, you gain complete control over your app's CSS and accessibility features. +

+Base UI is an unstyled UI component library for building accessible user interfaces while maintaining complete control over styling. +

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mui/base-ui/blob/HEAD/LICENSE) [![npm latest package](https://img.shields.io/npm/v/@base_ui/react/latest.svg)](https://www.npmjs.com/package/@base_ui/react) [![npm downloads](https://img.shields.io/npm/dm/@base_ui/react.svg)](https://www.npmjs.com/package/@base_ui/react) -[![CircleCI](https://circleci.com/gh/mui/base-ui/tree/master.svg?style=shield)](https://app.circleci.com/pipelines/github/mui/base-ui?branch=master) -[![Coverage status](https://img.shields.io/codecov/c/github/mui/base-ui/master.svg)](https://codecov.io/gh/mui/base-ui/branch/master) -[![Follow on X](https://img.shields.io/twitter/follow/MUI_hq.svg?label=follow+MUI)](https://twitter.com/MUI_hq) +[![GitHub branch status](https://img.shields.io/github/checks-status/mui/base-ui/HEAD)](https://github.com/mui/base-ui/commits/HEAD/) +[![Coverage status](https://img.shields.io/codecov/c/github/mui/base-ui.svg)](https://app.codecov.io/gh/mui/base-ui/) +[![Follow on X](https://img.shields.io/twitter/follow/Base_UI.svg?label=follow+Base+UI)](https://x.com/Base_UI) [![Renovate status](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://github.com/mui/base-ui/issues/2) [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/mui/base-ui.svg)](https://isitmaintained.com/project/mui/base-ui 'Average time to resolve an issue') [![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/mui-org)](https://opencollective.com/mui-org) @@ -19,12 +28,14 @@
+--- + ## Documentation -Visit [https://mui.com/base-ui/](https://mui.com/base-ui/) to view the full documentation. +Visit [mui.com/base-ui/getting-started](https://mui.com/base-ui/getting-started/) to view the full documentation. -**Note**: Base UI's API is currently being revised; there will be no new features or components added to the current implementation. -Learn more about plans for Base UI in [this blog post](https://mui.com/blog/base-ui-2024-plans/). +> **Note**: Base UI's API is currently being revised; there will be no new features or components added to the current implementation. +> Learn more about the roadmap in [this blog post](https://mui.com/blog/base-ui-2024-plans/). ## Sponsors @@ -40,8 +51,6 @@ Diamond sponsors are those who have pledged \$1,500/month or more to MUI. ### Gold 🏆 -via [Open Collective](https://opencollective.com/mui-org) or via [Patreon](https://www.patreon.com/oliviertassinari) -

tidelift.com Spotify @@ -54,6 +63,7 @@ via [Open Collective](https://opencollective.com/mui-org) or via [Patreon](http

Gold sponsors are those who have pledged \$500/month or more to MUI. +Via [Open Collective](https://opencollective.com/mui-org) or via [Patreon](https://www.patreon.com/oliviertassinari). ### More backers diff --git a/SECURITY.md b/SECURITY.md index 5f24d987cf..b347ad65c0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,13 @@ # Security policy +## Supported versions + +The versions of the project that are currently supported with security updates. + +| Base UI version | Release | Supported | +| --------------: | :------ | :----------------- | +| <=1.0.0 | TBD | :white_check_mark: | + ## Reporting a vulnerability You can report a vulnerability by contacting us via email at [security@mui.com](mailto:security@mui.com). diff --git a/docs/data/base/components/accordion/accordion.md b/docs/data/base/components/accordion/accordion.md deleted file mode 100644 index a8b814bb19..0000000000 --- a/docs/data/base/components/accordion/accordion.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -productId: base-ui -title: React Accordion component -githubLabel: 'component: accordion' -waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/ ---- - -# Accordion 🚧 - -

Accordions let users show and hide sections of related content on a page.

- -:::warning -The Base UI Accordion component isn't available yet, but you can upvote [this GitHub issue](https://github.com/mui/base-ui/issues/25) to see it arrive sooner. -::: diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.js b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.js new file mode 100644 index 0000000000..6c34a8e923 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.js @@ -0,0 +1,117 @@ +import * as React from 'react'; +import * as AlertDialog from '@base_ui/react/AlertDialog'; +import { useTheme } from '@mui/system'; + +export default function AlertDialogIntroduction() { + return ( + + + Subscribe + + + Subscribe + + Are you sure you want to subscribe? + +
+ Yes + No +
+
+
+ +
+ ); +} + +function useIsDarkMode() { + const theme = useTheme(); + return theme.palette.mode === 'dark'; +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +function Styles() { + // Replace this with your app logic for determining dark mode + const isDarkMode = useIsDarkMode(); + + return ( + + ); +} diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.tsx b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.tsx new file mode 100644 index 0000000000..6c34a8e923 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/css/index.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import * as AlertDialog from '@base_ui/react/AlertDialog'; +import { useTheme } from '@mui/system'; + +export default function AlertDialogIntroduction() { + return ( + + + Subscribe + + + Subscribe + + Are you sure you want to subscribe? + +
+ Yes + No +
+
+
+ +
+ ); +} + +function useIsDarkMode() { + const theme = useTheme(); + return theme.palette.mode === 'dark'; +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +function Styles() { + // Replace this with your app logic for determining dark mode + const isDarkMode = useIsDarkMode(); + + return ( + + ); +} diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.js b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.js new file mode 100644 index 0000000000..6f2f0907cd --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.js @@ -0,0 +1,104 @@ +import * as React from 'react'; +import * as AlertDialog from '@base_ui/react/AlertDialog'; +import { styled } from '@mui/system'; + +export default function AlertDialogIntroduction() { + return ( + + Subscribe + + + Subscribe + Are you sure you want to subscribe? + + Yes + No + + + + ); +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +const TriggerButton = styled(AlertDialog.Trigger)( + ({ theme }) => ` + background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + padding: 8px 16px; + border-radius: 4px; + border: none; + font-family: "IBM Plex Sans", sans-serif; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; + } +`, +); + +const Popup = styled(AlertDialog.Popup)( + ({ theme }) => ` + background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]}; + min-width: 400px; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px; + position: fixed; + top: 50%; + left: 50%; + font-family: "IBM Plex Sans", sans-serif; + transform: translate(-50%, -50%); + padding: 16px; + z-index: 2100; +`, +); + +const Controls = styled('div')( + ({ theme }) => ` + display: flex; + flex-direction: row-reverse; + background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; + gap: 8px; + padding: 16px; + margin: 32px -16px -16px; +`, +); + +const CloseButton = styled(AlertDialog.Close)( + ({ theme }) => ` + background-color: transparent; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; + color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + padding: 8px 16px; + border-radius: 4px; + font-family: "IBM Plex Sans", sans-serif; + min-width: 80px; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + } +`, +); + +const Title = styled(AlertDialog.Title)` + font-size: 1.25rem; +`; + +const Description = styled(AlertDialog.Description)``; + +const Backdrop = styled(AlertDialog.Backdrop)` + background: rgb(0 0 0 / 0.35); + position: fixed; + inset: 0; + backdrop-filter: blur(4px); + z-index: 2000; +`; diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx new file mode 100644 index 0000000000..6f2f0907cd --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx @@ -0,0 +1,104 @@ +import * as React from 'react'; +import * as AlertDialog from '@base_ui/react/AlertDialog'; +import { styled } from '@mui/system'; + +export default function AlertDialogIntroduction() { + return ( + + Subscribe + + + Subscribe + Are you sure you want to subscribe? + + Yes + No + + + + ); +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +const TriggerButton = styled(AlertDialog.Trigger)( + ({ theme }) => ` + background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + padding: 8px 16px; + border-radius: 4px; + border: none; + font-family: "IBM Plex Sans", sans-serif; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; + } +`, +); + +const Popup = styled(AlertDialog.Popup)( + ({ theme }) => ` + background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]}; + min-width: 400px; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px; + position: fixed; + top: 50%; + left: 50%; + font-family: "IBM Plex Sans", sans-serif; + transform: translate(-50%, -50%); + padding: 16px; + z-index: 2100; +`, +); + +const Controls = styled('div')( + ({ theme }) => ` + display: flex; + flex-direction: row-reverse; + background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; + gap: 8px; + padding: 16px; + margin: 32px -16px -16px; +`, +); + +const CloseButton = styled(AlertDialog.Close)( + ({ theme }) => ` + background-color: transparent; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; + color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + padding: 8px 16px; + border-radius: 4px; + font-family: "IBM Plex Sans", sans-serif; + min-width: 80px; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + } +`, +); + +const Title = styled(AlertDialog.Title)` + font-size: 1.25rem; +`; + +const Description = styled(AlertDialog.Description)``; + +const Backdrop = styled(AlertDialog.Backdrop)` + background: rgb(0 0 0 / 0.35); + position: fixed; + inset: 0; + backdrop-filter: blur(4px); + z-index: 2000; +`; diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview new file mode 100644 index 0000000000..b074c5cbb8 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview @@ -0,0 +1,12 @@ + + Subscribe + + + Subscribe + Are you sure you want to subscribe? + + Yes + No + + + \ No newline at end of file diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js new file mode 100644 index 0000000000..c051ff4a3d --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js @@ -0,0 +1,72 @@ +import * as React from 'react'; +import * as AlertDialog from '@base_ui/react/AlertDialog'; + +export default function UnstyledDialogIntroduction() { + return ( + + Subscribe + + + Subscribe + Are you sure you want to subscribe? + + Yes + No + + + + ); +} + +function TriggerButton(props) { + const className = ` + bg-slate-900 dark:bg-slate-50 text-slate-50 dark:text-slate-900 + py-2 px-4 rounded min-w-[80px] border-none font-sans + hover:bg-slate-700 dark:hover:bg-slate-200`; + + return ; +} + +function Popup(props) { + const className = ` + bg-slate-50 dark:bg-slate-900 border-[1px] border-solid border-slate-100 dark:border-slate-700 + min-w-[400px] rounded shadow-xl fixed top-2/4 left-2/4 z-[2100] + -translate-x-2/4 -translate-y-2/4 p-4`; + + return ; +} + +function Controls(props) { + return ( +
+ ); +} + +function CloseButton(props) { + const className = ` + bg-transparent border-[1px] border-solid border-slate-500 dark:border-slate-300 + text-slate-900 dark:text-slate-50 py-2 px-4 rounded font-sans min-w-[80px] + hover:bg-slate-200 dark:hover:bg-slate-700`; + + return ; +} + +function Title(props) { + return ; +} + +function Description(props) { + return ; +} + +function Backdrop(props) { + return ( + + ); +} diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx new file mode 100644 index 0000000000..07d9cddfbf --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import * as AlertDialog from '@base_ui/react/AlertDialog'; + +export default function UnstyledDialogIntroduction() { + return ( + + Subscribe + + + Subscribe + Are you sure you want to subscribe? + + Yes + No + + + + ); +} + +function TriggerButton(props: AlertDialog.TriggerProps) { + const className = ` + bg-slate-900 dark:bg-slate-50 text-slate-50 dark:text-slate-900 + py-2 px-4 rounded min-w-[80px] border-none font-sans + hover:bg-slate-700 dark:hover:bg-slate-200`; + + return ; +} + +function Popup(props: AlertDialog.PopupProps) { + const className = ` + bg-slate-50 dark:bg-slate-900 border-[1px] border-solid border-slate-100 dark:border-slate-700 + min-w-[400px] rounded shadow-xl fixed top-2/4 left-2/4 z-[2100] + -translate-x-2/4 -translate-y-2/4 p-4`; + + return ; +} + +function Controls(props: React.ComponentPropsWithoutRef<'div'>) { + return ( +
+ ); +} + +function CloseButton(props: AlertDialog.CloseProps) { + const className = ` + bg-transparent border-[1px] border-solid border-slate-500 dark:border-slate-300 + text-slate-900 dark:text-slate-50 py-2 px-4 rounded font-sans min-w-[80px] + hover:bg-slate-200 dark:hover:bg-slate-700`; + + return ; +} + +function Title(props: AlertDialog.TitleProps) { + return ; +} + +function Description(props: AlertDialog.DescriptionProps) { + return ; +} + +function Backdrop(props: AlertDialog.BackdropProps) { + return ( + + ); +} diff --git a/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview new file mode 100644 index 0000000000..b074c5cbb8 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview @@ -0,0 +1,12 @@ + + Subscribe + + + Subscribe + Are you sure you want to subscribe? + + Yes + No + + + \ No newline at end of file diff --git a/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.js b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.js new file mode 100644 index 0000000000..7e8e7fd592 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.js @@ -0,0 +1,135 @@ +import * as React from 'react'; +import * as BaseAlertDialog from '@base_ui/react/AlertDialog'; +import { styled } from '@mui/system'; + +export default function AlertDialogWithTransitions() { + return ( + + Open + + Animated alert dialog + + This alert dialog uses CSS transitions on entry and exit. + + + Close + + + + + ); +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +const Popup = styled(BaseAlertDialog.Popup)( + ({ theme }) => ` + background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]}; + min-width: 400px; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px; + position: fixed; + top: 50%; + left: 50%; + font-family: IBM Plex Sans; + padding: 16px; + z-index: 2100; + transition-property: opacity, transform; + transition-duration: 150ms; + transition-timing-function: ease-in; + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + + &[data-state='open'] { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + transition-timing-function: ease-out; + } + + &[data-entering] { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + } +`, +); + +const Backdrop = styled(BaseAlertDialog.Backdrop)` + background-color: rgb(0 0 0 / 0.2); + position: fixed; + inset: 0; + z-index: 2000; + backdrop-filter: blur(0); + opacity: 0; + transition-property: opacity, backdrop-filter; + transition-duration: 250ms; + transition-timing-function: ease-in; + + &[data-state='open'] { + backdrop-filter: blur(6px); + opacity: 1; + transition-timing-function: ease-out; + } + + &[data-entering] { + backdrop-filter: blur(0); + opacity: 0; + } +`; + +const Title = styled(BaseAlertDialog.Title)` + font-size: 1.25rem; +`; + +const Trigger = styled(BaseAlertDialog.Trigger)( + ({ theme }) => ` + background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + padding: 8px 16px; + border-radius: 4px; + border: none; + font-family: + "IBM Plex Sans", + sans-serif; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; + } +`, +); + +const Close = styled(BaseAlertDialog.Close)( + ({ theme }) => ` + background-color: transparent; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; + color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + padding: 8px 16px; + border-radius: 4px; + font-family: IBM Plex Sans, sans-serif; + min-width: 80px; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + } +`, +); + +const Controls = styled('div')( + ({ theme }) => ` + display: flex; + flex-direction: row-reverse; + background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; + gap: 8px; + padding: 16px; + margin: 32px -16px -16px; +`, +); diff --git a/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx new file mode 100644 index 0000000000..7e8e7fd592 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx @@ -0,0 +1,135 @@ +import * as React from 'react'; +import * as BaseAlertDialog from '@base_ui/react/AlertDialog'; +import { styled } from '@mui/system'; + +export default function AlertDialogWithTransitions() { + return ( + + Open + + Animated alert dialog + + This alert dialog uses CSS transitions on entry and exit. + + + Close + + + + + ); +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +const Popup = styled(BaseAlertDialog.Popup)( + ({ theme }) => ` + background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]}; + min-width: 400px; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px; + position: fixed; + top: 50%; + left: 50%; + font-family: IBM Plex Sans; + padding: 16px; + z-index: 2100; + transition-property: opacity, transform; + transition-duration: 150ms; + transition-timing-function: ease-in; + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + + &[data-state='open'] { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + transition-timing-function: ease-out; + } + + &[data-entering] { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + } +`, +); + +const Backdrop = styled(BaseAlertDialog.Backdrop)` + background-color: rgb(0 0 0 / 0.2); + position: fixed; + inset: 0; + z-index: 2000; + backdrop-filter: blur(0); + opacity: 0; + transition-property: opacity, backdrop-filter; + transition-duration: 250ms; + transition-timing-function: ease-in; + + &[data-state='open'] { + backdrop-filter: blur(6px); + opacity: 1; + transition-timing-function: ease-out; + } + + &[data-entering] { + backdrop-filter: blur(0); + opacity: 0; + } +`; + +const Title = styled(BaseAlertDialog.Title)` + font-size: 1.25rem; +`; + +const Trigger = styled(BaseAlertDialog.Trigger)( + ({ theme }) => ` + background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + padding: 8px 16px; + border-radius: 4px; + border: none; + font-family: + "IBM Plex Sans", + sans-serif; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; + } +`, +); + +const Close = styled(BaseAlertDialog.Close)( + ({ theme }) => ` + background-color: transparent; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; + color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + padding: 8px 16px; + border-radius: 4px; + font-family: IBM Plex Sans, sans-serif; + min-width: 80px; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + } +`, +); + +const Controls = styled('div')( + ({ theme }) => ` + display: flex; + flex-direction: row-reverse; + background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; + gap: 8px; + padding: 16px; + margin: 32px -16px -16px; +`, +); diff --git a/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx.preview b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx.preview new file mode 100644 index 0000000000..75f8ff39c9 --- /dev/null +++ b/docs/data/base/components/alert-dialog/AlertDialogWithTransitions.tsx.preview @@ -0,0 +1,13 @@ + + Open + + Animated alert dialog + + This alert dialog uses CSS transitions on entry and exit. + + + Close + + + + \ No newline at end of file diff --git a/docs/data/base/components/alert-dialog/NestedAlertDialogs.js b/docs/data/base/components/alert-dialog/NestedAlertDialogs.js new file mode 100644 index 0000000000..5c1385314c --- /dev/null +++ b/docs/data/base/components/alert-dialog/NestedAlertDialogs.js @@ -0,0 +1,163 @@ +import * as React from 'react'; +import * as BaseAlertDialog from '@base_ui/react/AlertDialog'; +import { styled } from '@mui/system'; + +export default function NestedAlertDialogs() { + return ( + + Open + + + Alert Dialog 1 + + + Open Nested + + + Alert Dialog 2 + + + Open Nested + + + Alert Dialog 3 + + Close + + + + Close + + + + Close + + + + ); +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +const Popup = styled(BaseAlertDialog.Popup)( + ({ theme }) => ` + --transition-duration: 150ms; + background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]}; + min-width: 400px; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px; + position: fixed; + top: 50%; + left: 50%; + font-family: IBM Plex Sans; + padding: 16px; + z-index: 2100; + transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs)))) + translateY(calc(-30px * var(--nested-dialogs))); + visibility: hidden; + opacity: 0.5; + transition: + transform var(--transition-duration) ease-in, + opacity var(--transition-duration) ease-in, + visibility var(--transition-duration) step-end; + + &[data-state='open'] { + @starting-style { + & { + transform: translate(-50%, -35%) scale(0.8) translateY(0); + opacity: 0.5; + } + } + + visibility: visible; + opacity: 1; + transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs)))) + translateY(calc(-30px * var(--nested-dialogs))); + transition: + transform var(--transition-duration) ease-out, + opacity var(--transition-duration) ease-out, + visibility var(--transition-duration) step-start; + } +`, +); + +const Title = styled(BaseAlertDialog.Title)` + font-size: 1.25rem; +`; + +const Trigger = styled(BaseAlertDialog.Trigger)( + ({ theme }) => ` + background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + padding: 8px 16px; + border-radius: 4px; + border: none; + font-family: + "IBM Plex Sans", + sans-serif; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; + } +`, +); + +const Backdrop = styled(BaseAlertDialog.Backdrop)` + background-color: rgb(0 0 0 / 0.2); + position: fixed; + inset: 0; + z-index: 2000; + backdrop-filter: blur(0); + opacity: 0; + transition-property: opacity, backdrop-filter; + transition-duration: 250ms; + transition-timing-function: ease-in; + + &[data-state='open'] { + backdrop-filter: blur(6px); + opacity: 1; + transition-timing-function: ease-out; + } + + &[data-entering] { + backdrop-filter: blur(0); + opacity: 0; + } +`; + +const Close = styled(BaseAlertDialog.Close)( + ({ theme }) => ` + background-color: transparent; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; + color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + padding: 8px 16px; + border-radius: 4px; + font-family: IBM Plex Sans, sans-serif; + min-width: 80px; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + } +`, +); + +const Controls = styled('div')( + ({ theme }) => ` + display: flex; + flex-direction: row-reverse; + background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; + gap: 8px; + padding: 16px; + margin: 32px -16px -16px; +`, +); diff --git a/docs/data/base/components/alert-dialog/NestedAlertDialogs.tsx b/docs/data/base/components/alert-dialog/NestedAlertDialogs.tsx new file mode 100644 index 0000000000..5c1385314c --- /dev/null +++ b/docs/data/base/components/alert-dialog/NestedAlertDialogs.tsx @@ -0,0 +1,163 @@ +import * as React from 'react'; +import * as BaseAlertDialog from '@base_ui/react/AlertDialog'; +import { styled } from '@mui/system'; + +export default function NestedAlertDialogs() { + return ( + + Open + + + Alert Dialog 1 + + + Open Nested + + + Alert Dialog 2 + + + Open Nested + + + Alert Dialog 3 + + Close + + + + Close + + + + Close + + + + ); +} + +const grey = { + 900: '#0f172a', + 800: '#1e293b', + 700: '#334155', + 500: '#64748b', + 300: '#cbd5e1', + 200: '#e2e8f0', + 100: '#f1f5f9', + 50: '#f8fafc', +}; + +const Popup = styled(BaseAlertDialog.Popup)( + ({ theme }) => ` + --transition-duration: 150ms; + background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[100]}; + min-width: 400px; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px; + position: fixed; + top: 50%; + left: 50%; + font-family: IBM Plex Sans; + padding: 16px; + z-index: 2100; + transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs)))) + translateY(calc(-30px * var(--nested-dialogs))); + visibility: hidden; + opacity: 0.5; + transition: + transform var(--transition-duration) ease-in, + opacity var(--transition-duration) ease-in, + visibility var(--transition-duration) step-end; + + &[data-state='open'] { + @starting-style { + & { + transform: translate(-50%, -35%) scale(0.8) translateY(0); + opacity: 0.5; + } + } + + visibility: visible; + opacity: 1; + transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs)))) + translateY(calc(-30px * var(--nested-dialogs))); + transition: + transform var(--transition-duration) ease-out, + opacity var(--transition-duration) ease-out, + visibility var(--transition-duration) step-start; + } +`, +); + +const Title = styled(BaseAlertDialog.Title)` + font-size: 1.25rem; +`; + +const Trigger = styled(BaseAlertDialog.Trigger)( + ({ theme }) => ` + background-color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + color: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; + padding: 8px 16px; + border-radius: 4px; + border: none; + font-family: + "IBM Plex Sans", + sans-serif; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; + } +`, +); + +const Backdrop = styled(BaseAlertDialog.Backdrop)` + background-color: rgb(0 0 0 / 0.2); + position: fixed; + inset: 0; + z-index: 2000; + backdrop-filter: blur(0); + opacity: 0; + transition-property: opacity, backdrop-filter; + transition-duration: 250ms; + transition-timing-function: ease-in; + + &[data-state='open'] { + backdrop-filter: blur(6px); + opacity: 1; + transition-timing-function: ease-out; + } + + &[data-entering] { + backdrop-filter: blur(0); + opacity: 0; + } +`; + +const Close = styled(BaseAlertDialog.Close)( + ({ theme }) => ` + background-color: transparent; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; + color: ${theme.palette.mode === 'dark' ? grey[50] : grey[900]}; + padding: 8px 16px; + border-radius: 4px; + font-family: IBM Plex Sans, sans-serif; + min-width: 80px; + + &:hover { + background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + } +`, +); + +const Controls = styled('div')( + ({ theme }) => ` + display: flex; + flex-direction: row-reverse; + background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; + gap: 8px; + padding: 16px; + margin: 32px -16px -16px; +`, +); diff --git a/docs/data/base/components/alert-dialog/alert-dialog.md b/docs/data/base/components/alert-dialog/alert-dialog.md new file mode 100644 index 0000000000..0c7225a2e1 --- /dev/null +++ b/docs/data/base/components/alert-dialog/alert-dialog.md @@ -0,0 +1,298 @@ +--- +productId: base-ui +title: React Alert Dialog component +components: AlertDialogBackdrop, AlertDialogClose, AlertDialogDescription, AlertDialogPopup, AlertDialogRoot, AlertDialogTitle, AlertDialogTrigger +githubLabel: 'component: alert-dialog' +waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/ +--- + +# Alert Dialog + +

Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks.

+ +{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} + +{{"component": "modules/components/ComponentPageTabs.js"}} + +{{"demo": "AlertDialogIntroduction", "defaultCodeOpen": false, "bg": "gradient"}} + +## Installation + +Base UI components are all available as a single package. + + + +```bash npm +npm install @base_ui/react +``` + +```bash yarn +yarn add @base_ui/react +``` + +```bash pnpm +pnpm add @base_ui/react +``` + + + +Once you have the package installed, import the component. + +```ts +import * as AlertDialog from '@base_ui/react/AlertDialog'; +``` + +## Anatomy + +Alert Dialogs are implemented using a collection of related components: + +- `` is a top-level component that facilitates communication between other components. It does not render to the DOM. +- `` is the alert dialog panel itself. +- `` is the background element appearing when a popup is visible. Use it to indicate that the page is inert. The Backdrop must be a sibling of the Popup component. +- `` is the component (a button by default) that, when clicked, shows the popup. When it's not provided, the visibility of the Alert Dialog can be controlled with its `open` prop (see [Controlled vs. uncontrolled behavior](#controlled-vs-uncontrolled-behavior)). +- `` renders a button that closes the popup. You can attach your own click handlers to it to perform additional actions. +- `` is an header element displaying the title of the alert dialog. It is referenced in the Dialog's ARIA attributes to properly announce it. +- `` is an element describing of the dialog. It is referenced in the Dialog's ARIA attributes to properly announce it. + +```tsx + + + + + + + + + + + +``` + +## Alert dialogs vs. dialogs + +The Alert Dialog is in many ways similar to the [Dialog](/base-ui/react-dialog/) component. +Alert dialogs should be used in cases where the normal user's workflow needs to be interrupted to get a response. +Therefore alert dialogs are always modal and cannot be dismissed any other way than by pressing a button inside them. + +## Controlled vs. uncontrolled behavior + +The simplest way to control the visibility of the alert dialog is to use the `AlertDialog.Trigger` and `AlertDialog.Close` components. + +You can set the initial state with the `defaultOpen` prop. + +```tsx + + Open + + Demo dialog + Close + + +``` + +Doing so ensures that the accessibity attributes are set correctly so that the trigger button is approriately announced by assistive technologies. + +If you need to control the visibility programmatically from the outside, use the `value` prop. +You can still use the `AlertDialog.Trigger` and `AlertDialog.Close` components (though it's not necessary), but you need to make sure to create a handler for the `onOpenChange` event and update the state manually. + +```tsx +const [open, setOpen] = React.useState(false); + +return ( + + Open + + Demo dialog + Close + + +); +``` + +## Nested dialogs + +An alert dialog can open another dialog (or alert dialog). +At times, it may be useful to know how may open sub-dialogs a given alert dialog has. +One example of this could be styling the bottom dialog in a way they appear below the top-most one. + +The number of open child dialogs is present in the `data-nested-dialogs` attribute and in the `--nested-dialogs` CSS variable on the `` component. + +{{"demo": "NestedAlertDialogs.js"}} + +Note that when dialogs are nested, only the bottom-most backdrop is rendered. + +## Animation + +The `` and `` components support transitions on entry and exit. + +CSS animations and transitions are supported out of the box. +If a component has a transition or animation applied to it when it closes, it will be unmounted only after the animation finishes. + +As this detection of exit animations requires an extra render, you may opt out of it by setting the `animated` prop on Popup and Backdrop to `false`. +We also recommend doing so in automated tests, to avoid asynchronous behavior and make testing easier. + +Alternatively, you can use JavaScript-based animations with a library like framer-motion, React Spring, or similar. +With this approach set the `keepMounted` to `true` and let the animation library control mounting and unmounting. + +### CSS transitions + +Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior: + +```jsx +Alert +``` + +```css +.AlertDialogPopup { + transition-property: opacity, transform; + transition-duration: 0.2s; + /* Represents the final styles once exited */ + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); +} + +/* Represents the final styles once entered */ +.AlertDialogPopup[data-state='open'] { + opacity: 1; + transform: translate(-50%, -50%) scale(1); +} + +/* Represents the initial styles when entering */ +.AlertDialogPopup[data-entering] { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); +} +``` + +Styles need to be applied in three states: + +- The exiting styles, placed on the base element class +- The open styles, placed on the base element class with `[data-state="open"]` +- The entering styles, placed on the base element class with `[data-entering]` + +{{"demo": "AlertDialogWithTransitions.js"}} + +In newer browsers, there is a feature called `@starting-style` which allows transitions to occur on open for conditionally-mounted components: + +```css +/* Base UI API - Polyfill */ +.AlertDialogPopup[data-entering] { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); +} + +/* Official Browser API - no Firefox support as of May 2024 */ +@starting-style { + .AlertDialogPopup[data-state='open'] { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + } +} +``` + +### CSS animations + +CSS animations can also be used, requiring only two separate declarations: + +```css +@keyframes scale-in { + from { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + } +} + +@keyframes scale-out { + to { + opacity: 0; + transform: translate(-50%, -35%) scale(0.8); + } +} + +.AlertDialogPopup { + animation: scale-in 0.2s forwards; +} + +.AlertDialogPopup[data-exiting] { + animation: scale-out 0.2s forwards; +} +``` + +### JavaScript animations + +The `keepMounted` prop lets an external library control the mounting, for example `framer-motion`'s `AnimatePresence` component. + +```js +function App() { + const [open, setOpen] = useState(false); + return ( + + Trigger + + {open && ( + + } + > + Alert Dialog + + )} + + + ); +} +``` + +### Animation states + +Four states are available as data attributes to animate the dialog, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the `keepMounted` prop. + +- `[data-state="open"]` - `open` state is `true`. +- `[data-state="closed"]` - `open` state is `false`. Can still be mounted to the DOM if closing. +- `[data-entering]` - the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering. +- `[data-exiting]` - the popup is in the process of being removed from the DOM, but is still mounted. + +## Composing a custom React component + +Use the `render` prop to override the rendered element: + +```jsx +} /> +// or + } /> +``` + +## Accessibility + +Using the `` sets the required accessibility attributes on the trigger button. +If you prefer controlling the open state differently, you need to apply these attributes on your own: + +```tsx +const [open, setOpen] = React.useState(false); + +return ( +
+ + + + + Demo dialog + Close + + +
+); +``` diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.js b/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.js deleted file mode 100644 index 34965307b9..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.js +++ /dev/null @@ -1,464 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { Button } from '@base_ui/react/Button'; -import { Input } from '@base_ui/react/Input'; -import { Popper } from '@base_ui/react/Popper'; -import { useTheme } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClearIcon from '@mui/icons-material/Clear'; -import clsx from 'clsx'; - -const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { - const { - disableClearable = false, - disabled = false, - readOnly = false, - options, - ...other - } = props; - - const { - getRootProps, - getInputProps, - getPopupIndicatorProps, - getClearProps, - getListboxProps, - getOptionProps, - dirty, - id, - popupOpen, - focused, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete({ - ...props, - componentName: 'BaseAutocompleteIntroduction', - }); - - const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - -
- - {hasClearIcon && ( - - )} - - -
- {anchorEl ? ( - -
    - {groupedOptions.map((option, index) => { - const optionProps = getOptionProps({ option, index }); - - return ( -
  • - {option.label} -
  • - ); - })} - - {groupedOptions.length === 0 && ( -
  • No results
  • - )} -
-
- ) : null} - -
- ); -}); - -Autocomplete.propTypes = { - /** - * If `true`, the input can't be cleared. - * @default false - */ - disableClearable: PropTypes.oneOf([false]), - /** - * If `true`, the component is disabled. - * @default false - */ - disabled: PropTypes.bool, - /** - * Array of options. - */ - options: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.string.isRequired, - year: PropTypes.number.isRequired, - }), - ).isRequired, - /** - * If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted. - * @default false - */ - readOnly: PropTypes.bool, -}; - -export default function AutocompleteIntroduction() { - return ; -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} - -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx b/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx deleted file mode 100644 index 2399689329..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx +++ /dev/null @@ -1,441 +0,0 @@ -import * as React from 'react'; -import { - useAutocomplete, - UseAutocompleteProps, -} from '@base_ui/react/useAutocomplete'; -import { Button } from '@base_ui/react/Button'; -import { Input } from '@base_ui/react/Input'; -import { Popper } from '@base_ui/react/Popper'; -import { useTheme } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClearIcon from '@mui/icons-material/Clear'; -import clsx from 'clsx'; - -const Autocomplete = React.forwardRef(function Autocomplete( - props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>, - ref: React.ForwardedRef, -) { - const { - disableClearable = false, - disabled = false, - readOnly = false, - options, - ...other - } = props; - - const { - getRootProps, - getInputProps, - getPopupIndicatorProps, - getClearProps, - getListboxProps, - getOptionProps, - dirty, - id, - popupOpen, - focused, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete({ - ...props, - componentName: 'BaseAutocompleteIntroduction', - }); - - const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - -
- - {hasClearIcon && ( - - )} - -
- {anchorEl ? ( - -
    - {(groupedOptions as typeof top100Films).map((option, index) => { - const optionProps = getOptionProps({ option, index }); - - return ( -
  • - {option.label} -
  • - ); - })} - - {groupedOptions.length === 0 && ( -
  • No results
  • - )} -
-
- ) : null} - -
- ); -}); - -export default function AutocompleteIntroduction() { - return ; -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} - -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx.preview b/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx.preview deleted file mode 100644 index 0991a681b0..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/css/index.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.js b/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.js deleted file mode 100644 index 3a8d047c21..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.js +++ /dev/null @@ -1,435 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { Button } from '@base_ui/react/Button'; -import { Popper } from '@base_ui/react/Popper'; -import { styled } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClearIcon from '@mui/icons-material/Clear'; - -const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { - const { - disableClearable = false, - disabled = false, - readOnly = false, - ...other - } = props; - - const { - getRootProps, - getInputProps, - getPopupIndicatorProps, - getClearProps, - getListboxProps, - getOptionProps, - dirty, - id, - popupOpen, - focused, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete({ - ...props, - componentName: 'BaseAutocompleteIntroduction', - }); - - const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - - - - {hasClearIcon && ( - - - - )} - - - - - - {anchorEl ? ( - - - {groupedOptions.map((option, index) => { - const optionProps = getOptionProps({ option, index }); - - return {option.label}; - })} - - {groupedOptions.length === 0 && ( - No results - )} - - - ) : null} - - ); -}); - -Autocomplete.propTypes = { - /** - * If `true`, the input can't be cleared. - * @default false - */ - disableClearable: PropTypes.oneOf([false]), - /** - * If `true`, the component is disabled. - * @default false - */ - disabled: PropTypes.bool, - /** - * If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted. - * @default false - */ - readOnly: PropTypes.bool, -}; - -export default function AutocompleteIntroduction() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const StyledAutocompleteRoot = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - - &.focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &:hover { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; - border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const StyledInput = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -// ComponentPageTabs has z-index: 1000 -const StyledPopper = styled('div')` - position: relative; - z-index: 1001; - width: 320px; -`; - -const StyledListbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - min-width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 4px 6px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.3)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const StyledOption = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -const StyledPopupIndicator = styled(Button)( - ({ theme }) => ` - outline: 0; - box-shadow: none; - border: 0; - border-radius: 4px; - background-color: transparent; - align-self: center; - padding: 0 2px; - - &:hover { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]}; - cursor: pointer; - } - - & > svg { - transform: translateY(2px); - } - - &.popupOpen > svg { - transform: translateY(2px) rotate(180deg); - } - `, -); - -const StyledClearIndicator = styled(Button)( - ({ theme }) => ` - outline: 0; - box-shadow: none; - border: 0; - border-radius: 4px; - background-color: transparent; - align-self: center; - padding: 0 2px; - - &:hover { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]}; - cursor: pointer; - } - - & > svg { - transform: translateY(2px) scale(0.9); - } - `, -); - -const StyledNoOptions = styled('li')` - list-style: none; - padding: 8px; - cursor: default; -`; - -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx b/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx deleted file mode 100644 index 00d8c19d76..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx +++ /dev/null @@ -1,421 +0,0 @@ -import * as React from 'react'; -import { - useAutocomplete, - UseAutocompleteProps, -} from '@base_ui/react/useAutocomplete'; -import { Button } from '@base_ui/react/Button'; -import { Popper } from '@base_ui/react/Popper'; -import { styled } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClearIcon from '@mui/icons-material/Clear'; - -const Autocomplete = React.forwardRef(function Autocomplete( - props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>, - ref: React.ForwardedRef, -) { - const { - disableClearable = false, - disabled = false, - readOnly = false, - ...other - } = props; - - const { - getRootProps, - getInputProps, - getPopupIndicatorProps, - getClearProps, - getListboxProps, - getOptionProps, - dirty, - id, - popupOpen, - focused, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete({ - ...props, - componentName: 'BaseAutocompleteIntroduction', - }); - - const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - - - - {hasClearIcon && ( - - - - )} - - - - - {anchorEl ? ( - - - {(groupedOptions as typeof top100Films).map((option, index) => { - const optionProps = getOptionProps({ option, index }); - - return {option.label}; - })} - - {groupedOptions.length === 0 && ( - No results - )} - - - ) : null} - - ); -}); - -export default function AutocompleteIntroduction() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const StyledAutocompleteRoot = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - - &.focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &:hover { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; - border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const StyledInput = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -// ComponentPageTabs has z-index: 1000 -const StyledPopper = styled('div')` - position: relative; - z-index: 1001; - width: 320px; -`; - -const StyledListbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - min-width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 4px 6px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.3)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const StyledOption = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -const StyledPopupIndicator = styled(Button)( - ({ theme }) => ` - outline: 0; - box-shadow: none; - border: 0; - border-radius: 4px; - background-color: transparent; - align-self: center; - padding: 0 2px; - - &:hover { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]}; - cursor: pointer; - } - - & > svg { - transform: translateY(2px); - } - - &.popupOpen > svg { - transform: translateY(2px) rotate(180deg); - } - `, -); - -const StyledClearIndicator = styled(Button)( - ({ theme }) => ` - outline: 0; - box-shadow: none; - border: 0; - border-radius: 4px; - background-color: transparent; - align-self: center; - padding: 0 2px; - - &:hover { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : blue[100]}; - cursor: pointer; - } - - & > svg { - transform: translateY(2px) scale(0.9); - } - `, -); - -const StyledNoOptions = styled('li')` - list-style: none; - padding: 8px; - cursor: default; -`; - -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx.preview b/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx.preview deleted file mode 100644 index 0991a681b0..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/system/index.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.js b/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.js deleted file mode 100644 index 1aabb6cffa..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.js +++ /dev/null @@ -1,294 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { Button } from '@base_ui/react/Button'; -import { Popper } from '@base_ui/react/Popper'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClearIcon from '@mui/icons-material/Clear'; -import clsx from 'clsx'; - -const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { - const { - disableClearable = false, - disabled = false, - readOnly = false, - options, - isOptionEqualToValue, - ...other - } = props; - - const { - getRootProps, - getInputProps, - getPopupIndicatorProps, - getClearProps, - getListboxProps, - getOptionProps, - dirty, - id, - popupOpen, - focused, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete({ - ...props, - componentName: 'BaseAutocompleteIntroduction', - }); - - const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - -
- - {hasClearIcon && ( - - )} - - -
- {anchorEl && ( - -
    - {groupedOptions.map((option, index) => { - const optionProps = getOptionProps({ option, index }); - - return ( -
  • - {option.label} -
  • - ); - })} - - {groupedOptions.length === 0 && ( -
  • No results
  • - )} -
-
- )} -
- ); -}); - -Autocomplete.propTypes = { - /** - * If `true`, the input can't be cleared. - * @default false - */ - disableClearable: PropTypes.oneOf([false]), - /** - * If `true`, the component is disabled. - * @default false - */ - disabled: PropTypes.bool, - /** - * Used to determine if the option represents the given value. - * Uses strict equality by default. - * ⚠️ Both arguments need to be handled, an option can only match with one value. - * - * @param {Value} option The option to test. - * @param {Value} value The value to test against. - * @returns {boolean} - */ - isOptionEqualToValue: PropTypes.func, - /** - * Array of options. - */ - options: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.string.isRequired, - year: PropTypes.number.isRequired, - }), - ).isRequired, - /** - * If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted. - * @default false - */ - readOnly: PropTypes.bool, -}; - -export default function AutocompleteIntroduction() { - return ( - option.label === value.label} - /> - ); -} - -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx b/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx deleted file mode 100644 index cf1dfe2391..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import * as React from 'react'; -import { - useAutocomplete, - UseAutocompleteProps, -} from '@base_ui/react/useAutocomplete'; -import { Button } from '@base_ui/react/Button'; -import { Popper } from '@base_ui/react/Popper'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ClearIcon from '@mui/icons-material/Clear'; -import clsx from 'clsx'; - -const Autocomplete = React.forwardRef(function Autocomplete( - props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>, - ref: React.ForwardedRef, -) { - const { - disableClearable = false, - disabled = false, - readOnly = false, - options, - isOptionEqualToValue, - ...other - } = props; - - const { - getRootProps, - getInputProps, - getPopupIndicatorProps, - getClearProps, - getListboxProps, - getOptionProps, - dirty, - id, - popupOpen, - focused, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete({ - ...props, - componentName: 'BaseAutocompleteIntroduction', - }); - - const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - -
- - {hasClearIcon && ( - - )} - -
- {anchorEl && ( - -
    - {(groupedOptions as typeof top100Films).map((option, index) => { - const optionProps = getOptionProps({ option, index }); - - return ( -
  • - {option.label} -
  • - ); - })} - - {groupedOptions.length === 0 && ( -
  • No results
  • - )} -
-
- )} -
- ); -}); - -export default function AutocompleteIntroduction() { - return ( - option.label === value.label} - /> - ); -} - -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx.preview deleted file mode 100644 index 78a5cab783..0000000000 --- a/docs/data/base/components/autocomplete/AutocompleteIntroduction/tailwind/index.tsx.preview +++ /dev/null @@ -1,4 +0,0 @@ - option.label === value.label} -/> \ No newline at end of file diff --git a/docs/data/base/components/autocomplete/ControlledStates.js b/docs/data/base/components/autocomplete/ControlledStates.js deleted file mode 100644 index 039291df3e..0000000000 --- a/docs/data/base/components/autocomplete/ControlledStates.js +++ /dev/null @@ -1,209 +0,0 @@ -import * as React from 'react'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { styled } from '@mui/system'; - -const options = ['Firefox', 'Google Chrome', 'Microsoft Edge', 'Safari', 'Opera']; - -export default function ControlledStates() { - const [value, setValue] = React.useState(options[0]); - const [inputValue, setInputValue] = React.useState(''); - - const { - getRootProps, - getInputProps, - getListboxProps, - getOptionProps, - groupedOptions, - focused, - } = useAutocomplete({ - id: 'controlled-state-demo', - options, - value, - onChange: (event, newValue) => setValue(newValue), - inputValue, - onInputChange: (event, newInputValue) => setInputValue(newInputValue), - }); - - return ( - -
-        value: {value ?? ' '}
-      
-
-        inputValue: {inputValue ?? ' '}
-      
- - - - - {groupedOptions.length > 0 && ( - - {groupedOptions.map((option, index) => ( - - ))} - - )} - -
- ); -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const AutocompleteWrapper = styled('div')` - position: relative; -`; - -const AutocompleteRoot = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - - &.Mui-focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const Input = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -const Listbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - max-width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - position: absolute; - left: 0; - right: 0; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 4px 6px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const Option = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -const Layout = styled('div')` - display: flex; - flex-flow: column nowrap; - gap: 4px; -`; - -const Pre = styled('pre')(({ theme }) => ({ - margin: '0.5rem 0', - fontSize: '0.75rem', - '& code': { - backgroundColor: theme.palette.mode === 'light' ? grey[100] : grey[900], - border: '1px solid', - borderColor: theme.palette.mode === 'light' ? grey[300] : grey[700], - color: theme.palette.mode === 'light' ? '#000' : '#fff', - padding: '0.125rem 0.25rem', - borderRadius: 3, - }, -})); diff --git a/docs/data/base/components/autocomplete/ControlledStates.tsx b/docs/data/base/components/autocomplete/ControlledStates.tsx deleted file mode 100644 index b5044424e0..0000000000 --- a/docs/data/base/components/autocomplete/ControlledStates.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import * as React from 'react'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { styled } from '@mui/system'; - -const options = ['Firefox', 'Google Chrome', 'Microsoft Edge', 'Safari', 'Opera']; - -export default function ControlledStates() { - const [value, setValue] = React.useState(options[0]); - const [inputValue, setInputValue] = React.useState(''); - - const { - getRootProps, - getInputProps, - getListboxProps, - getOptionProps, - groupedOptions, - focused, - } = useAutocomplete({ - id: 'controlled-state-demo', - options, - value, - onChange: (event, newValue) => setValue(newValue), - inputValue, - onInputChange: (event, newInputValue) => setInputValue(newInputValue), - }); - - return ( - -
-        value: {value ?? ' '}
-      
-
-        inputValue: {inputValue ?? ' '}
-      
- - - - - {groupedOptions.length > 0 && ( - - {(groupedOptions as string[]).map((option, index) => ( - - ))} - - )} - -
- ); -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const AutocompleteWrapper = styled('div')` - position: relative; -`; - -const AutocompleteRoot = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - - &.Mui-focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const Input = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -const Listbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - max-width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - position: absolute; - left: 0; - right: 0; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 4px 6px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const Option = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -const Layout = styled('div')` - display: flex; - flex-flow: column nowrap; - gap: 4px; -`; - -const Pre = styled('pre')(({ theme }) => ({ - margin: '0.5rem 0', - fontSize: '0.75rem', - '& code': { - backgroundColor: theme.palette.mode === 'light' ? grey[100] : grey[900], - border: '1px solid', - borderColor: theme.palette.mode === 'light' ? grey[300] : grey[700], - color: theme.palette.mode === 'light' ? '#000' : '#fff', - padding: '0.125rem 0.25rem', - borderRadius: 3, - }, -})); diff --git a/docs/data/base/components/autocomplete/UseAutocomplete.js b/docs/data/base/components/autocomplete/UseAutocomplete.js deleted file mode 100644 index 7ca9074295..0000000000 --- a/docs/data/base/components/autocomplete/UseAutocomplete.js +++ /dev/null @@ -1,307 +0,0 @@ -import * as React from 'react'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { styled } from '@mui/system'; - -export default function UseAutocomplete() { - const [value, setValue] = React.useState(null); - - const { - getRootProps, - getInputLabelProps, - getInputProps, - getListboxProps, - getOptionProps, - groupedOptions, - focused, - } = useAutocomplete({ - id: 'use-autocomplete-demo', - options: top100Films, - getOptionLabel: (option) => option.label, - value, - onChange: (event, newValue) => setValue(newValue), - }); - - return ( -
- - - - - {groupedOptions.length > 0 && ( - - {groupedOptions.map((option, index) => ( - - ))} - - )} -
- ); -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Label = styled('label')` - display: block; - font-family: sans-serif; - font-size: 14px; - font-weight: 500; - margin-bottom: 4px; -`; - -const Root = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - - &.Mui-focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const Input = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -const Listbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - position: absolute; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 2px 3px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const Option = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/UseAutocomplete.tsx b/docs/data/base/components/autocomplete/UseAutocomplete.tsx deleted file mode 100644 index d0b212c1cf..0000000000 --- a/docs/data/base/components/autocomplete/UseAutocomplete.tsx +++ /dev/null @@ -1,309 +0,0 @@ -import * as React from 'react'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { styled } from '@mui/system'; - -export default function UseAutocomplete() { - const [value, setValue] = React.useState<(typeof top100Films)[number] | null>( - null, - ); - - const { - getRootProps, - getInputLabelProps, - getInputProps, - getListboxProps, - getOptionProps, - groupedOptions, - focused, - } = useAutocomplete({ - id: 'use-autocomplete-demo', - options: top100Films, - getOptionLabel: (option) => option.label, - value, - onChange: (event, newValue) => setValue(newValue), - }); - - return ( -
- - - - - {groupedOptions.length > 0 && ( - - {(groupedOptions as typeof top100Films).map((option, index) => ( - - ))} - - )} -
- ); -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Label = styled('label')` - display: block; - font-family: sans-serif; - font-size: 14px; - font-weight: 500; - margin-bottom: 4px; -`; - -const Root = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - - &.Mui-focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const Input = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -const Listbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - position: absolute; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 2px 3px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const Option = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/UseAutocomplete.tsx.preview b/docs/data/base/components/autocomplete/UseAutocomplete.tsx.preview deleted file mode 100644 index 7c000513c9..0000000000 --- a/docs/data/base/components/autocomplete/UseAutocomplete.tsx.preview +++ /dev/null @@ -1,11 +0,0 @@ - - - - -{groupedOptions.length > 0 && ( - - {(groupedOptions as typeof top100Films).map((option, index) => ( - - ))} - -)} \ No newline at end of file diff --git a/docs/data/base/components/autocomplete/UseAutocompletePopper.js b/docs/data/base/components/autocomplete/UseAutocompletePopper.js deleted file mode 100644 index 0c5385b8bd..0000000000 --- a/docs/data/base/components/autocomplete/UseAutocompletePopper.js +++ /dev/null @@ -1,337 +0,0 @@ -import * as React from 'react'; -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { Popper } from '@base_ui/react/Popper'; -import { styled } from '@mui/system'; -import useForkRef from '@mui/utils/useForkRef'; - -const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { - const { - getRootProps, - getInputProps, - getListboxProps, - getOptionProps, - groupedOptions, - focused, - popupOpen, - anchorEl, - setAnchorEl, - } = useAutocomplete(props); - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - - - - - {anchorEl && ( - - - {groupedOptions.length > 0 ? ( - groupedOptions.map((option, index) => ( - - )) - ) : ( - No results - )} - - - )} - - ); -}); - -export default function UseAutocompletePopper() { - const [value, setValue] = React.useState(null); - - const handleChange = (event, newValue) => setValue(newValue); - - return ( - - ); -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Root = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - margin: 1.5rem 0; - - &.Mui-focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const StyledInput = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -// ComponentPageTabs has z-index: 1000 -const StyledPopper = styled('div')` - position: relative; - z-index: 1001; - width: 320px; -`; - -const Listbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - min-width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 4px 6px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const Option = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -const NoOptions = styled('li')` - list-style: none; - padding: 8px; - cursor: default; -`; - -// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx b/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx deleted file mode 100644 index 7b9333884a..0000000000 --- a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import * as React from 'react'; -import { - useAutocomplete, - UseAutocompleteProps, -} from '@base_ui/react/useAutocomplete'; -import { Popper } from '@base_ui/react/Popper'; -import { styled } from '@mui/system'; -import useForkRef from '@mui/utils/useForkRef'; - -const Autocomplete = React.forwardRef(function Autocomplete( - props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>, - ref: React.ForwardedRef, -) { - const { - getRootProps, - getInputProps, - getListboxProps, - getOptionProps, - groupedOptions, - focused, - popupOpen, - anchorEl, - setAnchorEl, - } = useAutocomplete(props); - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - - - - - {anchorEl && ( - - - {groupedOptions.length > 0 ? ( - (groupedOptions as typeof top100Films).map((option, index) => ( - - )) - ) : ( - No results - )} - - - )} - - ); -}); - -export default function UseAutocompletePopper() { - const [value, setValue] = React.useState<(typeof top100Films)[number] | null>( - null, - ); - - const handleChange = ( - event: React.SyntheticEvent, - newValue: (typeof top100Films)[number] | null, - ) => setValue(newValue); - - return ( - - ); -} - -const blue = { - 100: '#DAECFF', - 200: '#99CCF3', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Root = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - gap: 5px; - padding-right: 5px; - overflow: hidden; - width: 320px; - margin: 1.5rem 0; - - &.Mui-focused { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - &:focus-visible { - outline: 0; - } -`, -); - -const StyledInput = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; - flex: 1 0 auto; -`, -); - -// ComponentPageTabs has z-index: 1000 -const StyledPopper = styled('div')` - position: relative; - z-index: 1001; - width: 320px; -`; - -const Listbox = styled('ul')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - box-sizing: border-box; - padding: 6px; - margin: 12px 0; - min-width: 320px; - border-radius: 12px; - overflow: auto; - outline: 0px; - max-height: 300px; - z-index: 1; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - box-shadow: 0px 4px 6px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.50)' : 'rgba(0,0,0, 0.05)' - }; - `, -); - -const Option = styled('li')( - ({ theme }) => ` - list-style: none; - padding: 8px; - border-radius: 8px; - cursor: default; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - cursor: pointer; - } - - &[aria-selected=true] { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - - &.Mui-focused, - &.Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - } - - &.Mui-focusVisible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - &[aria-selected=true].Mui-focused, - &[aria-selected=true].Mui-focusVisible { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } - `, -); - -const NoOptions = styled('li')` - list-style: none; - padding: 8px; - cursor: default; -`; - -// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top -const top100Films = [ - { label: 'The Shawshank Redemption', year: 1994 }, - { label: 'The Godfather', year: 1972 }, - { label: 'The Godfather: Part II', year: 1974 }, - { label: 'The Dark Knight', year: 2008 }, - { label: '12 Angry Men', year: 1957 }, - { label: "Schindler's List", year: 1993 }, - { label: 'Pulp Fiction', year: 1994 }, - { - label: 'The Lord of the Rings: The Return of the King', - year: 2003, - }, - { label: 'The Good, the Bad and the Ugly', year: 1966 }, - { label: 'Fight Club', year: 1999 }, - { - label: 'The Lord of the Rings: The Fellowship of the Ring', - year: 2001, - }, - { - label: 'Star Wars: Episode V - The Empire Strikes Back', - year: 1980, - }, - { label: 'Forrest Gump', year: 1994 }, - { label: 'Inception', year: 2010 }, - { - label: 'The Lord of the Rings: The Two Towers', - year: 2002, - }, - { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, - { label: 'Goodfellas', year: 1990 }, - { label: 'The Matrix', year: 1999 }, - { label: 'Seven Samurai', year: 1954 }, - { - label: 'Star Wars: Episode IV - A New Hope', - year: 1977, - }, - { label: 'City of God', year: 2002 }, - { label: 'Se7en', year: 1995 }, - { label: 'The Silence of the Lambs', year: 1991 }, - { label: "It's a Wonderful Life", year: 1946 }, - { label: 'Life Is Beautiful', year: 1997 }, - { label: 'The Usual Suspects', year: 1995 }, - { label: 'Léon: The Professional', year: 1994 }, - { label: 'Spirited Away', year: 2001 }, - { label: 'Saving Private Ryan', year: 1998 }, - { label: 'Once Upon a Time in the West', year: 1968 }, - { label: 'American History X', year: 1998 }, - { label: 'Interstellar', year: 2014 }, - { label: 'Casablanca', year: 1942 }, - { label: 'City Lights', year: 1931 }, - { label: 'Psycho', year: 1960 }, - { label: 'The Green Mile', year: 1999 }, - { label: 'The Intouchables', year: 2011 }, - { label: 'Modern Times', year: 1936 }, - { label: 'Raiders of the Lost Ark', year: 1981 }, - { label: 'Rear Window', year: 1954 }, - { label: 'The Pianist', year: 2002 }, - { label: 'The Departed', year: 2006 }, - { label: 'Terminator 2: Judgment Day', year: 1991 }, - { label: 'Back to the Future', year: 1985 }, - { label: 'Whiplash', year: 2014 }, - { label: 'Gladiator', year: 2000 }, - { label: 'Memento', year: 2000 }, - { label: 'The Prestige', year: 2006 }, - { label: 'The Lion King', year: 1994 }, - { label: 'Apocalypse Now', year: 1979 }, - { label: 'Alien', year: 1979 }, - { label: 'Sunset Boulevard', year: 1950 }, - { - label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', - year: 1964, - }, - { label: 'The Great Dictator', year: 1940 }, - { label: 'Cinema Paradiso', year: 1988 }, - { label: 'The Lives of Others', year: 2006 }, - { label: 'Grave of the Fireflies', year: 1988 }, - { label: 'Paths of Glory', year: 1957 }, - { label: 'Django Unchained', year: 2012 }, - { label: 'The Shining', year: 1980 }, - { label: 'WALL·E', year: 2008 }, - { label: 'American Beauty', year: 1999 }, - { label: 'The Dark Knight Rises', year: 2012 }, - { label: 'Princess Mononoke', year: 1997 }, - { label: 'Aliens', year: 1986 }, - { label: 'Oldboy', year: 2003 }, - { label: 'Once Upon a Time in America', year: 1984 }, - { label: 'Witness for the Prosecution', year: 1957 }, - { label: 'Das Boot', year: 1981 }, - { label: 'Citizen Kane', year: 1941 }, - { label: 'North by Northwest', year: 1959 }, - { label: 'Vertigo', year: 1958 }, - { - label: 'Star Wars: Episode VI - Return of the Jedi', - year: 1983, - }, - { label: 'Reservoir Dogs', year: 1992 }, - { label: 'Braveheart', year: 1995 }, - { label: 'M', year: 1931 }, - { label: 'Requiem for a Dream', year: 2000 }, - { label: 'Amélie', year: 2001 }, - { label: 'A Clockwork Orange', year: 1971 }, - { label: 'Like Stars on Earth', year: 2007 }, - { label: 'Taxi Driver', year: 1976 }, - { label: 'Lawrence of Arabia', year: 1962 }, - { label: 'Double Indemnity', year: 1944 }, - { - label: 'Eternal Sunshine of the Spotless Mind', - year: 2004, - }, - { label: 'Amadeus', year: 1984 }, - { label: 'To Kill a Mockingbird', year: 1962 }, - { label: 'Toy Story 3', year: 2010 }, - { label: 'Logan', year: 2017 }, - { label: 'Full Metal Jacket', year: 1987 }, - { label: 'Dangal', year: 2016 }, - { label: 'The Sting', year: 1973 }, - { label: '2001: A Space Odyssey', year: 1968 }, - { label: "Singin' in the Rain", year: 1952 }, - { label: 'Toy Story', year: 1995 }, - { label: 'Bicycle Thieves', year: 1948 }, - { label: 'The Kid', year: 1921 }, - { label: 'Inglourious Basterds', year: 2009 }, - { label: 'Snatch', year: 2000 }, - { label: '3 Idiots', year: 2009 }, - { label: 'Monty Python and the Holy Grail', year: 1975 }, -]; diff --git a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx.preview b/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx.preview deleted file mode 100644 index 1d9614ad6d..0000000000 --- a/docs/data/base/components/autocomplete/UseAutocompletePopper.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/autocomplete/autocomplete.md b/docs/data/base/components/autocomplete/autocomplete.md index 0bce06e3a9..bade0ca0ed 100644 --- a/docs/data/base/components/autocomplete/autocomplete.md +++ b/docs/data/base/components/autocomplete/autocomplete.md @@ -13,139 +13,3 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/ {{"component": "@mui/docs/ComponentLinkHeader", "design": false}} {{"component": "modules/components/ComponentPageTabs.js"}} - -## Introduction - -An autocomplete component is an enhanced text input that shows a list of suggested options as users type and lets them select an option from the list. - -Base UI provides the `useAutocomplete` hook for building a custom Autocomplete. -It implements the WAI-ARIA Combobox pattern and is typically used to assist users in completing form inputs or search queries faster. - -{{"demo": "AutocompleteIntroduction", "defaultCodeOpen": false, "bg": "gradient"}} - -:::warning -Material UI and Joy UI have Autocomplete components that are built using the `useAutocomplete` hook, and they include many features not yet described here. - -To learn more about implementing a custom Autocomplete, you can explore the [`useAutocomplete` API docs](/base-ui/react-autocomplete/hooks-api/#use-autocomplete), or reference the Material UI and Joy UI implementations: - -- [Material UI Autocomplete](https://mui.com/material-ui/react-autocomplete/) -- [Joy UI Autocomplete](https://mui.com/joy-ui/react-autocomplete/) - -::: - -## Hook - -```jsx -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -``` - -The `useAutocomplete` hook requires a list of `options` to be displayed when the textbox receives focus. -The value must be chosen from a predefined set of values. - -The following demo shows how to create a simple combobox, apply styles, and write the selected value to a state variable using the `onChange` prop: - -{{"demo": "UseAutocomplete.js"}} - -## Customization - -### Rendering options - -By default, the `options` prop accepts an array of `string`s or `{ label: string }`: - -```js -const options = [ - { label: 'The Godfather', id: 1 }, - { label: 'Pulp Fiction', id: 2 }, -]; -// or -const options = ['The Godfather', 'Pulp Fiction']; -``` - -If you need to use a different structure for options, you must provide a function to the `getOptionLabel` prop that resolves each option to a unique value. - -```js -const options = [ - { issuer: 'Bank of America', brand: 'Visa', last4: '1234' }, - { issuer: 'Bank of America', brand: 'MasterCard', last4: '5678' }, - { issuer: 'Barclays', brand: 'Visa', last4: '4698' }, - // ... -]; - -const { - getRootProps, - // etc -} = useAutocomplete({ - getOptionLabel: (option) => option.last4, -}); -``` - -### Controlled states - -The `useAutocomplete` hook has two states that can be controlled: - -1. the "value" state with the `value`/`onChange` props combination. This state represents the value selected by the user, for instance when pressing Enter. -2. the "input value" state with the `inputValue`/`onInputChange` props combination. This state represents the value displayed in the textbox. - -These two states are isolated and should be controlled independently. - -:::info - -- A component is **controlled** when it's managed by its parent using props. -- A component is **uncontrolled** when it's managed by its own local state. - -Learn more about controlled and uncontrolled components in the [React documentation](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components). -::: - -{{"demo": "ControlledStates.js"}} - -### Using a portal - -React Portals can be used to render the listbox outside of the DOM hierarchy, making it easier to allow it to "float" above adjacent elements. - -Base UI provides a [Popper](/base-ui/react-popper/) component built around React's `createPortal()` for exactly this purpose, and additionally helps you manage keyboard focus as it moves in and out of the portal. - -To render the listbox in Base UI's Popper, the `ref`s must be merged as follows: - -```jsx -import { useAutocomplete } from '@base_ui/react/useAutocomplete'; -import { Popper } from '@base_ui/react/Popper'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; - -export default function App(props) { - const { - getRootProps, - getInputProps, - getListboxProps, - getOptionProps, - popupOpen, - anchorEl, - setAnchorEl, - groupedOptions, - } = useAutocomplete(props); - - const rootRef = useForkRef(ref, setAnchorEl); - - return ( - -
- -
- {anchorEl && ( - - {groupedOptions.length > 0 && ( -
    - {groupedOptions.map((option, index) => ( -
  • {option.label}
  • - ))} -
- )} -
- )} -
- ); -} -``` - -Here's a complete demo that renders the listbox inside a Popper: - -{{"demo": "UseAutocompletePopper.js", "defaultCodeOpen": false}} diff --git a/docs/data/base/components/badge/AccessibleBadges.js b/docs/data/base/components/badge/AccessibleBadges.js deleted file mode 100644 index caedd2ef1e..0000000000 --- a/docs/data/base/components/badge/AccessibleBadges.js +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -import MailIcon from '@mui/icons-material/Mail'; - -function notificationsLabel(count) { - if (count === 0) { - return 'no notifications'; - } - if (count > 99) { - return 'more than 99 notifications'; - } - return `${count} notifications`; -} - -export default function AccessibleBadges() { - return ( -
- - - -
- ); -} -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/AccessibleBadges.tsx b/docs/data/base/components/badge/AccessibleBadges.tsx deleted file mode 100644 index caf8866453..0000000000 --- a/docs/data/base/components/badge/AccessibleBadges.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -import MailIcon from '@mui/icons-material/Mail'; - -function notificationsLabel(count: number) { - if (count === 0) { - return 'no notifications'; - } - if (count > 99) { - return 'more than 99 notifications'; - } - return `${count} notifications`; -} - -export default function AccessibleBadges() { - return ( -
- - - -
- ); -} -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/AccessibleBadges.tsx.preview b/docs/data/base/components/badge/AccessibleBadges.tsx.preview deleted file mode 100644 index 9fde77197a..0000000000 --- a/docs/data/base/components/badge/AccessibleBadges.tsx.preview +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/BadgeMax.js b/docs/data/base/components/badge/BadgeMax.js deleted file mode 100644 index e883b2437a..0000000000 --- a/docs/data/base/components/badge/BadgeMax.js +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react'; -import Stack from '@mui/material/Stack'; -import { styled } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -import MailIcon from '@mui/icons-material/Mail'; - -export default function BadgeMax() { - return ( - - - - - - - - - - - - ); -} -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/BadgeMax.tsx b/docs/data/base/components/badge/BadgeMax.tsx deleted file mode 100644 index e883b2437a..0000000000 --- a/docs/data/base/components/badge/BadgeMax.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react'; -import Stack from '@mui/material/Stack'; -import { styled } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -import MailIcon from '@mui/icons-material/Mail'; - -export default function BadgeMax() { - return ( - - - - - - - - - - - - ); -} -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/BadgeMax.tsx.preview b/docs/data/base/components/badge/BadgeMax.tsx.preview deleted file mode 100644 index a95263effa..0000000000 --- a/docs/data/base/components/badge/BadgeMax.tsx.preview +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/BadgeVisibility.js b/docs/data/base/components/badge/BadgeVisibility.js deleted file mode 100644 index 2aca401dd3..0000000000 --- a/docs/data/base/components/badge/BadgeVisibility.js +++ /dev/null @@ -1,223 +0,0 @@ -import * as React from 'react'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -// Auxiliary demo components -import { styled, Stack } from '@mui/system'; -import { Button, buttonClasses } from '@base_ui/react/Button'; -import * as BaseSwitch from '@base_ui/react/Switch'; -import Divider from '@mui/material/Divider'; -// Icons -import AddIcon from '@mui/icons-material/Add'; -import RemoveIcon from '@mui/icons-material/Remove'; -import MailIcon from '@mui/icons-material/Mail'; - -const blue = { - 200: '#99CCF3', - 500: '#007FFF', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - position: relative; - display: flex; - align-self: center; - margin: 0; - padding: 0; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 14px; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - - & .${badgeClasses.invisible} { - opacity: 0; - pointer-events: none; - } - `, -); - -const StyledButton = styled(Button)( - ({ theme }) => ` - cursor: pointer; - padding: 4px 8px; - display: flex; - align-items: center; - border-radius: 8px; - transition: all 150ms ease; - background-color: transparent; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; - - &:hover { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; - } - - &.${buttonClasses.active} { - background: ${theme.palette.mode === 'dark' ? grey[900] : grey[100]}; - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 3px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 4px rgba(0, 127, 255, 0.5); - outline: none; - } - `, -); - -const Switch = styled(BaseSwitch.Root)( - ({ theme }) => ` - width: 32px; - height: 20px; - padding: 0; - box-sizing: border-box; - background: ${theme.palette.mode === 'dark' ? grey[600] : grey[400]}; - border-radius: 16px; - border: none; - display: inline-flex; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 120ms; - box-shadow: inset 0px 1px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.05)' - }; - - &[data-disabled] { - opacity: 0.4; - cursor: not-allowed; - } - - &:hover:not([data-disabled]) { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; - } - - &:focus-visible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &[data-state="checked"] { - border: none; - background: ${blue[500]}; - } - - &[data-state="checked"]:not([data-disabled]):hover { - background: ${blue[700]}; - } - `, -); - -const Thumb = styled(BaseSwitch.Thumb)( - ({ theme }) => ` - box-sizing: border-box; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; - display: block; - width: 14px; - height: 14px; - left: 3px; - top: 3px; - border-radius: 16px; - background-color: #FFF; - position: relative; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 120ms; - box-shadow: 0px 1px 2px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.1)' - }; - - &[data-state="checked"] { - left: 15px; - background-color: #fff; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); - } -`, -); - -const StyledLabel = styled('label')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - `, -); - -export default function BadgeVisibility() { - const [count, setCount] = React.useState(1); - const [invisible, setInvisible] = React.useState(false); - - const handleBadgeVisibility = () => { - setInvisible(!invisible); - }; - - return ( - - - - - - - { - setCount(Math.max(count - 1, 0)); - }} - > - - - { - setCount(count + 1); - }} - > - - - - - Show badge - - - - - - - ); -} diff --git a/docs/data/base/components/badge/BadgeVisibility.tsx b/docs/data/base/components/badge/BadgeVisibility.tsx deleted file mode 100644 index 2aca401dd3..0000000000 --- a/docs/data/base/components/badge/BadgeVisibility.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import * as React from 'react'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -// Auxiliary demo components -import { styled, Stack } from '@mui/system'; -import { Button, buttonClasses } from '@base_ui/react/Button'; -import * as BaseSwitch from '@base_ui/react/Switch'; -import Divider from '@mui/material/Divider'; -// Icons -import AddIcon from '@mui/icons-material/Add'; -import RemoveIcon from '@mui/icons-material/Remove'; -import MailIcon from '@mui/icons-material/Mail'; - -const blue = { - 200: '#99CCF3', - 500: '#007FFF', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - position: relative; - display: flex; - align-self: center; - margin: 0; - padding: 0; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 14px; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - - & .${badgeClasses.invisible} { - opacity: 0; - pointer-events: none; - } - `, -); - -const StyledButton = styled(Button)( - ({ theme }) => ` - cursor: pointer; - padding: 4px 8px; - display: flex; - align-items: center; - border-radius: 8px; - transition: all 150ms ease; - background-color: transparent; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; - - &:hover { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; - } - - &.${buttonClasses.active} { - background: ${theme.palette.mode === 'dark' ? grey[900] : grey[100]}; - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 3px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 4px rgba(0, 127, 255, 0.5); - outline: none; - } - `, -); - -const Switch = styled(BaseSwitch.Root)( - ({ theme }) => ` - width: 32px; - height: 20px; - padding: 0; - box-sizing: border-box; - background: ${theme.palette.mode === 'dark' ? grey[600] : grey[400]}; - border-radius: 16px; - border: none; - display: inline-flex; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 120ms; - box-shadow: inset 0px 1px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.05)' - }; - - &[data-disabled] { - opacity: 0.4; - cursor: not-allowed; - } - - &:hover:not([data-disabled]) { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; - border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; - } - - &:focus-visible { - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - &[data-state="checked"] { - border: none; - background: ${blue[500]}; - } - - &[data-state="checked"]:not([data-disabled]):hover { - background: ${blue[700]}; - } - `, -); - -const Thumb = styled(BaseSwitch.Thumb)( - ({ theme }) => ` - box-sizing: border-box; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; - display: block; - width: 14px; - height: 14px; - left: 3px; - top: 3px; - border-radius: 16px; - background-color: #FFF; - position: relative; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 120ms; - box-shadow: 0px 1px 2px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.1)' - }; - - &[data-state="checked"] { - left: 15px; - background-color: #fff; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); - } -`, -); - -const StyledLabel = styled('label')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - `, -); - -export default function BadgeVisibility() { - const [count, setCount] = React.useState(1); - const [invisible, setInvisible] = React.useState(false); - - const handleBadgeVisibility = () => { - setInvisible(!invisible); - }; - - return ( - - - - - - - { - setCount(Math.max(count - 1, 0)); - }} - > - - - { - setCount(count + 1); - }} - > - - - - - Show badge - - - - - - - ); -} diff --git a/docs/data/base/components/badge/ShowZeroBadge.js b/docs/data/base/components/badge/ShowZeroBadge.js deleted file mode 100644 index 88616ba976..0000000000 --- a/docs/data/base/components/badge/ShowZeroBadge.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import Stack from '@mui/material/Stack'; -import { styled } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -import MailIcon from '@mui/icons-material/Mail'; - -export default function ShowZeroBadge() { - return ( - - - - - - - - - ); -} - -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - - & .${badgeClasses.invisible} { - display: none; - } - `, -); diff --git a/docs/data/base/components/badge/ShowZeroBadge.tsx b/docs/data/base/components/badge/ShowZeroBadge.tsx deleted file mode 100644 index 88616ba976..0000000000 --- a/docs/data/base/components/badge/ShowZeroBadge.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import Stack from '@mui/material/Stack'; -import { styled } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; -import MailIcon from '@mui/icons-material/Mail'; - -export default function ShowZeroBadge() { - return ( - - - - - - - - - ); -} - -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 6x ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - - & .${badgeClasses.invisible} { - display: none; - } - `, -); diff --git a/docs/data/base/components/badge/ShowZeroBadge.tsx.preview b/docs/data/base/components/badge/ShowZeroBadge.tsx.preview deleted file mode 100644 index 18283159fe..0000000000 --- a/docs/data/base/components/badge/ShowZeroBadge.tsx.preview +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/UnstyledBadge/css/index.js b/docs/data/base/components/badge/UnstyledBadge/css/index.js deleted file mode 100644 index e9f5d7f84f..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/css/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react'; -import { Badge } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -export default function UnstyledBadge() { - return ( - - - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - return ( - - ); -} diff --git a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx b/docs/data/base/components/badge/UnstyledBadge/css/index.tsx deleted file mode 100644 index e9f5d7f84f..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react'; -import { Badge } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -export default function UnstyledBadge() { - return ( - - - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - return ( - - ); -} diff --git a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadge/css/index.tsx.preview deleted file mode 100644 index a1f344f2a6..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/css/index.tsx.preview +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/UnstyledBadge/system/index.js b/docs/data/base/components/badge/UnstyledBadge/system/index.js deleted file mode 100644 index 7787f48046..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/system/index.js +++ /dev/null @@ -1,81 +0,0 @@ -import * as React from 'react'; -import { styled, Box } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; - -function BadgeContent() { - return ( - - theme.palette.mode === 'dark' ? grey[400] : grey[300], - display: 'inline-block', - verticalAlign: 'middle', - }} - /> - ); -} - -export default function UnstyledBadge() { - return ( - - - - ); -} - -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - font-variant: tabular-nums; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 8px ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx b/docs/data/base/components/badge/UnstyledBadge/system/index.tsx deleted file mode 100644 index 7787f48046..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import * as React from 'react'; -import { styled, Box } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; - -function BadgeContent() { - return ( - - theme.palette.mode === 'dark' ? grey[400] : grey[300], - display: 'inline-block', - verticalAlign: 'middle', - }} - /> - ); -} - -export default function UnstyledBadge() { - return ( - - - - ); -} - -const blue = { - 500: '#007FFF', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - font-variant: tabular-nums; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 4px 8px ${theme.palette.mode === 'dark' ? grey[900] : grey[300]}; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadge/system/index.tsx.preview deleted file mode 100644 index 35b7141ca1..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/system/index.tsx.preview +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.js b/docs/data/base/components/badge/UnstyledBadge/tailwind/index.js deleted file mode 100644 index d478d261bc..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import { Badge as BaseBadge } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledBadge() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - - -
- ); -} - -const resolveSlotProps = (fn, args) => (typeof fn === 'function' ? fn(args) : fn); - -const Badge = React.forwardRef((props, ref) => { - return ( - { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.root, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'box-border m-0 p-0 text-xs font-sans list-none relative inline-block leading-none', - resolvedSlotProps?.className, - ), - }; - }, - badge: (ownerState) => { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.badge, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right', - resolvedSlotProps?.className, - ), - }; - }, - }} - /> - ); -}); - -Badge.propTypes = { - /** - * The props used for each slot inside the Badge. - * @default {} - */ - slotProps: PropTypes.shape({ - badge: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), -}; diff --git a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx b/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx deleted file mode 100644 index 3d9bcd670b..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from 'react'; -import clsx from 'clsx'; -import { Badge as BaseBadge, BadgeProps } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledBadge() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - - -
- ); -} - -const resolveSlotProps = (fn: any, args: any) => - typeof fn === 'function' ? fn(args) : fn; - -const Badge = React.forwardRef((props, ref) => { - return ( - { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.root, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'box-border m-0 p-0 text-xs font-sans list-none relative inline-block leading-none', - resolvedSlotProps?.className, - ), - }; - }, - badge: (ownerState) => { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.badge, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right', - resolvedSlotProps?.className, - ), - }; - }, - }} - /> - ); -}); diff --git a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx.preview deleted file mode 100644 index bd190ede6b..0000000000 --- a/docs/data/base/components/badge/UnstyledBadge/tailwind/index.tsx.preview +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.js b/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.js deleted file mode 100644 index a27d5caf20..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react'; -import { Badge } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -export default function UnstyledBadgeIntroduction() { - return ( - - - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - return ( - - ); -} diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx b/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx deleted file mode 100644 index a27d5caf20..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react'; -import { Badge } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -export default function UnstyledBadgeIntroduction() { - return ( - - - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - return ( - - ); -} diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx.preview deleted file mode 100644 index 4b5d65f54f..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/css/index.tsx.preview +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.js b/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.js deleted file mode 100644 index e8998cecda..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { styled, Box } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; - -const blue = { - 100: '#DAECFF', - 500: '#007FFF', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function BadgeContent() { - return ( - - theme.palette.mode === 'dark' ? grey[700] : grey[200], - display: 'inline-block', - verticalAlign: 'middle', - }} - /> - ); -} - -export default function UnstyledBadgeIntroduction() { - return ( - - - - ); -} - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - font-variant: tabular-nums; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 2px 24px ${ - theme.palette.mode === 'dark' ? blue[900] : blue[100] - }; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx b/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx deleted file mode 100644 index e8998cecda..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { styled, Box } from '@mui/system'; -import { Badge as BaseBadge, badgeClasses } from '@base_ui/react/Badge'; - -const blue = { - 100: '#DAECFF', - 500: '#007FFF', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function BadgeContent() { - return ( - - theme.palette.mode === 'dark' ? grey[700] : grey[200], - display: 'inline-block', - verticalAlign: 'middle', - }} - /> - ); -} - -export default function UnstyledBadgeIntroduction() { - return ( - - - - ); -} - -const Badge = styled(BaseBadge)( - ({ theme }) => ` - box-sizing: border-box; - margin: 0; - padding: 0; - font-size: 14px; - font-variant: tabular-nums; - list-style: none; - font-family: 'IBM Plex Sans', sans-serif; - position: relative; - display: inline-block; - line-height: 1; - - & .${badgeClasses.badge} { - z-index: auto; - position: absolute; - top: 0; - right: 0; - min-width: 22px; - height: 22px; - padding: 0 6px; - color: #fff; - font-weight: 600; - font-size: 12px; - line-height: 22px; - white-space: nowrap; - text-align: center; - border-radius: 12px; - background: ${blue[500]}; - box-shadow: 0px 2px 24px ${ - theme.palette.mode === 'dark' ? blue[900] : blue[100] - }; - transform: translate(50%, -50%); - transform-origin: 100% 0; - } - `, -); diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx.preview deleted file mode 100644 index 35b7141ca1..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/system/index.tsx.preview +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.js b/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.js deleted file mode 100644 index 2c6377cf2f..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import { Badge as BaseBadge } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledBadgeIntroduction() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - - -
- ); -} - -const resolveSlotProps = (fn, args) => (typeof fn === 'function' ? fn(args) : fn); - -const Badge = React.forwardRef((props, ref) => { - return ( - { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.root, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'box-border m-0 p-0 text-xs list-none relative inline-block leading-none', - resolvedSlotProps?.className, - ), - }; - }, - badge: (ownerState) => { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.badge, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs font-sans rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right', - resolvedSlotProps?.className, - ), - }; - }, - }} - /> - ); -}); - -Badge.propTypes = { - /** - * The props used for each slot inside the Badge. - * @default {} - */ - slotProps: PropTypes.shape({ - badge: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), -}; diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx b/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx deleted file mode 100644 index 5134735353..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from 'react'; -import clsx from 'clsx'; -import { Badge as BaseBadge, BadgeProps } from '@base_ui/react/Badge'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledBadgeIntroduction() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - - -
- ); -} - -const resolveSlotProps = (fn: any, args: any) => - typeof fn === 'function' ? fn(args) : fn; - -const Badge = React.forwardRef((props, ref) => { - return ( - { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.root, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'box-border m-0 p-0 text-xs list-none relative inline-block leading-none', - resolvedSlotProps?.className, - ), - }; - }, - badge: (ownerState) => { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.badge, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'z-auto absolute top-0 right-0 min-w-badge min-h-badge font-sans p-0 text-white font-semibold font-xs font-sans rounded-xl bg-purple-500 leading-5.5 whitespace-nowrap text-center translate-x-1/2 -translate-y-1/2 drop-shadow-lg origin-right', - resolvedSlotProps?.className, - ), - }; - }, - }} - /> - ); -}); diff --git a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx.preview deleted file mode 100644 index bd190ede6b..0000000000 --- a/docs/data/base/components/badge/UnstyledBadgeIntroduction/tailwind/index.tsx.preview +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/data/base/components/badge/badge.md b/docs/data/base/components/badge/badge.md deleted file mode 100644 index fc9e7139eb..0000000000 --- a/docs/data/base/components/badge/badge.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -productId: base-ui -title: React Badge component and hook -components: Badge -hooks: useBadge -githubLabel: 'component: badge' ---- - -# Badge - -

The Badge component generates a small label that is attached to its child element.

- -{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} - -{{"component": "modules/components/ComponentPageTabs.js"}} - -## Introduction - -A badge is a small descriptor for UI elements. -It typically sits on or near an element and indicates the status of that element by displaying a number, icon, or other short set of characters. - -The Badge component creates a badge that is applied to its child element. - -{{"demo": "UnstyledBadgeIntroduction", "defaultCodeOpen": false, "bg": "gradient"}} - -## Component - -```jsx -import { Badge } from '@base_ui/react/Badge'; -``` - -The Badge wraps around the UI element that it's attached to. - -### Anatomy - -The Badge component is composed of a root `` that houses the element that the Badge is attached to, followed by a `` slot to represent the Badge itself: - -```html - - - badge content - -``` - -### Custom structure - -Use the `slots` prop to override the root or any other interior slot: - -```jsx - -``` - -:::info -The `slots` prop is available on all non-utility Base components. -See [Overriding component structure](/base-ui/guides/overriding-component-structure/) for full details. -::: - -Use the `slotProps` prop to pass custom props to internal slots. -The following code snippet applies a CSS class called `my-badge` to the badge slot: - -```jsx - -``` - -### Usage with TypeScript - -In TypeScript, you can specify the custom component type used in the `slots.root` as a generic parameter of the unstyled component. -This way, you can safely provide the custom root's props directly on the component: - -```tsx - slots={{ root: CustomComponent }} customProp /> -``` - -The same applies for props specific to custom primitive elements: - -```tsx - slots={{ root: 'img' }} src="badge.png" /> -``` - -## Hook - -```jsx -import { useBadge } from '@base_ui/react/useBadge'; -``` - -The `useBadge` hook lets you apply the functionality of a Badge to a fully custom component. -It returns props to be placed on the custom component, along with fields representing the component's internal state. - -Hooks _do not_ support [slot props](#custom-structure), but they do support [customization props](#customization). - -:::info -Hooks give you the most room for customization, but require more work to implement. -With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need. - -You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy). -::: - -## Customization - -:::info -The following features can be used with both components and hooks. -For the sake of simplicity, demos, and code snippets primarily feature components. -::: - -### Badge content - -The `badgeContent` prop defines the content that's displayed inside the Badge. -When this content is a number, there are additional props you can use for further customization—see the [Numerical Badges section](#numerical-badges) below. - -The following demo shows how to create and style a typical numerical Badge that's attached to a generic box element: - -{{"demo": "UnstyledBadge", "defaultCodeOpen": false}} - -### Badge visibility - -You can control the visibility of a Badge by using the `invisible` prop. -Setting a Badge to `invisible` does not actually hide it—instead, this prop adds the `BaseBadge-invisible` class to the Badge, which you can target with styles to hide however you prefer: - -{{"demo": "BadgeVisibility.js"}} - -### Numerical Badges - -The following props are useful when `badgeContent` is a number. - -#### The showZero prop - -By default, Badges automatically hide when `badgeContent={0}`. -You can override this behavior with the `showZero` prop: - -{{"demo": "ShowZeroBadge.js"}} - -#### The max prop - -You can use the `max` prop to set a maximum value for `badgeContent`. -The default is 99. - -{{"demo": "BadgeMax.js"}} - -## Accessibility - -Screen readers may not provide users with enough information about a Badge's contents. -To make your badge accessible, you must provide a full description with `aria-label`, as shown in the demo below: - -{{"demo": "AccessibleBadges.js"}} diff --git a/docs/data/base/components/button/UnstyledButtonCustom.js b/docs/data/base/components/button/UnstyledButtonCustom.js deleted file mode 100644 index 225eeda467..0000000000 --- a/docs/data/base/components/button/UnstyledButtonCustom.js +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { Button, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; - -const ButtonRoot = React.forwardRef(function ButtonRoot(props, ref) { - const { children, ...other } = props; - - return ( - - - - -
{children}
-
-
- ); -}); - -ButtonRoot.propTypes = { - children: PropTypes.node, -}; - -const SvgButton = React.forwardRef(function SvgButton(props, ref) { - return - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx b/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx deleted file mode 100644 index b36ce05b76..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from 'react'; -import { Button } from '@base_ui/react/Button'; -import { useTheme } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsIntroduction() { - return ( - - - - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx.preview b/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx.preview deleted file mode 100644 index a7acca3d14..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/css/index.tsx.preview +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.js b/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.js deleted file mode 100644 index 3a17beb8a2..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.js +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsIntroduction() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } - `, -); diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx b/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx deleted file mode 100644 index 3a17beb8a2..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsIntroduction() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } - `, -); diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx.preview b/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx.preview deleted file mode 100644 index b7b5add52a..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/system/index.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.js b/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.js deleted file mode 100644 index 1af53ada6b..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { Button as BaseButton } from '@base_ui/react/Button'; -import Stack from '@mui/material/Stack'; -import clsx from 'clsx'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledButtonsIntroduction() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - Button - Disabled - -
- ); -} - -const CustomButton = React.forwardRef((props, ref) => { - const { className, ...other } = props; - return ( - - ); -}); - -CustomButton.propTypes = { - className: PropTypes.string, -}; diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx b/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx deleted file mode 100644 index a8b9ee179f..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, ButtonProps } from '@base_ui/react/Button'; -import Stack from '@mui/material/Stack'; -import clsx from 'clsx'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledButtonsIntroduction() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - Button - Disabled - -
- ); -} - -const CustomButton = React.forwardRef( - (props, ref) => { - const { className, ...other } = props; - return ( - - ); - }, -); diff --git a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx.preview deleted file mode 100644 index 53f6465d2c..0000000000 --- a/docs/data/base/components/button/UnstyledButtonIntroduction/tailwind/index.tsx.preview +++ /dev/null @@ -1,4 +0,0 @@ - - Button - Disabled - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.js b/docs/data/base/components/button/UnstyledButtonsDisabledFocus.js deleted file mode 100644 index db4f511e8f..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.js +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsDisabledFocus() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx b/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx deleted file mode 100644 index db4f511e8f..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsDisabledFocus() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx.preview b/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx.preview deleted file mode 100644 index 13ae2ccca2..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsDisabledFocus.tsx.preview +++ /dev/null @@ -1,4 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.js b/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.js deleted file mode 100644 index 9c39abc808..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.js +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsDisabledFocusCustom() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx b/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx deleted file mode 100644 index c12b05e26c..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from 'react'; -import { - Button as BaseButton, - buttonClasses, - ButtonTypeMap, -} from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; -import { PolymorphicComponent } from '@base_ui/react/utils'; - -export default function UnstyledButtonsDisabledFocusCustom() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -) as PolymorphicComponent; diff --git a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx.preview b/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx.preview deleted file mode 100644 index 61742c2ce4..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsDisabledFocusCustom.tsx.preview +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledButtonsSimple.js b/docs/data/base/components/button/UnstyledButtonsSimple.js deleted file mode 100644 index 28c2022603..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsSimple.js +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsSimple() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &:active { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &:focus-visible { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.base--disabled { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsSimple.tsx b/docs/data/base/components/button/UnstyledButtonsSimple.tsx deleted file mode 100644 index 28c2022603..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsSimple.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsSimple() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &:active { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &:focus-visible { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.base--disabled { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsSimple.tsx.preview b/docs/data/base/components/button/UnstyledButtonsSimple.tsx.preview deleted file mode 100644 index b7b5add52a..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsSimple.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledButtonsSpan.js b/docs/data/base/components/button/UnstyledButtonsSpan.js deleted file mode 100644 index 5157fb7ce1..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsSpan.js +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsSpan() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsSpan.tsx b/docs/data/base/components/button/UnstyledButtonsSpan.tsx deleted file mode 100644 index 5157fb7ce1..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsSpan.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; - -export default function UnstyledButtonsSpan() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UnstyledButtonsSpan.tsx.preview b/docs/data/base/components/button/UnstyledButtonsSpan.tsx.preview deleted file mode 100644 index 3a8d9ed0ae..0000000000 --- a/docs/data/base/components/button/UnstyledButtonsSpan.tsx.preview +++ /dev/null @@ -1,4 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/base/components/button/UnstyledLinkButton.js b/docs/data/base/components/button/UnstyledLinkButton.js deleted file mode 100644 index 3064feff34..0000000000 --- a/docs/data/base/components/button/UnstyledLinkButton.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { prepareForSlot } from '@base_ui/react/utils'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; -import Link from 'next/link'; - -const LinkSlot = prepareForSlot(Link); - -export default function UnstyledLinkButton() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - text-decoration: none; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } - `, -); diff --git a/docs/data/base/components/button/UnstyledLinkButton.tsx b/docs/data/base/components/button/UnstyledLinkButton.tsx deleted file mode 100644 index 3064feff34..0000000000 --- a/docs/data/base/components/button/UnstyledLinkButton.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { Button as BaseButton, buttonClasses } from '@base_ui/react/Button'; -import { prepareForSlot } from '@base_ui/react/utils'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; -import Link from 'next/link'; - -const LinkSlot = prepareForSlot(Link); - -export default function UnstyledLinkButton() { - return ( - - - - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const Button = styled(BaseButton)( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - text-decoration: none; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.${buttonClasses.active} { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.${buttonClasses.focusVisible} { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.${buttonClasses.disabled} { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } - `, -); diff --git a/docs/data/base/components/button/UnstyledLinkButton.tsx.preview b/docs/data/base/components/button/UnstyledLinkButton.tsx.preview deleted file mode 100644 index 3c5dd7dea1..0000000000 --- a/docs/data/base/components/button/UnstyledLinkButton.tsx.preview +++ /dev/null @@ -1,4 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/base/components/button/UseButton.js b/docs/data/base/components/button/UseButton.js deleted file mode 100644 index af2a0f1cc5..0000000000 --- a/docs/data/base/components/button/UseButton.js +++ /dev/null @@ -1,110 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; -import { useButton } from '@base_ui/react/useButton'; - -const CustomButton = React.forwardRef(function CustomButton(props, ref) { - const { children, disabled } = props; - const { active, focusVisible, getRootProps } = useButton({ - ...props, - rootRef: ref, - }); - - return ( - - {children} - - ); -}); - -CustomButton.propTypes = { - children: PropTypes.node, - /** - * If `true`, the component is disabled. - * @default false - */ - disabled: PropTypes.bool, -}; - -export default function UseButton() { - return ( - - console.log('click!')}>Button - Disabled - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const CustomButtonRoot = styled('button')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.active { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.focusVisible { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.disabled { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UseButton.tsx b/docs/data/base/components/button/UseButton.tsx deleted file mode 100644 index 05fd30523e..0000000000 --- a/docs/data/base/components/button/UseButton.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react'; -import clsx from 'clsx'; -import { styled } from '@mui/system'; -import Stack from '@mui/material/Stack'; -import { useButton } from '@base_ui/react/useButton'; -import { ButtonProps } from '@base_ui/react/Button'; - -const CustomButton = React.forwardRef(function CustomButton( - props: ButtonProps, - ref: React.ForwardedRef, -) { - const { children, disabled } = props; - const { active, focusVisible, getRootProps } = useButton({ - ...props, - rootRef: ref, - }); - - return ( - - {children} - - ); -}); - -export default function UseButton() { - return ( - - console.log('click!')}>Button - Disabled - - ); -} - -const blue = { - 200: '#99CCFF', - 300: '#66B2FF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0066CC', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const CustomButtonRoot = styled('button')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 600; - font-size: 0.875rem; - line-height: 1.5; - background-color: ${blue[500]}; - padding: 8px 16px; - border-radius: 8px; - color: white; - transition: all 150ms ease; - cursor: pointer; - border: 1px solid ${blue[500]}; - box-shadow: 0 2px 1px ${ - theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.5)' : 'rgba(45, 45, 60, 0.2)' - }, inset 0 1.5px 1px ${blue[400]}, inset 0 -2px 1px ${blue[600]}; - - &:hover { - background-color: ${blue[600]}; - } - - &.active { - background-color: ${blue[700]}; - box-shadow: none; - transform: scale(0.99); - } - - &.focusVisible { - box-shadow: 0 0 0 4px ${theme.palette.mode === 'dark' ? blue[300] : blue[200]}; - outline: none; - } - - &.disabled { - background-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[700]}; - border: 0; - cursor: default; - box-shadow: none; - transform: scale(1); - } -`, -); diff --git a/docs/data/base/components/button/UseButton.tsx.preview b/docs/data/base/components/button/UseButton.tsx.preview deleted file mode 100644 index 475a9b68ef..0000000000 --- a/docs/data/base/components/button/UseButton.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - console.log('click!')}>Button -Disabled \ No newline at end of file diff --git a/docs/data/base/components/button/button.md b/docs/data/base/components/button/button.md deleted file mode 100644 index ad72ea436e..0000000000 --- a/docs/data/base/components/button/button.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -productId: base-ui -title: React Button component and hook -components: Button -hooks: useButton -githubLabel: 'component: button' -waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/button/ ---- - -# Button - -

Buttons let users take actions and make choices with a single tap.

- -{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} - -{{"component": "modules/components/ComponentPageTabs.js"}} - -## Introduction - -The Button component replaces the native HTML ` -``` - -### Custom structure - -Use the `slots.root` prop to override the root slot with a custom element: - -```jsx - + + + + Demo dialog + Close + + +
+); +``` diff --git a/docs/data/base/components/drawer/drawer.md b/docs/data/base/components/drawer/drawer.md deleted file mode 100644 index 92f3253ea7..0000000000 --- a/docs/data/base/components/drawer/drawer.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -productId: base-ui -title: React Drawer component -githubLabel: 'component: drawer' ---- - -# Drawer 🚧 - -

Navigation drawers (also known as sidebars) provide ergonomic access to different destinations without taking the user out of context.

- -:::warning -The Base UI Drawer component isn't available yet, but you can upvote [this GitHub issue](https://github.com/mui/base-ui/issues/38) to see it arrive sooner. -::: diff --git a/docs/data/base/components/field/UnstyledFieldAsync.js b/docs/data/base/components/field/UnstyledFieldAsync.js new file mode 100644 index 0000000000..d4c73250c8 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldAsync.js @@ -0,0 +1,149 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +const cache = new Map(); + +function checkAvailability(name) { + const takenNames = ['admin', 'root', 'superuser']; + return new Promise((resolve) => { + setTimeout(() => { + const result = takenNames.includes(name) ? 'Name taken' : null; + cache.set(name, result); + resolve(result); + }, 500); + }); +} + +export default function UnstyledFieldAsync() { + const [loading, setLoading] = React.useState(false); + + async function handleValidate(value) { + const name = value; + + if (name === '') { + return null; + } + + const isCached = cache.has(name); + if (isCached) { + return cache.get(name); + } + + setLoading(true); + + try { + const error = await checkAvailability(name); + setLoading(false); + return error; + } catch (e) { + setLoading(false); + return 'Failed to fetch name availability'; + } + } + + return ( +
+

Handle availability checker

+ +
+ @ + + {(state) => ( + + )} + +
+ + {(state) => { + if (loading) { + return Checking availability...; + } + + if (state.value === '') { + return Enter a name; + } + + if (!state.validity.customError) { + return ( + + @{state.value} is available + + ); + } + + return ; + }} + +
+
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='invalid']:not([data-pending]) { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &[data-field='valid']:not([data-pending]) { + border-color: green; + background-color: rgb(0 255 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='invalid']:not([data-pending]) { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + + &[data-field='valid']:not([data-pending]) { + box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3); + } + } +`; + +const FieldDescription = styled(Field.Description)` + font-size: 90%; + margin: 0; + margin-top: 4px; + line-height: 1.1; + color: grey; + + &[data-type='success'] { + color: green; + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin: 0; + margin-top: 4px; + line-height: 1.1; + color: red; +`; diff --git a/docs/data/base/components/field/UnstyledFieldAsync.tsx b/docs/data/base/components/field/UnstyledFieldAsync.tsx new file mode 100644 index 0000000000..83354edc18 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldAsync.tsx @@ -0,0 +1,149 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +const cache = new Map(); + +function checkAvailability(name: string) { + const takenNames = ['admin', 'root', 'superuser']; + return new Promise((resolve) => { + setTimeout(() => { + const result = takenNames.includes(name) ? 'Name taken' : null; + cache.set(name, result); + resolve(result); + }, 500); + }); +} + +export default function UnstyledFieldAsync() { + const [loading, setLoading] = React.useState(false); + + async function handleValidate(value: unknown) { + const name = value as string; + + if (name === '') { + return null; + } + + const isCached = cache.has(name); + if (isCached) { + return cache.get(name) as string | null; + } + + setLoading(true); + + try { + const error = await checkAvailability(name); + setLoading(false); + return error; + } catch (e) { + setLoading(false); + return 'Failed to fetch name availability'; + } + } + + return ( +
+

Handle availability checker

+ +
+ @ + + {(state) => ( + + )} + +
+ + {(state) => { + if (loading) { + return Checking availability...; + } + + if (state.value === '') { + return Enter a name; + } + + if (!state.validity.customError) { + return ( + + @{state.value as string} is available + + ); + } + + return ; + }} + +
+
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='invalid']:not([data-pending]) { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &[data-field='valid']:not([data-pending]) { + border-color: green; + background-color: rgb(0 255 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='invalid']:not([data-pending]) { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + + &[data-field='valid']:not([data-pending]) { + box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3); + } + } +`; + +const FieldDescription = styled(Field.Description)` + font-size: 90%; + margin: 0; + margin-top: 4px; + line-height: 1.1; + color: grey; + + &[data-type='success'] { + color: green; + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin: 0; + margin-top: 4px; + line-height: 1.1; + color: red; +`; diff --git a/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.js b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.js new file mode 100644 index 0000000000..4405f78b64 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.js @@ -0,0 +1,86 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +export default function UnstyledFieldIntroduction() { + return ( + (value === 'admin' ? 'Name not allowed' : null)}> +
+ Name + +
+ + {({ validity, value }) => { + if ( + validity.valueMissing || + validity.patternMismatch || + value === 'admin' + ) { + return null; + } + + return ( + + Your name will be visible on your profile. + + ); + }} + + + + + Only alphanumeric characters are allowed (a-z, A-Z, 0-9). + +
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='invalid'] { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='invalid'] { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + } +`; + +const FieldDescription = styled(Field.Description)` + font-size: 90%; + margin-bottom: 0; + margin-top: 4px; + line-height: 1.1; + color: grey; + + &[data-error] { + color: red; + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin: 0; + margin-bottom: 0; + margin-top: 4px; + line-height: 1.1; + color: red; +`; diff --git a/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.tsx b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.tsx new file mode 100644 index 0000000000..4405f78b64 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldIntroduction/system/index.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +export default function UnstyledFieldIntroduction() { + return ( + (value === 'admin' ? 'Name not allowed' : null)}> +
+ Name + +
+ + {({ validity, value }) => { + if ( + validity.valueMissing || + validity.patternMismatch || + value === 'admin' + ) { + return null; + } + + return ( + + Your name will be visible on your profile. + + ); + }} + + + + + Only alphanumeric characters are allowed (a-z, A-Z, 0-9). + +
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='invalid'] { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='invalid'] { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + } +`; + +const FieldDescription = styled(Field.Description)` + font-size: 90%; + margin-bottom: 0; + margin-top: 4px; + line-height: 1.1; + color: grey; + + &[data-error] { + color: red; + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin: 0; + margin-bottom: 0; + margin-top: 4px; + line-height: 1.1; + color: red; +`; diff --git a/docs/data/base/components/field/UnstyledFieldPassword.js b/docs/data/base/components/field/UnstyledFieldPassword.js new file mode 100644 index 0000000000..eaa46599e1 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldPassword.js @@ -0,0 +1,100 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +function validate(value) { + const password = value; + const errors = []; + + if (password.length < 8) { + errors.push('Password must be at least 8 characters long.'); + } + + if ((password.match(/[A-Z]/g) ?? []).length < 2) { + errors.push('Password must contain at least 2 uppercase letters.'); + } + + if ((password.match(/[!@#$%^&*]/g) ?? []).length < 2) { + errors.push( + 'Password must contain at least 2 unique symbols from the set [!@#$%^&*].', + ); + } + + return errors; +} + +export default function UnstyledFieldPassword() { + const [value, setValue] = React.useState(''); + const errors = validate(value); + + return ( + 0}> + Password + setValue(event.currentTarget.value)} + /> + +
    + {errors.map((error) => ( +
  • {error}
  • + ))} +
+
+
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='valid'][data-dirty] { + border-color: green; + background-color: rgb(0 255 0 / 0.1); + } + + &[data-field='invalid'][data-touched][data-dirty] { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='valid'][data-dirty] { + border-color: green; + box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3); + } + + &[data-field='invalid'][data-touched][data-dirty] { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin-top: 10px; + line-height: 1.1; + + &[data-touched][data-dirty] { + color: red; + } + + ul { + padding: 0; + } +`; diff --git a/docs/data/base/components/field/UnstyledFieldPassword.tsx b/docs/data/base/components/field/UnstyledFieldPassword.tsx new file mode 100644 index 0000000000..667bec5780 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldPassword.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +function validate(value: string) { + const password = value; + const errors: string[] = []; + + if (password.length < 8) { + errors.push('Password must be at least 8 characters long.'); + } + + if ((password.match(/[A-Z]/g) ?? []).length < 2) { + errors.push('Password must contain at least 2 uppercase letters.'); + } + + if ((password.match(/[!@#$%^&*]/g) ?? []).length < 2) { + errors.push( + 'Password must contain at least 2 unique symbols from the set [!@#$%^&*].', + ); + } + + return errors; +} + +export default function UnstyledFieldPassword() { + const [value, setValue] = React.useState(''); + const errors = validate(value); + + return ( + 0}> + Password + setValue(event.currentTarget.value)} + /> + +
    + {errors.map((error) => ( +
  • {error}
  • + ))} +
+
+
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='valid'][data-dirty] { + border-color: green; + background-color: rgb(0 255 0 / 0.1); + } + + &[data-field='invalid'][data-touched][data-dirty] { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='valid'][data-dirty] { + border-color: green; + box-shadow: 0 0 0 3px rgba(100 200 100 / 0.3); + } + + &[data-field='invalid'][data-touched][data-dirty] { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin-top: 10px; + line-height: 1.1; + + &[data-touched][data-dirty] { + color: red; + } + + ul { + padding: 0; + } +`; diff --git a/docs/data/base/components/field/UnstyledFieldPassword.tsx.preview b/docs/data/base/components/field/UnstyledFieldPassword.tsx.preview new file mode 100644 index 0000000000..6dcd33da03 --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldPassword.tsx.preview @@ -0,0 +1,15 @@ + 0}> + Password + setValue(event.currentTarget.value)} + /> + +
    + {errors.map((error) => ( +
  • {error}
  • + ))} +
+
+
\ No newline at end of file diff --git a/docs/data/base/components/field/UnstyledFieldServerError.js b/docs/data/base/components/field/UnstyledFieldServerError.js new file mode 100644 index 0000000000..1f60d6e6ba --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldServerError.js @@ -0,0 +1,161 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +export default function UnstyledFieldServerError() { + const [error, setError] = React.useState(false); + const [status, setStatus] = React.useState('initial'); + + const controlRef = React.useRef(null); + + async function handleSubmit(event) { + event.preventDefault(); + + if (error || !controlRef.current?.validity.valid) { + return; + } + + const formData = new FormData(event.currentTarget); + const email = formData.get('email'); + + setStatus('loading'); + + // Mimic a server request + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + + if (email && email.endsWith('@example.com')) { + setStatus('error'); + setError(true); + } else { + setStatus('success'); + } + + controlRef.current?.focus(); + } + + return ( +
+ + Email address + { + setStatus('initial'); + setError(false); + }} + /> + + {(state) => ( + { + if (status === 'loading') { + event.preventDefault(); + } + }} + > + {status === 'loading' ? 'Processing...' : 'Change email'} + + )} + + + @example.com is not allowed + {status === 'success' && ( + + Email changed successfully + + )} + + + On the client, standard email validation is performed. On the server, we + check a blocklist of email domains: the blocked domain is @example.com. + + +
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='invalid'] { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='invalid'] { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin: 0; + padding: 0; + margin-top: 10px; + line-height: 1.1; + + &[data-dirty], + &[data-touched] { + color: red; + } +`; + +const FieldSuccess = styled(Field.Description)` + font-size: 90%; + margin: 0; + padding: 0; + margin-top: 10px; + color: green; +`; + +const FieldDescription = styled('p')` + font-size: 90%; + margin: 0; + padding: 0; + margin-top: 10px; + line-height: 1.1; + color: grey; +`; + +const FieldSubmit = styled('button')` + display: block; + margin-top: 10px; + padding: 10px; + width: 100%; + font-size: 100%; + background-color: #0078d4; + color: white; + border: none; + border-radius: 4px; + + &[aria-disabled='true'] { + background-color: #ddd; + color: black; + } +`; diff --git a/docs/data/base/components/field/UnstyledFieldServerError.tsx b/docs/data/base/components/field/UnstyledFieldServerError.tsx new file mode 100644 index 0000000000..7dfb843aaa --- /dev/null +++ b/docs/data/base/components/field/UnstyledFieldServerError.tsx @@ -0,0 +1,162 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import { styled } from '@mui/system'; + +type Status = 'initial' | 'loading' | 'success' | 'error'; + +export default function UnstyledFieldServerError() { + const [error, setError] = React.useState(false); + const [status, setStatus] = React.useState('initial'); + + const controlRef = React.useRef(null); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + if (error || !controlRef.current?.validity.valid) { + return; + } + + const formData = new FormData(event.currentTarget); + const email = formData.get('email') as string; + + setStatus('loading'); + + // Mimic a server request + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + + if (email && email.endsWith('@example.com')) { + setStatus('error'); + setError(true); + } else { + setStatus('success'); + } + + controlRef.current?.focus(); + } + + return ( +
+ + Email address + { + setStatus('initial'); + setError(false); + }} + /> + + {(state) => ( + { + if (status === 'loading') { + event.preventDefault(); + } + }} + > + {status === 'loading' ? 'Processing...' : 'Change email'} + + )} + + + @example.com is not allowed + {status === 'success' && ( + + Email changed successfully + + )} + + On the client, standard email validation is performed. On the server, we + check a blocklist of email domains: the blocked domain is @example.com. + + +
+ ); +} + +const FieldRoot = styled(Field.Root)` + width: 275px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + font-size: 100%; + + &[data-field='invalid'] { + border-color: red; + background-color: rgb(255 0 0 / 0.1); + } + + &:focus { + outline: 0; + border-color: #0078d4; + box-shadow: 0 0 0 3px rgba(0 100 255 / 0.3); + + &[data-field='invalid'] { + border-color: red; + box-shadow: 0 0 0 3px rgba(255 0 0 / 0.3); + } + } +`; + +const FieldError = styled(Field.Error)` + display: block; + font-size: 90%; + margin: 0; + padding: 0; + margin-top: 10px; + line-height: 1.1; + + &[data-dirty], + &[data-touched] { + color: red; + } +`; + +const FieldSuccess = styled(Field.Description)` + font-size: 90%; + margin: 0; + padding: 0; + margin-top: 10px; + color: green; +`; + +const FieldDescription = styled('p')` + font-size: 90%; + margin: 0; + padding: 0; + margin-top: 10px; + line-height: 1.1; + color: grey; +`; + +const FieldSubmit = styled('button')` + display: block; + margin-top: 10px; + padding: 10px; + width: 100%; + font-size: 100%; + background-color: #0078d4; + color: white; + border: none; + border-radius: 4px; + + &[aria-disabled='true'] { + background-color: #ddd; + color: black; + } +`; diff --git a/docs/data/base/components/field/field.md b/docs/data/base/components/field/field.md new file mode 100644 index 0000000000..3b6c205f1b --- /dev/null +++ b/docs/data/base/components/field/field.md @@ -0,0 +1,275 @@ +--- +productId: base-ui +title: React Field component and hook +components: FieldRoot, FieldLabel, FieldDescription, FieldError, FieldControl, FieldValidity +githubLabel: 'component: field' +packageName: '@base_ui/react' +--- + +# Field + +

Fields represent an individual section of a form containing an associated control and label, as well as any description or validation messages.

+ +{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} + +{{"component": "modules/components/ComponentPageTabs.js"}} + +{{"demo": "UnstyledFieldIntroduction", "defaultCodeOpen": false, "bg": "gradient"}} + +## Installation + +Base UI components are all available as a single package. + + + +```bash npm +npm install @base_ui/react +``` + +```bash yarn +yarn add @base_ui/react +``` + +```bash pnpm +pnpm add @base_ui/react +``` + + + +Once you have the package installed, import the component. + +```ts +import * as Field from '@base_ui/react/Field'; +``` + +## Anatomy + +Fields are implemented using a collection of related components: + +- `` is a top-level component that wraps all other components. +- `` renders the control when not using a native Base UI input component. +- `` renders a label for the control. +- `` renders an optional description for the control to provide additional information. +- `` renders error messages for the control. +- `` accepts a function as a child that enables reading raw `ValidityState` to render custom JSX. + +```jsx + + + + + + + +``` + +## Labeling and descriptive help text + +All Base UI input components are aware of Base UI's `Field` component. The label and description are automatically wired to these components when placed inside a `Field.Root`: + +```jsx + + + + + My checkbox + My description + +``` + +When using a native control like `input` or a custom component which is not aware of Base UI's `Field`, use `Field.Control`: + +```jsx + + + My input + My description + +``` + +The `render` prop allows you to pass a custom component or tag, different from the default of `input`: + +```jsx +} /> +``` + +## Validation + +When adding native HTML validation props like `required` or `pattern`, `Field.Error` renders error messages inside of it automatically: + +```jsx + + My input + + + +``` + +The `children` by default is the browser's native message, which is automatically internationalized. You may pass custom `children` instead: + +```jsx + + + Field is required + +``` + +### Individual constraint validation failures + +When there are multiple HTML validation props, you can target individual validity state failures using the `show` prop to render custom messages: + +```jsx + + + Field is required + + Only alphanumeric characters allowed + + +``` + +For the list of supported `show` strings, visit [`ValidityState` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState#instance_properties). + +### Custom validation + +In addition to the native HTML constraint validation, custom validation can be used by specifying a `validate` function on `Field.Root`. It receives the control's `value` as its argument, and returns an error string or array of error strings if the field is invalid, or `null` otherwise. + +```jsx + + value === 'password' ? 'Cannot literally use `password` as your password.' : null + } +> + + Password + + +``` + +:::info +For Base UI input components, `value` represents the component's value type, while for native elements, it is always the native `element.value` DOM property. Attach a `ref` to the `Control` element and access it to read its state inside the `validate` function for further control as an alternative if necessary. +::: + +To customize the rendering of multiple messages, you can use the `Validity` subcomponent: + +```jsx + { + const errors = []; + if (value.length < 8) { + errors.push('Password must be at least 8 characters long.'); + } + if (value === 'password') { + errors.push('Cannot literally use `password` as your password.'); + } + return errors; + }} +> + + Password + +
    + + {(state) => state.errors.map((error) =>
  • {error}
  • )} +
    +
+
+
+``` + +The `Validity` subcomponent enables rendering custom JSX based on the `state` parameter, which contains the following properties: + +- `state.validity`, the field's `ValidityState` +- `state.errors`, an array of custom errors returned from the `validate` prop (if present) +- `state.error`, a custom error string returned from the `validate` prop (if present) +- `state.value`, the field control's current value +- `state.initialValue`, the field control's initial value upon mount + +It can be placed anywhere inside `Field.Root`, including other Field subcomponents. + +### Controlled validity + +When the `invalid` prop is applied to `Field.Root`, the Field is placed into an invalid state regardless of client-side validation. In this state, a given `Field.Error` message can be forced to be shown by specifying a `forceShow` prop. + +This is useful for server-side error messages, or displaying errors initially during SSR phase. + +```jsx +const [serverErrors, setServerErrors] = React.useState({ + email: false, +}); + +return ( + + + Client-side only error message + + Client + server-side error message + + + Server-side only message + + +); +``` + +The `show` prop is for client-side validation, while the `forceShow` prop is for server-side validation. Both can be combined together to share the same error message. + +Performing an email validity check on the server: + +{{"demo": "UnstyledFieldServerError.js", "defaultCodeOpen": false}} + +Errors shown initially for password validation: + +{{"demo": "UnstyledFieldPassword.js", "defaultCodeOpen": false}} + +### Realtime and async validation + +`validateOnChange` reports the validity of the control on every `change` event, such as a keypress: + +```jsx + +``` + +The `validate` function can also be async by returning a promise, enabling inline server-side validation through network requests. + +In the demo below, the taken names are `admin`, `root`, and `superuser` — every other name is available. For demonstration purposes, a fake network request that takes 500ms is initiated to mimic a trip to the server to check for availability on the back-end. + +{{"demo": "UnstyledFieldAsync.js", "defaultCodeOpen": false}} + +The `change` validation is debounced by 500ms to avoid firing a network request on every keystroke by specifying the `validateDebounceTime` prop: + +```jsx + +``` + +## Styling + +The `[data-field="valid"]` and `[data-field="invalid"]` style hooks determine if the field is invalid or not: + +```jsx + + + +``` + +```css +.FieldControl[data-field='invalid'] { + color: red; +} +``` + +`[data-touched]` is applied if the field has been "touched": blurred after being interacted with, or submitted if pressing Enter on an input. + +```css +.FieldControl[data-touched] { + color: red; +} +``` + +`[data-dirty]` is applied if the field's value has been changed from its initial one. + +```css +.FieldControl[data-dirty] { + color: orange; +} +``` diff --git a/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.js b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.js new file mode 100644 index 0000000000..ab2c01ea7a --- /dev/null +++ b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.js @@ -0,0 +1,50 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import * as Fieldset from '@base_ui/react/Fieldset'; +import { styled } from '@mui/system'; + +export default function UnstyledFieldsetIntroduction() { + return ( + + Account details + + Name + + + + Address + + + + Bio + } /> + + + ); +} + +const FieldsetRoot = styled(Fieldset.Root)` + border: none; + width: 300px; +`; + +const FieldsetLegend = styled(Fieldset.Legend)` + display: inline-block; + font-size: 125%; + font-weight: bold; + margin-bottom: 10px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + margin-bottom: 5px; + + &[data-textarea] { + min-width: 300px; + max-width: 300px; + min-height: 100px; + } +`; diff --git a/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx new file mode 100644 index 0000000000..ab2c01ea7a --- /dev/null +++ b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import * as Field from '@base_ui/react/Field'; +import * as Fieldset from '@base_ui/react/Fieldset'; +import { styled } from '@mui/system'; + +export default function UnstyledFieldsetIntroduction() { + return ( + + Account details + + Name + + + + Address + + + + Bio + } /> + + + ); +} + +const FieldsetRoot = styled(Fieldset.Root)` + border: none; + width: 300px; +`; + +const FieldsetLegend = styled(Fieldset.Legend)` + display: inline-block; + font-size: 125%; + font-weight: bold; + margin-bottom: 10px; +`; + +const FieldControl = styled(Field.Control)` + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + padding: 6px; + margin-bottom: 5px; + + &[data-textarea] { + min-width: 300px; + max-width: 300px; + min-height: 100px; + } +`; diff --git a/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx.preview b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx.preview new file mode 100644 index 0000000000..7bdd82c548 --- /dev/null +++ b/docs/data/base/components/fieldset/UnstyledFieldsetIntroduction/system/index.tsx.preview @@ -0,0 +1,15 @@ + + Account details + + Name + + + + Address + + + + Bio + } /> + + \ No newline at end of file diff --git a/docs/data/base/components/fieldset/fieldset.md b/docs/data/base/components/fieldset/fieldset.md new file mode 100644 index 0000000000..eb26449596 --- /dev/null +++ b/docs/data/base/components/fieldset/fieldset.md @@ -0,0 +1,78 @@ +--- +productId: base-ui +title: React Fieldset component and hook +components: FieldsetRoot, FieldsetLegend +githubLabel: 'component: fieldset' +packageName: '@base_ui/react' +--- + +# Fieldset + +

Fieldsets group multiple fields together with a label. This is a light wrapper over the native `fieldset` and `legend` elements, but makes the `legend` element easily stylable.

+ +{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} + +{{"component": "modules/components/ComponentPageTabs.js"}} + +{{"demo": "UnstyledFieldsetIntroduction", "defaultCodeOpen": false, "bg": "gradient"}} + +## Installation + +Base UI components are all available as a single package. + + + +```bash npm +npm install @base_ui/react +``` + +```bash yarn +yarn add @base_ui/react +``` + +```bash pnpm +pnpm add @base_ui/react +``` + + + +Once you have the package installed, import the component. + +```ts +import * as Fieldset from '@base_ui/react/Fieldset'; +``` + +## Anatomy + +Fieldsets are composed of two components: + +- `` renders the `fieldset` element. +- `` renders a label for the fieldset. + +```jsx + + + +``` + +## Usage with Fields + +`Field` components are placed inside the `Fieldset` component. + +```jsx + + Account details + + Name + + + + Address + + + + Bio + } /> + + +``` diff --git a/docs/data/base/components/focus-trap/BasicFocusTrap.js b/docs/data/base/components/focus-trap/BasicFocusTrap.js index cc908a1d5f..f8a10ed484 100644 --- a/docs/data/base/components/focus-trap/BasicFocusTrap.js +++ b/docs/data/base/components/focus-trap/BasicFocusTrap.js @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function BasicFocusTrap() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/BasicFocusTrap.tsx b/docs/data/base/components/focus-trap/BasicFocusTrap.tsx index cc908a1d5f..f8a10ed484 100644 --- a/docs/data/base/components/focus-trap/BasicFocusTrap.tsx +++ b/docs/data/base/components/focus-trap/BasicFocusTrap.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function BasicFocusTrap() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js index f7d875e6d9..1090e56fcc 100644 --- a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js +++ b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.js @@ -1,6 +1,6 @@ import * as React from 'react'; import Stack from '@mui/system/Stack'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function ContainedToggleTrappedFocus() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx index f7d875e6d9..1090e56fcc 100644 --- a/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx +++ b/docs/data/base/components/focus-trap/ContainedToggleTrappedFocus.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Stack from '@mui/system/Stack'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function ContainedToggleTrappedFocus() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/DisableEnforceFocus.js b/docs/data/base/components/focus-trap/DisableEnforceFocus.js index 4a5aec32fc..116542d7e6 100644 --- a/docs/data/base/components/focus-trap/DisableEnforceFocus.js +++ b/docs/data/base/components/focus-trap/DisableEnforceFocus.js @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function DisableEnforceFocus() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx b/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx index 4a5aec32fc..116542d7e6 100644 --- a/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx +++ b/docs/data/base/components/focus-trap/DisableEnforceFocus.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function DisableEnforceFocus() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/LazyFocusTrap.js b/docs/data/base/components/focus-trap/LazyFocusTrap.js index d15ad50e06..a50485968c 100644 --- a/docs/data/base/components/focus-trap/LazyFocusTrap.js +++ b/docs/data/base/components/focus-trap/LazyFocusTrap.js @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function LazyFocusTrap() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/LazyFocusTrap.tsx b/docs/data/base/components/focus-trap/LazyFocusTrap.tsx index d15ad50e06..a50485968c 100644 --- a/docs/data/base/components/focus-trap/LazyFocusTrap.tsx +++ b/docs/data/base/components/focus-trap/LazyFocusTrap.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function LazyFocusTrap() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/PortalFocusTrap.js b/docs/data/base/components/focus-trap/PortalFocusTrap.js index 71ec041606..95bb695353 100644 --- a/docs/data/base/components/focus-trap/PortalFocusTrap.js +++ b/docs/data/base/components/focus-trap/PortalFocusTrap.js @@ -1,7 +1,7 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { Portal } from '@base_ui/react/Portal'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { Portal } from '@base_ui/react/legacy/Portal'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function PortalFocusTrap() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/PortalFocusTrap.tsx b/docs/data/base/components/focus-trap/PortalFocusTrap.tsx index 9c070e56be..1f8127fa6f 100644 --- a/docs/data/base/components/focus-trap/PortalFocusTrap.tsx +++ b/docs/data/base/components/focus-trap/PortalFocusTrap.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import Box from '@mui/system/Box'; -import { Portal } from '@base_ui/react/Portal'; -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { Portal } from '@base_ui/react/legacy/Portal'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; export default function PortalFocusTrap() { const [open, setOpen] = React.useState(false); diff --git a/docs/data/base/components/focus-trap/focus-trap.md b/docs/data/base/components/focus-trap/focus-trap.md index 587d1744d6..788cb357e9 100644 --- a/docs/data/base/components/focus-trap/focus-trap.md +++ b/docs/data/base/components/focus-trap/focus-trap.md @@ -20,7 +20,7 @@ Focus Trap is a utility component that's useful when implementing an overlay suc ## Component ```jsx -import { FocusTrap } from '@base_ui/react/FocusTrap'; +import { FocusTrap } from '@base_ui/react/legacy/FocusTrap'; ``` Focus Trap wraps around the UI elements that should hold the user's focus. diff --git a/docs/data/base/components/form-control/BasicFormControl/css/index.js b/docs/data/base/components/form-control/BasicFormControl/css/index.js deleted file mode 100644 index 5742eafdf0..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/css/index.js +++ /dev/null @@ -1,144 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; -import { Input, inputClasses } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; -import clsx from 'clsx'; - -export default function BasicFormControl() { - return ( - - - - - - - - - ); -} - -const Label = React.forwardRef(({ className: classNameProp, children }, ref) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return

{children}

; - } - - const { error, required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return ( -

- {children} - {required ? ' *' : ''} -

- ); -}); - -const HelperText = React.forwardRef((props, ref) => { - const { className, ...other } = props; - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return null; - } - - const { required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return showRequiredError ? ( -

- This field is required. -

- ) : null; -}); - -HelperText.propTypes = { - className: PropTypes.string, -}; - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx b/docs/data/base/components/form-control/BasicFormControl/css/index.tsx deleted file mode 100644 index b03c4f1d25..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import * as React from 'react'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; -import { Input, inputClasses } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; -import clsx from 'clsx'; - -export default function BasicFormControl() { - return ( - - - - - - - - - ); -} - -const Label = React.forwardRef< - HTMLParagraphElement, - { className?: string; children?: React.ReactNode } ->(({ className: classNameProp, children }, ref) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return

{children}

; - } - - const { error, required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return ( -

- {children} - {required ? ' *' : ''} -

- ); -}); - -const HelperText = React.forwardRef( - (props, ref) => { - const { className, ...other } = props; - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return null; - } - - const { required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return showRequiredError ? ( -

- This field is required. -

- ) : null; - }, -); - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx.preview b/docs/data/base/components/form-control/BasicFormControl/css/index.tsx.preview deleted file mode 100644 index a09c70ab02..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/css/index.tsx.preview +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/docs/data/base/components/form-control/BasicFormControl/system/index.js b/docs/data/base/components/form-control/BasicFormControl/system/index.js deleted file mode 100644 index 5d879458d6..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/system/index.js +++ /dev/null @@ -1,122 +0,0 @@ -import * as React from 'react'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; -import { Input, inputClasses } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; -import clsx from 'clsx'; - -export default function BasicFormControl() { - return ( - - - - - - ); -} - -const StyledInput = styled(Input)( - ({ theme }) => ` - - .${inputClasses.input} { - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - outline: 0; - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - } -`, -); - -const Label = styled(({ children, className }) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return

{children}

; - } - - const { error, required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return ( -

- {children} - {required ? ' *' : ''} -

- ); -})` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - margin-bottom: 4px; - - &.invalid { - color: red; - } -`; - -const HelperText = styled((props) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return null; - } - - const { required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return showRequiredError ?

This field is required.

: null; -})` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; -`; - -const blue = { - 100: '#DAECFF', - 200: '#b6daff', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; diff --git a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx b/docs/data/base/components/form-control/BasicFormControl/system/index.tsx deleted file mode 100644 index 546863e267..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import * as React from 'react'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; -import { Input, inputClasses } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; -import clsx from 'clsx'; - -export default function BasicFormControl() { - return ( - - - - - - ); -} - -const StyledInput = styled(Input)( - ({ theme }) => ` - - .${inputClasses.input} { - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - outline: 0; - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - } -`, -); - -const Label = styled( - ({ children, className }: { children?: React.ReactNode; className?: string }) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return

{children}

; - } - - const { error, required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return ( -

- {children} - {required ? ' *' : ''} -

- ); - }, -)` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - margin-bottom: 4px; - - &.invalid { - color: red; - } -`; - -const HelperText = styled((props: {}) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return null; - } - - const { required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return showRequiredError ?

This field is required.

: null; -})` - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; -`; - -const blue = { - 100: '#DAECFF', - 200: '#b6daff', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; diff --git a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx.preview b/docs/data/base/components/form-control/BasicFormControl/system/index.tsx.preview deleted file mode 100644 index 3c87a88033..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/system/index.tsx.preview +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.js b/docs/data/base/components/form-control/BasicFormControl/tailwind/index.js deleted file mode 100644 index 7f0a074965..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.js +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; -import clsx from 'clsx'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function BasicFormControl() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - - - - -
- ); -} - -const Label = React.forwardRef(({ className: classNameProp, children }, ref) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return

{children}

; - } - - const { error, required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return ( -

- {children} - {required ? ' *' : ''} -

- ); -}); - -const HelperText = React.forwardRef((props, ref) => { - const { className, ...other } = props; - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return null; - } - - const { required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return showRequiredError ? ( -

- This field is required. -

- ) : null; -}); - -HelperText.propTypes = { - className: PropTypes.string, -}; diff --git a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx b/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx deleted file mode 100644 index 4a5605c133..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from 'react'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; -import clsx from 'clsx'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function BasicFormControl() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( -
- - - - - -
- ); -} - -const Label = React.forwardRef< - HTMLParagraphElement, - { className?: string; children?: React.ReactNode } ->(({ className: classNameProp, children }, ref) => { - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return

{children}

; - } - - const { error, required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return ( -

- {children} - {required ? ' *' : ''} -

- ); -}); - -const HelperText = React.forwardRef( - (props, ref) => { - const { className, ...other } = props; - const formControlContext = useFormControlContext(); - const [dirty, setDirty] = React.useState(false); - - React.useEffect(() => { - if (formControlContext?.filled) { - setDirty(true); - } - }, [formControlContext]); - - if (formControlContext === undefined) { - return null; - } - - const { required, filled } = formControlContext; - const showRequiredError = dirty && required && !filled; - - return showRequiredError ? ( -

- This field is required. -

- ) : null; - }, -); diff --git a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx.preview b/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx.preview deleted file mode 100644 index f5d74c426a..0000000000 --- a/docs/data/base/components/form-control/BasicFormControl/tailwind/index.tsx.preview +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file diff --git a/docs/data/base/components/form-control/FormControlFunctionChild.js b/docs/data/base/components/form-control/FormControlFunctionChild.js deleted file mode 100644 index 5632e22ab0..0000000000 --- a/docs/data/base/components/form-control/FormControlFunctionChild.js +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { FormControl } from '@base_ui/react/FormControl'; -import { Input, inputClasses } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -export default function FormControlFunctionChild() { - return ( - - {({ filled, focused }) => ( - - - {filled && !focused && } - - )} - - ); -} - -const StyledInput = styled(Input)( - ({ theme }) => ` - display: inline-block; - - .${inputClasses.input} { - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - outline: 0; - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - } - - &.filled .${inputClasses.input} { - box-shadow: 0 0 2px 2px rgba(125, 200, 0, 0.25); - } -`, -); - -const OkMark = styled('span')` - margin-left: 8px; - margin-top: 10px; - position: absolute; - color: rgb(125 200 0 / 1); -`; - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 600: '#0072E5', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; diff --git a/docs/data/base/components/form-control/FormControlFunctionChild.tsx b/docs/data/base/components/form-control/FormControlFunctionChild.tsx deleted file mode 100644 index 4f33df2001..0000000000 --- a/docs/data/base/components/form-control/FormControlFunctionChild.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react'; -import { FormControl, FormControlState } from '@base_ui/react/FormControl'; -import { Input, inputClasses } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -export default function FormControlFunctionChild() { - return ( - - {({ filled, focused }: FormControlState) => ( - - - {filled && !focused && } - - )} - - ); -} - -const StyledInput = styled(Input)( - ({ theme }) => ` - display: inline-block; - - .${inputClasses.input} { - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - outline: 0; - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - } - - &.filled .${inputClasses.input} { - box-shadow: 0 0 2px 2px rgba(125, 200, 0, 0.25); - } -`, -); - -const OkMark = styled('span')` - margin-left: 8px; - margin-top: 10px; - position: absolute; - color: rgb(125 200 0 / 1); -`; - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 600: '#0072E5', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; diff --git a/docs/data/base/components/form-control/FormControlFunctionChild.tsx.preview b/docs/data/base/components/form-control/FormControlFunctionChild.tsx.preview deleted file mode 100644 index d4531fd564..0000000000 --- a/docs/data/base/components/form-control/FormControlFunctionChild.tsx.preview +++ /dev/null @@ -1,8 +0,0 @@ - - {({ filled, focused }: FormControlState) => ( - - - {filled && !focused && } - - )} - \ No newline at end of file diff --git a/docs/data/base/components/form-control/UseFormControl.js b/docs/data/base/components/form-control/UseFormControl.js deleted file mode 100644 index 22b5ec6546..0000000000 --- a/docs/data/base/components/form-control/UseFormControl.js +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from 'react'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; - -export default function UseFormControl() { - return ( - - - - - ); -} - -function CustomInput() { - const formControlContext = useFormControlContext(); - - if (formControlContext === undefined) { - return null; - } - - const { value, required, onChange, disabled, onFocus, onBlur } = - formControlContext; - - return ( - - ); -} - -function ControlStateDisplay() { - const formControlContext = useFormControlContext(); - if (formControlContext === undefined) { - return null; - } - - const { filled, focused } = formControlContext; - - return ( -

- {filled ? 'filled' : 'empty'} |  - {focused ? 'focused' : 'not focused'} -

- ); -} diff --git a/docs/data/base/components/form-control/UseFormControl.tsx b/docs/data/base/components/form-control/UseFormControl.tsx deleted file mode 100644 index 8b70bc0280..0000000000 --- a/docs/data/base/components/form-control/UseFormControl.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from 'react'; -import { FormControl, useFormControlContext } from '@base_ui/react/FormControl'; - -export default function UseFormControl() { - return ( - - - - - ); -} - -function CustomInput() { - const formControlContext = useFormControlContext(); - - if (formControlContext === undefined) { - return null; - } - - const { value, required, onChange, disabled, onFocus, onBlur } = - formControlContext; - - return ( - - ); -} - -function ControlStateDisplay() { - const formControlContext = useFormControlContext(); - if (formControlContext === undefined) { - return null; - } - - const { filled, focused } = formControlContext; - - return ( -

- {filled ? 'filled' : 'empty'} |  - {focused ? 'focused' : 'not focused'} -

- ); -} diff --git a/docs/data/base/components/form-control/UseFormControl.tsx.preview b/docs/data/base/components/form-control/UseFormControl.tsx.preview deleted file mode 100644 index 611748df31..0000000000 --- a/docs/data/base/components/form-control/UseFormControl.tsx.preview +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/docs/data/base/components/form-control/form-control.md b/docs/data/base/components/form-control/form-control.md index 208b82479a..53b4b88841 100644 --- a/docs/data/base/components/form-control/form-control.md +++ b/docs/data/base/components/form-control/form-control.md @@ -13,98 +13,3 @@ githubLabel: 'component: FormControl' {{"component": "@mui/docs/ComponentLinkHeader", "design": false}} {{"component": "modules/components/ComponentPageTabs.js"}} - -## Introduction - -Form Control is a utility that wraps an input component with other associated components in order to make the state of the input available to those components. - -For instance, you may want to show an additional element asking the user to enter a value if the input is empty, or display a warning icon if the entered value is incorrect. - -## Component - -```jsx -import { FormControl } from '@base_ui/react/FormControl'; -``` - -Form Control wraps around the elements of a form that need access to the state of an ``. -For instance, if the form's **Submit** button needs to change states after the user enters information, then the component will be structured like this: - -```jsx - - - - -``` - -The following demo shows how to create and style a form that uses Form Control to wrap the elements of the form. -Note that it also uses the `useFormControlContext` hook in order to pass props to the custom Input—see the [Hook](#hook) section below for more details. - -{{"demo": "BasicFormControl"}} - -### Usage with TypeScript - -In TypeScript, you can specify the custom component type used in the `slots.root` as a generic parameter of the unstyled component. -This way, you can safely provide the custom root's props directly on the component: - -```tsx - slots={{ root: CustomComponent }} customProp /> -``` - -The same applies for props specific to custom primitive elements: - -```tsx - slots={{ root: 'button' }} onClick={() => {}} /> -``` - -## Hook - -```jsx -import { useFormControlContext } from '@base_ui/react/FormControl'; -``` - -The `useFormControlContext` hook reads the context provided by Form Control. -This hook lets you work with custom input components inside of the Form Control. -You can also use it to read the form control's state and react to its changes in a custom component. - -Hooks _do not_ support [slot props](#custom-structure), but they do support [customization props](#customization). - -:::info -Hooks give you the most room for customization, but require more work to implement. -With hooks, you can take full control over how your component is rendered, and define all the custom props and CSS classes you need. - -You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy). -::: - -The demo below shows how to integrate this hook with its component counterpart: - -- `CustomInput` is a wrapper around a native HTML `` that adds Form Control integration. -- `ControlStateDisplay` reads the state of the form control and displays it as text. - -{{"demo": "UseFormControl.js", "defaultCodeOpen": false}} - -Note that even though Form Control supports both controlled and uncontrolled-style APIs (that is it accepts `value` and `defaultValue` props), `useFormControlContext` returns only the controlled `value`. -This way, you don't have to implement both in your custom input—Form Control does this for you. - -:::info - -- A component is **controlled** when it's managed by its parent using props. -- A component is **uncontrolled** when it's managed by its own local state. - -Learn more about controlled and uncontrolled components in the [React documentation](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components). -::: - -## Customization - -:::info -The following features can be used with both components and hooks. -For the sake of simplicity, demos, and code snippets primarily feature components. -::: - -### Accessing the form control state - -You can access the state of the Form Control by providing a function as a child. -The state will be provided as a parameter to this function. - -The following demo shows how to access the state of the Form Control in an Input component nested inside: - -{{"demo": "FormControlFunctionChild.js"}} diff --git a/docs/data/base/components/input/InputAdornments.js b/docs/data/base/components/input/InputAdornments.js deleted file mode 100644 index 34cc2df7fa..0000000000 --- a/docs/data/base/components/input/InputAdornments.js +++ /dev/null @@ -1,186 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { Box, styled } from '@mui/system'; -import { Button } from '@base_ui/react/Button'; -import { Input as BaseInput, inputClasses } from '@base_ui/react/Input'; -import Visibility from '@mui/icons-material/Visibility'; -import VisibilityOff from '@mui/icons-material/VisibilityOff'; - -const Input = React.forwardRef(function CustomInput(props, ref) { - const { slots, ...other } = props; - return ( - - ); -}); - -Input.propTypes = { - /** - * The components used for each slot inside the InputBase. - * Either a string to use a HTML element or a component. - * @default {} - */ - slots: PropTypes.shape({ - input: PropTypes.elementType, - root: PropTypes.elementType, - textarea: PropTypes.elementType, - }), -}; - -export default function InputAdornments() { - const [values, setValues] = React.useState({ - amount: '', - password: '', - weight: '', - weightRange: '', - showPassword: false, - }); - - const handleChange = (prop) => (event) => { - setValues({ ...values, [prop]: event.target.value }); - }; - - const handleClickShowPassword = () => { - setValues({ - ...values, - showPassword: !values.showPassword, - }); - }; - - const handleMouseDownPassword = (event) => { - event.preventDefault(); - }; - - return ( - - kg} - /> - - - {values.showPassword ? ( - - ) : ( - - )} - - - } - /> - - ); -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputRoot = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - align-items: center; - justify-content: center; - - - &.${inputClasses.focused} { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); - -const InputElement = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - flex-grow: 1; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; -`, -); - -const IconButton = styled(Button)( - ({ theme }) => ` - display: inline-flex; - align-items: center; - justify-content: center; - border: none; - background: inherit; - cursor: pointer; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[700]}; - `, -); - -const InputAdornment = styled('div')` - margin: 8px; - display: inline-flex; - align-items: center; - justify-content: center; -`; diff --git a/docs/data/base/components/input/InputAdornments.tsx b/docs/data/base/components/input/InputAdornments.tsx deleted file mode 100644 index a511884ee8..0000000000 --- a/docs/data/base/components/input/InputAdornments.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import * as React from 'react'; -import { Box, styled } from '@mui/system'; -import { Button } from '@base_ui/react/Button'; -import { Input as BaseInput, InputProps, inputClasses } from '@base_ui/react/Input'; -import Visibility from '@mui/icons-material/Visibility'; -import VisibilityOff from '@mui/icons-material/VisibilityOff'; - -const Input = React.forwardRef(function CustomInput( - props: InputProps, - ref: React.ForwardedRef, -) { - const { slots, ...other } = props; - return ( - - ); -}); - -interface State { - amount: string; - password: string; - weight: string; - weightRange: string; - showPassword: boolean; -} - -export default function InputAdornments() { - const [values, setValues] = React.useState({ - amount: '', - password: '', - weight: '', - weightRange: '', - showPassword: false, - }); - - const handleChange = - (prop: keyof State) => (event: React.ChangeEvent) => { - setValues({ ...values, [prop]: event.target.value }); - }; - - const handleClickShowPassword = () => { - setValues({ - ...values, - showPassword: !values.showPassword, - }); - }; - - const handleMouseDownPassword = (event: React.MouseEvent) => { - event.preventDefault(); - }; - - return ( - - kg} - /> - - - {values.showPassword ? ( - - ) : ( - - )} - - - } - /> - - ); -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputRoot = styled('div')( - ({ theme }) => ` - font-family: 'IBM Plex Sans', sans-serif; - font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[500]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - display: flex; - align-items: center; - justify-content: center; - - - &.${inputClasses.focused} { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - &:hover { - border-color: ${blue[400]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); - -const InputElement = styled('input')( - ({ theme }) => ` - font-size: 0.875rem; - font-family: inherit; - font-weight: 400; - line-height: 1.5; - flex-grow: 1; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: inherit; - border: none; - border-radius: inherit; - padding: 8px 12px; - outline: 0; -`, -); - -const IconButton = styled(Button)( - ({ theme }) => ` - display: inline-flex; - align-items: center; - justify-content: center; - border: none; - background: inherit; - cursor: pointer; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[700]}; - `, -); - -const InputAdornment = styled('div')` - margin: 8px; - display: inline-flex; - align-items: center; - justify-content: center; -`; diff --git a/docs/data/base/components/input/InputMultiline.js b/docs/data/base/components/input/InputMultiline.js deleted file mode 100644 index 6129128376..0000000000 --- a/docs/data/base/components/input/InputMultiline.js +++ /dev/null @@ -1,83 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput(props, ref) { - return ( - - ); -}); - -export default function InputMultiline() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const RootDiv = styled('div')` - display: flex; - max-width: 100%; -`; - -const TextareaElement = styled('textarea', { - shouldForwardProp: (prop) => - !['ownerState', 'minRows', 'maxRows'].includes(prop.toString()), -})( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5rem; - padding: 8px 12px; - border-radius: 8px 8px 0 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/InputMultiline.tsx b/docs/data/base/components/input/InputMultiline.tsx deleted file mode 100644 index d810da4c46..0000000000 --- a/docs/data/base/components/input/InputMultiline.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput, InputProps } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput( - props: InputProps, - ref: React.ForwardedRef, -) { - return ( - - ); -}); - -export default function InputMultiline() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const RootDiv = styled('div')` - display: flex; - max-width: 100%; -`; - -const TextareaElement = styled('textarea', { - shouldForwardProp: (prop) => - !['ownerState', 'minRows', 'maxRows'].includes(prop.toString()), -})( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5rem; - padding: 8px 12px; - border-radius: 8px 8px 0 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/InputMultiline.tsx.preview b/docs/data/base/components/input/InputMultiline.tsx.preview deleted file mode 100644 index b454cb1b02..0000000000 --- a/docs/data/base/components/input/InputMultiline.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/InputMultilineAutosize.js b/docs/data/base/components/input/InputMultilineAutosize.js deleted file mode 100644 index fb74cecf51..0000000000 --- a/docs/data/base/components/input/InputMultilineAutosize.js +++ /dev/null @@ -1,81 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { TextareaAutosize } from '@base_ui/react/TextareaAutosize'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput(props, ref) { - return ( - - ); -}); - -export default function InputMultilineAutosize() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const RootDiv = styled('div')` - display: flex; - max-width: 100%; -`; - -const TextareaElement = styled(TextareaAutosize)( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5rem; - padding: 8px 12px; - border-radius: 8px 8px 0 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/InputMultilineAutosize.tsx b/docs/data/base/components/input/InputMultilineAutosize.tsx deleted file mode 100644 index 2d12528519..0000000000 --- a/docs/data/base/components/input/InputMultilineAutosize.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput, InputProps } from '@base_ui/react/Input'; -import { TextareaAutosize } from '@base_ui/react/TextareaAutosize'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput( - props: InputProps, - ref: React.ForwardedRef, -) { - return ( - - ); -}); - -export default function InputMultilineAutosize() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const RootDiv = styled('div')` - display: flex; - max-width: 100%; -`; - -const TextareaElement = styled(TextareaAutosize)( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5rem; - padding: 8px 12px; - border-radius: 8px 8px 0 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/InputMultilineAutosize.tsx.preview b/docs/data/base/components/input/InputMultilineAutosize.tsx.preview deleted file mode 100644 index b454cb1b02..0000000000 --- a/docs/data/base/components/input/InputMultilineAutosize.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/OTPInput.js b/docs/data/base/components/input/OTPInput.js deleted file mode 100644 index ac620348c7..0000000000 --- a/docs/data/base/components/input/OTPInput.js +++ /dev/null @@ -1,231 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { Box, styled } from '@mui/system'; - -function OTP({ separator, length, value, onChange }) { - const inputRefs = React.useRef(new Array(length).fill(null)); - - const focusInput = (targetIndex) => { - const targetInput = inputRefs.current[targetIndex]; - targetInput.focus(); - }; - - const selectInput = (targetIndex) => { - const targetInput = inputRefs.current[targetIndex]; - targetInput.select(); - }; - - const handleKeyDown = (event, currentIndex) => { - switch (event.key) { - case 'ArrowUp': - case 'ArrowDown': - case ' ': - event.preventDefault(); - break; - case 'ArrowLeft': - event.preventDefault(); - if (currentIndex > 0) { - focusInput(currentIndex - 1); - selectInput(currentIndex - 1); - } - break; - case 'ArrowRight': - event.preventDefault(); - if (currentIndex < length - 1) { - focusInput(currentIndex + 1); - selectInput(currentIndex + 1); - } - break; - case 'Delete': - event.preventDefault(); - onChange((prevOtp) => { - const otp = - prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1); - return otp; - }); - - break; - case 'Backspace': - event.preventDefault(); - if (currentIndex > 0) { - focusInput(currentIndex - 1); - selectInput(currentIndex - 1); - } - - onChange((prevOtp) => { - const otp = - prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1); - return otp; - }); - break; - - default: - break; - } - }; - - const handleChange = (event, currentIndex) => { - const currentValue = event.target.value; - let indexToEnter = 0; - - while (indexToEnter <= currentIndex) { - if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) { - indexToEnter += 1; - } else { - break; - } - } - onChange((prev) => { - const otpArray = prev.split(''); - const lastValue = currentValue[currentValue.length - 1]; - otpArray[indexToEnter] = lastValue; - return otpArray.join(''); - }); - if (currentValue !== '') { - if (currentIndex < length - 1) { - focusInput(currentIndex + 1); - } - } - }; - - const handleClick = (event, currentIndex) => { - selectInput(currentIndex); - }; - - const handlePaste = (event, currentIndex) => { - event.preventDefault(); - const clipboardData = event.clipboardData; - - // Check if there is text data in the clipboard - if (clipboardData.types.includes('text/plain')) { - let pastedText = clipboardData.getData('text/plain'); - pastedText = pastedText.substring(0, length).trim(); - let indexToEnter = 0; - - while (indexToEnter <= currentIndex) { - if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) { - indexToEnter += 1; - } else { - break; - } - } - - const otpArray = value.split(''); - - for (let i = indexToEnter; i < length; i += 1) { - const lastValue = pastedText[i - indexToEnter] ?? ' '; - otpArray[i] = lastValue; - } - - onChange(otpArray.join('')); - } - }; - - return ( - - {new Array(length).fill(null).map((_, index) => ( - - { - inputRefs.current[index] = ele; - }, - onKeyDown: (event) => handleKeyDown(event, index), - onChange: (event) => handleChange(event, index), - onClick: (event) => handleClick(event, index), - onPaste: (event) => handlePaste(event, index), - value: value[index] ?? '', - }, - }} - /> - {index === length - 1 ? null : separator} - - ))} - - ); -} - -OTP.propTypes = { - length: PropTypes.number.isRequired, - onChange: PropTypes.func.isRequired, - separator: PropTypes.node, - value: PropTypes.string.isRequired, -}; - -export default function OTPInput() { - const [otp, setOtp] = React.useState(''); - - return ( - - -} value={otp} onChange={setOtp} length={5} /> - Entered value: {otp} - - ); -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputElement = styled('input')( - ({ theme }) => ` - width: 40px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 0px; - border-radius: 8px; - text-align: center; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/OTPInput.tsx b/docs/data/base/components/input/OTPInput.tsx deleted file mode 100644 index 84dd43be1e..0000000000 --- a/docs/data/base/components/input/OTPInput.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { Box, styled } from '@mui/system'; - -function OTP({ - separator, - length, - value, - onChange, -}: { - separator: React.ReactNode; - length: number; - value: string; - onChange: React.Dispatch>; -}) { - const inputRefs = React.useRef(new Array(length).fill(null)); - - const focusInput = (targetIndex: number) => { - const targetInput = inputRefs.current[targetIndex]; - targetInput.focus(); - }; - - const selectInput = (targetIndex: number) => { - const targetInput = inputRefs.current[targetIndex]; - targetInput.select(); - }; - - const handleKeyDown = ( - event: React.KeyboardEvent, - currentIndex: number, - ) => { - switch (event.key) { - case 'ArrowUp': - case 'ArrowDown': - case ' ': - event.preventDefault(); - break; - case 'ArrowLeft': - event.preventDefault(); - if (currentIndex > 0) { - focusInput(currentIndex - 1); - selectInput(currentIndex - 1); - } - break; - case 'ArrowRight': - event.preventDefault(); - if (currentIndex < length - 1) { - focusInput(currentIndex + 1); - selectInput(currentIndex + 1); - } - break; - case 'Delete': - event.preventDefault(); - onChange((prevOtp) => { - const otp = - prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1); - return otp; - }); - - break; - case 'Backspace': - event.preventDefault(); - if (currentIndex > 0) { - focusInput(currentIndex - 1); - selectInput(currentIndex - 1); - } - - onChange((prevOtp) => { - const otp = - prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1); - return otp; - }); - break; - - default: - break; - } - }; - - const handleChange = ( - event: React.ChangeEvent, - currentIndex: number, - ) => { - const currentValue = event.target.value; - let indexToEnter = 0; - - while (indexToEnter <= currentIndex) { - if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) { - indexToEnter += 1; - } else { - break; - } - } - onChange((prev) => { - const otpArray = prev.split(''); - const lastValue = currentValue[currentValue.length - 1]; - otpArray[indexToEnter] = lastValue; - return otpArray.join(''); - }); - if (currentValue !== '') { - if (currentIndex < length - 1) { - focusInput(currentIndex + 1); - } - } - }; - - const handleClick = ( - event: React.MouseEvent, - currentIndex: number, - ) => { - selectInput(currentIndex); - }; - - const handlePaste = ( - event: React.ClipboardEvent, - currentIndex: number, - ) => { - event.preventDefault(); - const clipboardData = event.clipboardData; - - // Check if there is text data in the clipboard - if (clipboardData.types.includes('text/plain')) { - let pastedText = clipboardData.getData('text/plain'); - pastedText = pastedText.substring(0, length).trim(); - let indexToEnter = 0; - - while (indexToEnter <= currentIndex) { - if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) { - indexToEnter += 1; - } else { - break; - } - } - - const otpArray = value.split(''); - - for (let i = indexToEnter; i < length; i += 1) { - const lastValue = pastedText[i - indexToEnter] ?? ' '; - otpArray[i] = lastValue; - } - - onChange(otpArray.join('')); - } - }; - - return ( - - {new Array(length).fill(null).map((_, index) => ( - - { - inputRefs.current[index] = ele!; - }, - onKeyDown: (event) => handleKeyDown(event, index), - onChange: (event) => handleChange(event, index), - onClick: (event) => handleClick(event, index), - onPaste: (event) => handlePaste(event, index), - value: value[index] ?? '', - }, - }} - /> - {index === length - 1 ? null : separator} - - ))} - - ); -} - -export default function OTPInput() { - const [otp, setOtp] = React.useState(''); - - return ( - - -} value={otp} onChange={setOtp} length={5} /> - Entered value: {otp} - - ); -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputElement = styled('input')( - ({ theme }) => ` - width: 40px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 0px; - border-radius: 8px; - text-align: center; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/OTPInput.tsx.preview b/docs/data/base/components/input/OTPInput.tsx.preview deleted file mode 100644 index 9e5f02b247..0000000000 --- a/docs/data/base/components/input/OTPInput.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ --} value={otp} onChange={setOtp} length={5} /> -Entered value: {otp} \ No newline at end of file diff --git a/docs/data/base/components/input/UnstyledInputBasic/css/index.js b/docs/data/base/components/input/UnstyledInputBasic/css/index.js deleted file mode 100644 index d84c04bea7..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/css/index.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; - -export default function UnstyledInputBasic() { - return ( - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx b/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx deleted file mode 100644 index d84c04bea7..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; - -export default function UnstyledInputBasic() { - return ( - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx.preview b/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx.preview deleted file mode 100644 index b4003b693f..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/css/index.tsx.preview +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/docs/data/base/components/input/UnstyledInputBasic/system/index.js b/docs/data/base/components/input/UnstyledInputBasic/system/index.js deleted file mode 100644 index c68cd2475c..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/system/index.js +++ /dev/null @@ -1,62 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput(props, ref) { - return ; -}); - -export default function UnstyledInputBasic() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputElement = styled('input')( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx b/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx deleted file mode 100644 index d08ed64836..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput, InputProps } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput( - props: InputProps, - ref: React.ForwardedRef, -) { - return ; -}); - -export default function UnstyledInputBasic() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputElement = styled('input')( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx.preview b/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx.preview deleted file mode 100644 index 12d25a9fa9..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/system/index.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.js b/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.js deleted file mode 100644 index cd8af58451..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from 'react'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledInputBasic() { - // Replace this with your app logic for determining dark modes - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx b/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx deleted file mode 100644 index cd8af58451..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from 'react'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -export default function UnstyledInputBasic() { - // Replace this with your app logic for determining dark modes - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx.preview b/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx.preview deleted file mode 100644 index 2431f649ca..0000000000 --- a/docs/data/base/components/input/UnstyledInputBasic/tailwind/index.tsx.preview +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.js b/docs/data/base/components/input/UnstyledInputIntroduction/css/index.js deleted file mode 100644 index 22aa3be103..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.js +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; - -export default function UnstyledInputIntroduction() { - return ( - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx b/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx deleted file mode 100644 index 22aa3be103..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react'; -import { Input } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; - -export default function UnstyledInputIntroduction() { - return ( - - - - - ); -} - -const cyan = { - 50: '#E9F8FC', - 100: '#BDEBF4', - 200: '#99D8E5', - 300: '#66BACC', - 400: '#1F94AD', - 500: '#0D5463', - 600: '#094855', - 700: '#063C47', - 800: '#043039', - 900: '#022127', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Styles() { - // Replace this with your app logic for determining dark mode - const isDarkMode = useIsDarkMode(); - - return ( - - ); -} diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx.preview b/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx.preview deleted file mode 100644 index 7ae3c657ea..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/css/index.tsx.preview +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.js b/docs/data/base/components/input/UnstyledInputIntroduction/system/index.js deleted file mode 100644 index 559418a247..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.js +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput(props, ref) { - return ; -}); - -export default function UnstyledInputIntroduction() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#b6daff', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputElement = styled('input')( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx b/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx deleted file mode 100644 index ac2e53fcd0..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { styled } from '@mui/system'; - -const Input = React.forwardRef(function CustomInput( - props: React.InputHTMLAttributes, - ref: React.ForwardedRef, -) { - return ; -}); - -export default function UnstyledInputIntroduction() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#b6daff', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 900: '#003A75', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const InputElement = styled('input')( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx.preview b/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx.preview deleted file mode 100644 index 12d25a9fa9..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/system/index.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.js b/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.js deleted file mode 100644 index df6a738e74..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { Input as BaseInput } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; -import clsx from 'clsx'; - -export default function UnstyledInputIntroduction() { - return ; -} - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -const resolveSlotProps = (fn, args) => (typeof fn === 'function' ? fn(args) : fn); - -const Input = React.forwardRef((props, ref) => { - // Replace this with your app logic for determining dark modes - const isDarkMode = useIsDarkMode(); - - return ( - { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.input, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'w-80 text-sm font-normal font-sans leading-5 px-3 py-2 rounded-lg shadow-md shadow-slate-100 dark:shadow-slate-900 focus:shadow-outline-purple dark:focus:shadow-outline-purple dark:outline-purple-600 focus:shadow-lg border border-solid border-slate-300 hover:border-purple-500 dark:hover:border-purple-500 focus:border-purple-500 dark:focus:border-purple-600 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-300 focus-visible:outline-0', - resolvedSlotProps?.className, - ), - }; - }, - }} - /> - ); -}); - -Input.propTypes = { - /** - * Class name applied to the root element. - */ - className: PropTypes.string, - /** - * The props used for each slot inside the Input. - * @default {} - */ - slotProps: PropTypes.shape({ - input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), -}; diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx b/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx deleted file mode 100644 index ee54d299b2..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import * as React from 'react'; -import { Input as BaseInput, InputProps } from '@base_ui/react/Input'; -import { useTheme } from '@mui/system'; -import clsx from 'clsx'; - -export default function UnstyledInputIntroduction() { - return ; -} - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -const resolveSlotProps = (fn: any, args: any) => - typeof fn === 'function' ? fn(args) : fn; - -const Input = React.forwardRef((props, ref) => { - // Replace this with your app logic for determining dark modes - const isDarkMode = useIsDarkMode(); - - return ( - { - const resolvedSlotProps = resolveSlotProps( - props.slotProps?.input, - ownerState, - ); - return { - ...resolvedSlotProps, - className: clsx( - 'w-80 text-sm font-normal font-sans leading-5 px-3 py-2 rounded-lg shadow-md shadow-slate-100 dark:shadow-slate-900 focus:shadow-outline-purple dark:focus:shadow-outline-purple dark:outline-purple-600 focus:shadow-lg border border-solid border-slate-300 hover:border-purple-500 dark:hover:border-purple-500 focus:border-purple-500 dark:focus:border-purple-600 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-300 focus-visible:outline-0', - resolvedSlotProps?.className, - ), - }; - }, - }} - /> - ); -}); diff --git a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx.preview b/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx.preview deleted file mode 100644 index 12d25a9fa9..0000000000 --- a/docs/data/base/components/input/UnstyledInputIntroduction/tailwind/index.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/UseInput.js b/docs/data/base/components/input/UseInput.js deleted file mode 100644 index eeac15d8e4..0000000000 --- a/docs/data/base/components/input/UseInput.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react'; -import { useInput } from '@base_ui/react/useInput'; -import { styled } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; - -const CustomInput = React.forwardRef(function CustomInput(props, ref) { - const { getRootProps, getInputProps } = useInput(props); - - const inputProps = getInputProps(); - - // Make sure that both the forwarded ref and the ref returned from the getInputProps are applied on the input element - inputProps.ref = useForkRef(inputProps.ref, ref); - - return ( -
- -
- ); -}); - -export default function UseInput() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const StyledInputElement = styled('input')( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/UseInput.tsx b/docs/data/base/components/input/UseInput.tsx deleted file mode 100644 index 9e8a8f4ffc..0000000000 --- a/docs/data/base/components/input/UseInput.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react'; -import { useInput } from '@base_ui/react/useInput'; -import { styled } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; - -const CustomInput = React.forwardRef(function CustomInput( - props: React.InputHTMLAttributes, - ref: React.ForwardedRef, -) { - const { getRootProps, getInputProps } = useInput(props); - - const inputProps = getInputProps(); - - // Make sure that both the forwarded ref and the ref returned from the getInputProps are applied on the input element - inputProps.ref = useForkRef(inputProps.ref, ref); - - return ( -
- -
- ); -}); - -export default function UseInput() { - return ; -} - -const blue = { - 100: '#DAECFF', - 200: '#80BFFF', - 400: '#3399FF', - 500: '#007FFF', - 600: '#0072E5', - 700: '#0059B2', -}; - -const grey = { - 50: '#F3F6F9', - 100: '#E5EAF2', - 200: '#DAE2ED', - 300: '#C7D0DD', - 400: '#B0B8C4', - 500: '#9DA8B7', - 600: '#6B7A90', - 700: '#434D5B', - 800: '#303740', - 900: '#1C2025', -}; - -const StyledInputElement = styled('input')( - ({ theme }) => ` - width: 320px; - font-family: 'IBM Plex Sans', sans-serif; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - padding: 8px 12px; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${ - theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; - - &:hover { - border-color: ${blue[400]}; - } - - &:focus { - border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; - } - - // firefox - &:focus-visible { - outline: 0; - } -`, -); diff --git a/docs/data/base/components/input/UseInput.tsx.preview b/docs/data/base/components/input/UseInput.tsx.preview deleted file mode 100644 index bd1c548432..0000000000 --- a/docs/data/base/components/input/UseInput.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/base/components/input/input.md b/docs/data/base/components/input/input.md deleted file mode 100644 index 4bd7d81492..0000000000 --- a/docs/data/base/components/input/input.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -productId: base-ui -title: React Input component and hook -components: Input -hooks: useInput -githubLabel: 'component: input' ---- - -# Input - -

The Input component provides users with a field to enter and edit text.

- -{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} - -{{"component": "modules/components/ComponentPageTabs.js"}} - -## Introduction - -An input is a UI element that accepts text data from the user. -The Input component replaces the native HTML `` tag, and offers expanded customization and accessibility features. -It can also be transformed into a `