diff --git a/.circleci/config.yml b/.circleci/config.yml index 90fc210cbc..45575418bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,12 +221,6 @@ jobs: - checkout - install_js: react-version: << parameters.react-version >> - - run: - name: Transpile TypeScript demos - command: pnpm docs:typescript:formatted - - run: - name: '`pnpm docs:typescript:formatted` changes committed?' - command: git add -A && git diff --exit-code --staged - run: name: Tests TypeScript definitions command: pnpm typescript diff --git a/.eslintignore b/.eslintignore index 9e721f30f5..b31898281c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,7 +2,6 @@ .nyc_output /coverage /docs/.next -/docs/data/**/*.js /docs/export /docs/src/app/(private)/playground/ /packages/react/**/*.min.* diff --git a/.eslintrc.js b/.eslintrc.js index d00a429c7c..e0cc0660b8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -87,17 +87,5 @@ module.exports = { '@typescript-eslint/no-use-before-define': 'off', }, }, - { - files: ['docs/data/**/*{.tsx,.js}'], - excludedFiles: [ - 'docs/data/**/css/*{.tsx,.js}', - 'docs/data/**/css-modules/*{.tsx,.js}', - 'docs/data/**/system/*{.tsx,.js}', - 'docs/data/**/tailwind/*{.tsx,.js}', - ], - rules: { - 'filenames/match-exported': ['error'], - }, - }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a2548d431c..59dffcdd0f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ * @atomiks @michaldudak -/docs/data/ @atomiks @colmtuite @michaldudak +/docs/src/ @atomiks @colmtuite @michaldudak /packages/react/ @atomiks @colmtuite @michaldudak /scripts/ @michaldudak diff --git a/docs/data/components/accordion/AccordionIntroduction.js b/docs/data/components/accordion/AccordionIntroduction.js deleted file mode 100644 index 7c4583c34e..0000000000 --- a/docs/data/components/accordion/AccordionIntroduction.js +++ /dev/null @@ -1,59 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Accordion } from '@base-ui-components/react/accordion'; -import classes from './styles.module.css'; - -export default function AccordionIntroduction() { - return ( - - - - - Trigger 1 - - - - - This is the contents of Accordion.Panel 1 - - - - - - Trigger 2 - - - - - This is the contents of Accordion.Panel 2 - - - - - - Trigger 3 - - - - - This is the contents of Accordion.Panel 3 - - - - ); -} - -function ExpandMoreIcon(props) { - return ( - - - - ); -} diff --git a/docs/data/components/accordion/AccordionIntroduction.tsx b/docs/data/components/accordion/AccordionIntroduction.tsx deleted file mode 100644 index c90b44b8a5..0000000000 --- a/docs/data/components/accordion/AccordionIntroduction.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Accordion } from '@base-ui-components/react/accordion'; -import classes from './styles.module.css'; - -export default function AccordionIntroduction() { - return ( - - - - - Trigger 1 - - - - - This is the contents of Accordion.Panel 1 - - - - - - Trigger 2 - - - - - This is the contents of Accordion.Panel 2 - - - - - - Trigger 3 - - - - - This is the contents of Accordion.Panel 3 - - - - ); -} - -function ExpandMoreIcon(props: React.SVGProps) { - return ( - - - - ); -} diff --git a/docs/data/components/accordion/accordion.mdx b/docs/data/components/accordion/accordion.mdx deleted file mode 100644 index 093aa73cde..0000000000 --- a/docs/data/components/accordion/accordion.mdx +++ /dev/null @@ -1,350 +0,0 @@ ---- -productId: base-ui -title: React Accordion components -components: AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionPanel -githubLabel: 'component: accordion' -waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/ -packageName: '@base-ui-components/react' ---- - -# Accordion - - - - - -## Introduction - - - -## Installation - - - -## Anatomy - -Accordions are implemented using a collection of related components: - -- `` is a top-level component that wraps the other components. -- `` is a component that wraps each section of content and it's associated `Trigger` -- `` is a button that toggles the open state of its associated `Item` -- `` is a heading (`h3` by default) that wraps the `Trigger` -- `` is the element that contains content in a `Item` - -```jsx - - - - Toggle one - - Panel one content - - - - Toggle two - - Panel two content - - -``` - -## Value - -Each `Accordion.Item` is represented by a value, which by default is its zero-based index by DOM position. -The first `Item` has an implicit `value` of `0`, the second one `Item` a `value` of `1`, and so on. - -The open state of the accordion is represented an array holding the `value`s of all open `Item`s. - -You can optionally specify a custom `value` prop on `Item`: - -```jsx - - - - Toggle one - - Panel one content - - - - Toggle two - - Panel two content - - -``` - -### Default value - -When uncontrolled, use the `defaultValue` prop to set the initial state of the accordion: - -```jsx - - - - Toggle one - - Panel one content - - - - Toggle two - - Panel two content - -; - -// with custom `value`s - - - - Toggle one - - Panel one content - - - - Toggle two - - Panel two content - -; -``` - -### Controlled - -When controlled, pass the `value` and `onValueChange` props to `Accordion.Root`: - -```jsx -const [value, setValue] = React.useState(['a']); - -return ( - - - - Toggle one - - Panel one content - - - - Toggle two - - Panel two content - - -); -``` - -## Customization - -### Only one `Item` open at a time - -By default, all accordion items can be opened at the same time. Use the `openMultiple` prop to only allow one open item at a time: - -```jsx -{/* subcomponents */} -``` - -### At least one `Item` remains open - -Use controlled mode to always keep one `Item` open: - -```jsx -const [value, setValue] = React.useState([0]); - -const handleValueChange = (newValue) => { - if (newValue.length > 0) { - setValue(newValue); - } -}; - -return ( - - {/* subcomponents */} - -); -``` - -## Horizontal - -Use the `orientation` prop to configure a horizontal accordion. In a horizontal accordion, focus will move between `Accordion.Trigger`s with the Right Arrow and Left Arrow keys, instead of Down/Up. - -```jsx -{/* subcomponents */} -``` - -## RTL - -To change direction for right-to-left languages, place the accordion in [`DirectionProvider`](/components/react-direction-provider) with the `direction="rtl"` prop to configure RTL behavior, and set the `dir` attribute accordingly in your HTML to affect styling: - -```jsx - - - - {/* Subcomponents */} - - - -``` - -When RTL, keyboard actions for horizontal accordions are reversed - Left Arrow moves focus to the next trigger and Right Arrow moves focus to the previous trigger. - -## Hidden state - -``s are unmounted from the DOM by default when they are closed. The `keepMounted` prop can be used to keep them mounted when closed, and instead rely on the `hidden` attribute to hide the content: - -```jsx -{/* accordion items */} -``` - -Alternatively `keepMounted` can be passed to `Accordion.Panel`s directly to enable this for only one `Item` instead of the whole accordion. - -## Improving searchability of hidden content - - - This is [not yet - supported](https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value) in Safari and - Firefox as of August 2024 and will fall back to the default `hidden` behavior. - - -Content hidden by `Accordion.Panel` components can be made accessible only to a browser's find-in-page functionality with the `hiddenUntilFound` prop to improve searchability: - -```js -{/* subcomponents */} -``` - -When `hiddenUntilFound` is used, `Accordion.Panel`s remain mounted even when closed, overriding the `keepMounted` prop on the root or the panel. - -Alternatively `hiddenUntilFound` can be passed to `Accordion.Panel`s directly to enable this for only one `Item` instead of the whole accordion. - -We recommend using [CSS animations](#css-animations) for animated accordions that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a [CSS transition](#css-transitions) applied. - -This relies on the HTML `hidden="until-found"` attribute which only has [partial browser support](https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value) as of August 2024, but automatically falls back to the default `hidden` state in unsupported browsers. - -## Animations - -Accordion uses [`Collapsible`](/components/react-collapsible) internally, and can be animated in a [similar way](/components/react-collapsible#animations). - -Three states are available as data attributes to animate the `Accordion.Panel`: - -- `[data-open]` - `open` state is `true`. -- `[data-starting-style]` - the `hidden` attribute was just removed from the DOM and the panel element participates in page layout. The `data-starting-style` attribute will be removed 1 animation frame later. -- `[data-ending-style]` - the panel element is in the process of being hidden from the DOM, but is still mounted. - -The component can be animate when opening or closing using either: - -- CSS animations -- CSS transitions -- JavaScript animations - -### Styling - -The `Accordion.Panel` element receives the following CSS variables about its dimensions, which can be used to style animations or transitions: - -- `--accordion-panel-height`: Specifies the height of the `Panel`. -- `--accordion-panel-width`: Specifies the width of the `Panel`. - -### CSS Animations - -CSS animations can be used with two declarations: - -```css -.AccordionPanel { - overflow: hidden; - animation: slideUp 300ms ease-in; -} - -.AccordionPanel[data-open] { - animation: slideDown 300ms ease-out; -} - -@keyframes slideDown { - from { - height: 0; - } - to { - height: var(--accordion-panel-height); - } -} - -@keyframes slideUp { - from { - height: var(--accordion-panel-height); - } - to { - height: 0; - } -} -``` - -### CSS Transitions - -When using CSS transitions, styles for the `Panel` must be applied to 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-starting-style]` - -```css -.AccordionPanel { - overflow: hidden; - /* The final styles once closed/exited */ - height: 0; - transition: height 300ms ease-in; -} - -/* The final styles once opened/entered */ -.AccordionPanel[data-open] { - height: var(--accordion-panel-height); - transition: height 300ms ease-out; -} - -/* The initial styles when opening/entering */ -.AccordionPanel[data-starting-style] { - height: 0; -} -``` - -### JavaScript Animations - -Use the `keepMounted` prop lets an external library control the mounting, for example `framer-motion`: - -```js -function App() { - const [value, setValue] = useState([0]); - return ( - - - - Toggle - - - } - > - This is the content - - - {/* more accordion items */} - - ); -} -``` diff --git a/docs/data/components/accordion/styles.module.css b/docs/data/components/accordion/styles.module.css deleted file mode 100644 index c78f0edf30..0000000000 --- a/docs/data/components/accordion/styles.module.css +++ /dev/null @@ -1,80 +0,0 @@ -.root { - --shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), - 0px 1px 3px 0px rgba(0, 0, 0, 0.12); - - font-family: system-ui, sans-serif; - box-shadow: var(--shadow); - background-color: rgba(0, 0, 0, 0.12); - border-radius: 0.3rem; - min-width: 22rem; -} - -.item { - width: 100%; - position: relative; - background-color: #fff; - color: rgba(0, 0, 0, 0.87); -} - -.item:not(:first-of-type) { - margin-top: 1px; -} - -.item:first-of-type { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; -} - -.item:last-of-type { - border-bottom-left-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; -} - -.header { - margin: 0; - border-radius: inherit; -} - -.header:hover { - cursor: pointer; -} - -.trigger { - appearance: none; - background-color: transparent; - border: 0; - border-radius: inherit; - color: inherit; - padding: 1rem; - position: relative; - width: 100%; - display: flex; - flex-flow: row nowrap; - align-items: center; - font-size: 1rem; - line-height: 1.5; -} - -.trigger:hover { - cursor: pointer; - background-color: rgba(0, 0, 0, 0.12); -} - -.trigger:focus-visible { - outline: 2px solid black; - z-index: 1; -} - -.trigger .triggerText { - font-size: 1rem; - line-height: 1.5; - margin: 12px auto 12px 0; -} - -.trigger[data-panel-open] svg { - transform: rotate(180deg); -} - -.panel { - padding: 1rem; -} diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/index.js b/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/index.js deleted file mode 100644 index 71e12ce398..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/index.js +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog } from '@base-ui-components/react/alert-dialog'; -import classes from './styles.module.css'; - -export default function AlertDialogIntroduction() { - return ( - - - Subscribe - - - - - Subscribe - - Are you sure you want to subscribe? - -
- Yes - No -
-
-
-
- ); -} diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/index.tsx b/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/index.tsx deleted file mode 100644 index 71e12ce398..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog } from '@base-ui-components/react/alert-dialog'; -import classes from './styles.module.css'; - -export default function AlertDialogIntroduction() { - return ( - - - Subscribe - - - - - Subscribe - - Are you sure you want to subscribe? - -
- Yes - No -
-
-
-
- ); -} diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/styles.module.css b/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/styles.module.css deleted file mode 100644 index 20cb749dfb..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/css-modules/styles.module.css +++ /dev/null @@ -1,93 +0,0 @@ -.popup { - background: var(--color-gray-50); - border: 1px solid var(--color-gray-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; - transform: translate(-50%, -50%); - padding: 16px; - z-index: 2100; - - :global(.dark) & { - color: var(--color-gray-900); - border-color: var(--color-gray-700); - } -} - -.trigger { - background-color: var(--color-gray-900); - color: var(--color-gray-50); - padding: 8px 16px; - border-radius: 4px; - border: none; - font-family: - IBM Plex Sans, - sans-serif; - - &:hover { - background-color: var(--color-gray-700); - - :global(.dark) & { - background-color: var(--color-gray-200); - } - } - - :global(.dark) & { - background-color: var(--color-gray-50); - color: var(--color-gray-900); - } -} - -.close { - background-color: transparent; - border: 1px solid var(--color-gray-500); - color: var(--color-gray-900); - padding: 8px 16px; - border-radius: 4px; - font-family: - IBM Plex Sans, - sans-serif; - min-width: 80px; - - &:hover { - background-color: var(--color-gray-200); - - :global(.dark) & { - background-color: var(--color-gray-700); - } - } - - :global(.dark) & { - border-color: var(--color-gray-300); - color: var(--color-gray-50); - } -} - -.controls { - display: flex; - flex-direction: row-reverse; - background: var(--color-gray-100); - gap: 8px; - padding: 16px; - margin: 32px -16px -16px; - - :global(.dark) & { - background: var(--color-gray-800); - } -} - -.title { - font-size: 1.25rem; -} - -.backdrop { - background: rgba(0, 0, 0, 0.35); - position: fixed; - inset: 0; - backdrop-filter: blur(4px); - z-index: 2000; -} diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.js b/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.js deleted file mode 100644 index 39b6f77bf7..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.js +++ /dev/null @@ -1,107 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog } from '@base-ui-components/react/alert-dialog'; -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/components/alert-dialog/AlertDialogIntroduction/system/index.tsx b/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.tsx deleted file mode 100644 index 39b6f77bf7..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog } from '@base-ui-components/react/alert-dialog'; -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/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview b/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview deleted file mode 100644 index bf84491897..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/system/index.tsx.preview +++ /dev/null @@ -1,14 +0,0 @@ - - Subscribe - - - - Subscribe - Are you sure you want to subscribe? - - Yes - No - - - - \ No newline at end of file diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js b/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js deleted file mode 100644 index 13c8503946..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.js +++ /dev/null @@ -1,75 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog } from '@base-ui-components/react/alert-dialog'; - -export default function UnstyledDialogIntroduction() { - return ( - - Subscribe - - - - Subscribe - Are you sure you want to subscribe? - - Yes - No - - - - - ); -} - -function TriggerButton(props) { - const className = ` - bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 - py-2 px-4 rounded min-w-[80px] border-none font-sans - hover:bg-gray-700 dark:hover:bg-gray-200`; - - return ; -} - -function Popup(props) { - const className = ` - bg-gray-50 dark:bg-gray-900 border-[1px] border-solid border-gray-100 dark:border-gray-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-gray-500 dark:border-gray-300 - text-gray-900 dark:text-gray-50 py-2 px-4 rounded font-sans min-w-[80px] - hover:bg-gray-200 dark:hover:bg-gray-700`; - - return ; -} - -function Title(props) { - return ; -} - -function Description(props) { - return ; -} - -function Backdrop(props) { - return ( - - ); -} diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx b/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx deleted file mode 100644 index 688fe186ea..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog } from '@base-ui-components/react/alert-dialog'; - -export default function UnstyledDialogIntroduction() { - return ( - - Subscribe - - - - Subscribe - Are you sure you want to subscribe? - - Yes - No - - - - - ); -} - -function TriggerButton(props: AlertDialog.Trigger.Props) { - const className = ` - bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 - py-2 px-4 rounded min-w-[80px] border-none font-sans - hover:bg-gray-700 dark:hover:bg-gray-200`; - - return ; -} - -function Popup(props: AlertDialog.Popup.Props) { - const className = ` - bg-gray-50 dark:bg-gray-900 border-[1px] border-solid border-gray-100 dark:border-gray-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.Close.Props) { - const className = ` - bg-transparent border-[1px] border-solid border-gray-500 dark:border-gray-300 - text-gray-900 dark:text-gray-50 py-2 px-4 rounded font-sans min-w-[80px] - hover:bg-gray-200 dark:hover:bg-gray-700`; - - return ; -} - -function Title(props: AlertDialog.Title.Props) { - return ; -} - -function Description(props: AlertDialog.Description.Props) { - return ; -} - -function Backdrop(props: AlertDialog.Backdrop.Props) { - return ( - - ); -} diff --git a/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview b/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview deleted file mode 100644 index bf84491897..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogIntroduction/tailwind/index.tsx.preview +++ /dev/null @@ -1,14 +0,0 @@ - - Subscribe - - - - Subscribe - Are you sure you want to subscribe? - - Yes - No - - - - \ No newline at end of file diff --git a/docs/data/components/alert-dialog/AlertDialogWithTransitions.js b/docs/data/components/alert-dialog/AlertDialogWithTransitions.js deleted file mode 100644 index 13f1a75f55..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogWithTransitions.js +++ /dev/null @@ -1,138 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog as BaseAlertDialog } from '@base-ui-components/react/alert-dialog'; -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-open] { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - transition-timing-function: ease-out; - } - - &[data-starting-style] { - 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-open] { - backdrop-filter: blur(6px); - opacity: 1; - transition-timing-function: ease-out; - } - - &[data-starting-style] { - 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/components/alert-dialog/AlertDialogWithTransitions.tsx b/docs/data/components/alert-dialog/AlertDialogWithTransitions.tsx deleted file mode 100644 index 13f1a75f55..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogWithTransitions.tsx +++ /dev/null @@ -1,138 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog as BaseAlertDialog } from '@base-ui-components/react/alert-dialog'; -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-open] { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - transition-timing-function: ease-out; - } - - &[data-starting-style] { - 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-open] { - backdrop-filter: blur(6px); - opacity: 1; - transition-timing-function: ease-out; - } - - &[data-starting-style] { - 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/components/alert-dialog/AlertDialogWithTransitions.tsx.preview b/docs/data/components/alert-dialog/AlertDialogWithTransitions.tsx.preview deleted file mode 100644 index 3d78f9706d..0000000000 --- a/docs/data/components/alert-dialog/AlertDialogWithTransitions.tsx.preview +++ /dev/null @@ -1,15 +0,0 @@ - - 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/components/alert-dialog/NestedAlertDialogs.js b/docs/data/components/alert-dialog/NestedAlertDialogs.js deleted file mode 100644 index 8cc8010234..0000000000 --- a/docs/data/components/alert-dialog/NestedAlertDialogs.js +++ /dev/null @@ -1,166 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog as BaseAlertDialog } from '@base-ui-components/react/alert-dialog'; -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-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-open] { - backdrop-filter: blur(6px); - opacity: 1; - transition-timing-function: ease-out; - } - - &[data-starting-style] { - 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/components/alert-dialog/NestedAlertDialogs.tsx b/docs/data/components/alert-dialog/NestedAlertDialogs.tsx deleted file mode 100644 index 8cc8010234..0000000000 --- a/docs/data/components/alert-dialog/NestedAlertDialogs.tsx +++ /dev/null @@ -1,166 +0,0 @@ -'use client'; -import * as React from 'react'; -import { AlertDialog as BaseAlertDialog } from '@base-ui-components/react/alert-dialog'; -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-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-open] { - backdrop-filter: blur(6px); - opacity: 1; - transition-timing-function: ease-out; - } - - &[data-starting-style] { - 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/components/alert-dialog/alert-dialog.mdx b/docs/data/components/alert-dialog/alert-dialog.mdx deleted file mode 100644 index 3fc21eb345..0000000000 --- a/docs/data/components/alert-dialog/alert-dialog.mdx +++ /dev/null @@ -1,266 +0,0 @@ ---- -productId: base-ui -title: React Alert Dialog component -description: Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks. -components: AlertDialogBackdrop, AlertDialogClose, AlertDialogDescription, AlertDialogPopup, AlertDialogPortal, AlertDialogRoot, AlertDialogTitle, AlertDialogTrigger -githubLabel: 'component: alert-dialog' -waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/ ---- - -# Alert Dialog - - - - - - - -## Installation - - - -## 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](/components/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. - - - -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. - -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; -} - -.AlertDialogPopup[data-starting-style], -.AlertDialogPopup[data-ending-style] { - opacity: 0; - transform: translate(-50%, -35%) scale(0.8); -} -``` - -`[data-starting-style]` and `[data-ending-style]` are attributes that are added to the popup when it is first inserted to the DOM and when it is about to be removed, respectively. In these states, you can specify the initial and final styles of the popup, which for symmetric transitions should be the same. - - - -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-starting-style] { - opacity: 0; - transform: translate(-50%, -35%) scale(0.8); -} - -/* Official Browser API */ -@starting-style { - .AlertDialogPopup[data-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-ending-style] { - 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-open]` - `open` state is `true`. -- `[data-starting-style]` - the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. -- `[data-ending-style]` - the popup's final styles once it closes. - -## 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/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.js b/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.js deleted file mode 100644 index b27d1630ad..0000000000 --- a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.js +++ /dev/null @@ -1,168 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Checkbox } from '@base-ui-components/react/checkbox'; -import { CheckboxGroup } from '@base-ui-components/react/checkbox-group'; -import { Field } from '@base-ui-components/react/field'; - -export default function UnstyledCheckboxIndeterminateGroup() { - return ( - -
- - Colors -
- - - - - - - Red - - - - - - - - Green - - - - - - - - Blue - -
-
- -
-
- ); -} - -const grey = { - 100: '#E5EAF2', - 300: '#C7D0DD', - 500: '#9DA8B7', - 600: '#6B7A90', - 800: '#303740', - 900: '#1C2025', -}; - -function Styles() { - return ( - - ); -} - -function CheckIcon(props) { - return ( - - - - ); -} diff --git a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.tsx b/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.tsx deleted file mode 100644 index 02f0de1bcc..0000000000 --- a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/css/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Checkbox } from '@base-ui-components/react/checkbox'; -import { CheckboxGroup } from '@base-ui-components/react/checkbox-group'; -import { Field } from '@base-ui-components/react/field'; - -export default function UnstyledCheckboxIndeterminateGroup() { - return ( - -
- - Colors -
- - - - - - - Red - - - - - - - - Green - - - - - - - - Blue - -
-
- -
-
- ); -} - -const grey = { - 100: '#E5EAF2', - 300: '#C7D0DD', - 500: '#9DA8B7', - 600: '#6B7A90', - 800: '#303740', - 900: '#1C2025', -}; - -function Styles() { - return ( - - ); -} - -function CheckIcon(props: React.SVGProps) { - return ( - - - - ); -} diff --git a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.js b/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.js deleted file mode 100644 index 285a1b81c7..0000000000 --- a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.js +++ /dev/null @@ -1,139 +0,0 @@ -'use client'; -import * as React from 'react'; -import { css, styled } from '@mui/system'; -import { Checkbox as BaseCheckbox } from '@base-ui-components/react/checkbox'; -import { CheckboxGroup } from '@base-ui-components/react/checkbox-group'; -import { Field } from '@base-ui-components/react/field'; - -export default function UnstyledCheckboxIndeterminateGroup() { - return ( -
- - - Colors -
- - - - - - - Red - - - - - - - - Green - - - - - - - - Blue - -
-
-
-
- ); -} - -const blue = { - 400: '#3399FF', - 600: '#0072E6', - 800: '#004C99', -}; - -const grey = { - 100: '#E5EAF2', - 400: '#B0B8C4', - 800: '#303740', -}; - -const labelStyles = css` - display: flex; - gap: 8px; - margin-bottom: 8px; -`; - -const FieldRoot = styled(Field.Root)` - display: flex; -`; - -const CheckboxLabel = styled(Field.Label)` - ${labelStyles} - padding-left: 8px; -`; - -const CheckboxGroupLabel = styled(Field.Label)` - font-size: 17px; - font-weight: bold; - ${labelStyles} -`; - -const Checkbox = styled(BaseCheckbox.Root)( - ({ theme }) => ` - width: 24px; - height: 24px; - padding: 0; - border-radius: 4px; - border: 2px solid ${blue[600]}; - background: none; - transition-property: background, border-color; - transition-duration: 0.15s; - outline: none; - - &[data-disabled] { - opacity: 0.4; - cursor: not-allowed; - } - - &:focus-visible { - outline: 2px solid ${theme.palette.mode === 'dark' ? blue[800] : blue[400]}; - outline-offset: 2px; - } - - &[data-checked], &[data-indeterminate] { - border-color: transparent; - background: ${blue[600]}; - } - `, -); - -const CheckIcon = styled(function CheckIcon(props) { - return ( - - - - ); -})` - height: 100%; - width: 100%; -`; - -const Indicator = styled(BaseCheckbox.Indicator)` - height: 100%; - display: inline-block; - visibility: hidden; - color: ${grey[100]}; - - &[data-checked], - &[data-indeterminate] { - visibility: visible; - } -`; diff --git a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.tsx b/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.tsx deleted file mode 100644 index a8307311cc..0000000000 --- a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/system/index.tsx +++ /dev/null @@ -1,139 +0,0 @@ -'use client'; -import * as React from 'react'; -import { css, styled } from '@mui/system'; -import { Checkbox as BaseCheckbox } from '@base-ui-components/react/checkbox'; -import { CheckboxGroup } from '@base-ui-components/react/checkbox-group'; -import { Field } from '@base-ui-components/react/field'; - -export default function UnstyledCheckboxIndeterminateGroup() { - return ( -
- - - Colors -
- - - - - - - Red - - - - - - - - Green - - - - - - - - Blue - -
-
-
-
- ); -} - -const blue = { - 400: '#3399FF', - 600: '#0072E6', - 800: '#004C99', -}; - -const grey = { - 100: '#E5EAF2', - 400: '#B0B8C4', - 800: '#303740', -}; - -const labelStyles = css` - display: flex; - gap: 8px; - margin-bottom: 8px; -`; - -const FieldRoot = styled(Field.Root)` - display: flex; -`; - -const CheckboxLabel = styled(Field.Label)` - ${labelStyles} - padding-left: 8px; -`; - -const CheckboxGroupLabel = styled(Field.Label)` - font-size: 17px; - font-weight: bold; - ${labelStyles} -`; - -const Checkbox = styled(BaseCheckbox.Root)( - ({ theme }) => ` - width: 24px; - height: 24px; - padding: 0; - border-radius: 4px; - border: 2px solid ${blue[600]}; - background: none; - transition-property: background, border-color; - transition-duration: 0.15s; - outline: none; - - &[data-disabled] { - opacity: 0.4; - cursor: not-allowed; - } - - &:focus-visible { - outline: 2px solid ${theme.palette.mode === 'dark' ? blue[800] : blue[400]}; - outline-offset: 2px; - } - - &[data-checked], &[data-indeterminate] { - border-color: transparent; - background: ${blue[600]}; - } - `, -); - -const CheckIcon = styled(function CheckIcon(props: React.SVGProps) { - return ( - - - - ); -})` - height: 100%; - width: 100%; -`; - -const Indicator = styled(BaseCheckbox.Indicator)` - height: 100%; - display: inline-block; - visibility: hidden; - color: ${grey[100]}; - - &[data-checked], - &[data-indeterminate] { - visibility: visible; - } -`; diff --git a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.js b/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.js deleted file mode 100644 index b20e85c483..0000000000 --- a/docs/data/components/checkbox-group/UnstyledCheckboxGroupIntroduction/tailwind/index.js +++ /dev/null @@ -1,144 +0,0 @@ -'use client'; -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { useTheme } from '@mui/system'; -import { Checkbox as BaseCheckbox } from '@base-ui-components/react/checkbox'; -import { CheckboxGroup } from '@base-ui-components/react/checkbox-group'; -import { Field } from '@base-ui-components/react/field'; - -function classNames(...classes) { - return classes.filter(Boolean).join(' '); -} - -function useIsDarkMode() { - const theme = useTheme(); - return theme.palette.mode === 'dark'; -} - -function Label(props) { - return ( - // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -