The accordion component allows the user to show and hide sections of related content on a page.
-
-An accordion is a lightweight container that may either be used standalone, or be connected to a larger surface, such as a card.
+The Accordion component lets users show and hide sections of related content on a page.
{{"component": "modules/components/ComponentLinkHeader.js"}}
+## Introduction
+
+The Material UI Accordion component includes several complementary utility components to handle various use cases:
+
+- Accordion: the wrapper for grouping related components.
+- Accordion Summary: the wrapper for the Accordion header, which expands or collapses the content when clicked.
+- Accordion Details: the wrapper for the Accordion content.
+- Accordion Actions: an optional wrapper that groups a set of buttons.
+
+{{"demo": "AccordionUsage.js", "bg": true}}
+
:::info
This component is no longer documented in the [Material Design guidelines](https://m2.material.io/), but Material UI will continue to support it.
:::
-## Basic accordion
+## Basics
+
+```jsx
+import Accordion from '@mui/material/Accordion';
+import AccordionDetails from '@mui/material/AccordionDetails';
+import AccordionSummary from '@mui/material/AccordionSummary';
+```
+
+### Expand icon
+
+Use the `expandIcon` prop on the Accordion Summary component to change the expand indicator icon.
+The component handles the turning upside-down transition automatically.
+
+{{"demo": "AccordionExpandIcon.js", "bg": true}}
+
+### Expanded by default
+
+Use the `defaultExpanded` prop on the Accordion component to have it opened by default.
+
+{{"demo": "AccordionExpandDefault.js", "bg": true}}
+
+### Transition
+
+Use the `TransitionComponent` and `TransitionProps` props to change the Accordion's default transition.
+
+{{"demo": "AccordionTransition.js", "bg": true}}
-{{"demo": "BasicAccordion.js", "bg": true}}
+### Disabled item
-## Controlled accordion
+Use the `disabled` prop on the Accordion component to disable interaction and focus.
+
+{{"demo": "DisabledAccordion.js", "bg": true}}
+
+### Controlled Accordion
The Accordion component can be controlled or uncontrolled.
+{{"demo": "ControlledAccordions.js", "bg": true}}
+
:::info
- A component is **controlled** when it's managed by its parent using props.
@@ -35,34 +75,55 @@ The Accordion component can be controlled or uncontrolled.
Learn more about controlled and uncontrolled components in the [React documentation](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components).
:::
-{{"demo": "ControlledAccordions.js", "bg": true}}
-
## Customization
-Here is an example of customizing the component.
-You can learn more about this in the [overrides documentation page](/material-ui/customization/how-to-customize/).
+### Only one expanded at a time
-{{"demo": "CustomizedAccordions.js"}}
+Use the `expanded` prop with React's `useState` hook to allow only one Accordion item to be expanded at a time.
+The demo below also shows a bit of visual customziation.
+
+{{"demo": "CustomizedAccordions.js", "bg": true}}
## Performance
-The content of Accordions is mounted by default even if the accordion is not expanded.
+The Accordion content is mounted by default even if it's not expanded.
This default behavior has server-side rendering and SEO in mind.
-If you render expensive component trees inside your accordion details or simply render many
-accordions it might be a good idea to change this default behavior by enabling the
-`unmountOnExit` in `TransitionProps`:
+
+If you render the Accordion Details with a big component tree nested inside, or if you have many Accordions, you may want to change this behavior by setting `unmountOnExit` to `true` inside the `TransitionProps` prop to improve performance:
```jsx
` that houses interior elements like the Accordion Summary and other optional components (such as buttons or decorators).
-For optimal accessibility we recommend setting `id` and `aria-controls` on the
-`AccordionSummary`. The `Accordion` will derive the necessary `aria-labelledby`
-and `id` for the content region of the accordion.
+```jsx
+
+```
diff --git a/docs/next.config.js b/docs/next.config.js
index ec73e6ba66bb9c..946495b1d4b079 100644
--- a/docs/next.config.js
+++ b/docs/next.config.js
@@ -1,4 +1,6 @@
+// @ts-check
const path = require('path');
+// @ts-ignore
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const pkg = require('../package.json');
const withDocsInfra = require('./nextConfigDocsInfra');
@@ -12,7 +14,7 @@ const {
const workspaceRoot = path.join(__dirname, '../');
-const l10nPRInNetlify = /^l10n_/.test(process.env.HEAD) && process.env.NETLIFY === 'true';
+const l10nPRInNetlify = /^l10n_/.test(process.env.HEAD || '') && process.env.NETLIFY === 'true';
const vercelDeploy = Boolean(process.env.VERCEL);
const isDeployPreview = Boolean(process.env.PULL_REQUEST_ID);
// For crowdin PRs we want to build all locales for testing.
@@ -48,6 +50,7 @@ module.exports = withDocsInfra({
const [nextExternals, ...externals] = config.externals;
config.externals = [
+ // @ts-ignore
(ctx, callback) => {
const { request } = ctx;
const hasDependencyOnRepoPackages = [
@@ -71,8 +74,9 @@ module.exports = withDocsInfra({
];
}
- config.module.rules.forEach((r) => {
- r.resourceQuery = { not: [/raw/] };
+ // @ts-ignore
+ config.module.rules.forEach((rule) => {
+ rule.resourceQuery = { not: [/raw/] };
});
return {
@@ -83,6 +87,7 @@ module.exports = withDocsInfra({
// resolve .tsx first
extensions: [
'.tsx',
+ // @ts-ignore
...config.resolve.extensions.filter((extension) => extension !== '.tsx'),
],
},
@@ -171,25 +176,29 @@ module.exports = withDocsInfra({
};
},
env: {
- GITHUB_AUTH: process.env.GITHUB_AUTH
- ? `Basic ${Buffer.from(process.env.GITHUB_AUTH).toString('base64')}`
- : null,
+ // docs-infra
LIB_VERSION: pkg.version,
- FEEDBACK_URL: process.env.FEEDBACK_URL,
- SOURCE_GITHUB_BRANCH: 'master', // #default-branch-switch
SOURCE_CODE_REPO: 'https://github.com/mui/material-ui',
+ SOURCE_GITHUB_BRANCH: 'master', // #default-branch-switch
GITHUB_TEMPLATE_DOCS_FEEDBACK: '4.docs-feedback.yml',
BUILD_ONLY_ENGLISH_LOCALE: String(buildOnlyEnglishLocale),
+ // MUI Core related
+ GITHUB_AUTH: process.env.GITHUB_AUTH
+ ? `Basic ${Buffer.from(process.env.GITHUB_AUTH).toString('base64')}`
+ : '',
},
// Next.js provides a `defaultPathMap` argument, we could simplify the logic.
// However, we don't in order to prevent any regression in the `findPages()` method.
+ // @ts-ignore
exportPathMap: () => {
const pages = findPages();
const map = {};
+ // @ts-ignore
function traverse(pages2, userLanguage) {
const prefix = userLanguage === 'en' ? '' : `/${userLanguage}`;
+ // @ts-ignore
pages2.forEach((page) => {
// The experiments pages are only meant for experiments, they shouldn't leak to production.
if (
@@ -206,6 +215,7 @@ module.exports = withDocsInfra({
// map api-docs to api
// i: /api-docs/* > /api/* (old structure)
// ii: /*/api-docs/* > /*/api/* (for new structure)
+ // @ts-ignore
map[`${prefix}${page.pathname.replace(/^(\/[^/]+)?\/api-docs\/(.*)/, '$1/api/$2')}`] = {
page: page.pathname,
query: {
diff --git a/docs/nextConfigDocsInfra.js b/docs/nextConfigDocsInfra.js
index b92e6764a604e2..ba6e0690714473 100644
--- a/docs/nextConfigDocsInfra.js
+++ b/docs/nextConfigDocsInfra.js
@@ -33,6 +33,19 @@ if (
process.env.DEPLOY_ENV = DEPLOY_ENV;
+/**
+ * @typedef {import('next').NextConfig} NextConfig
+ * @typedef {NextConfig['env']} NextConfigEnv
+ * @typedef {{env : {
+ * LIB_VERSION: string;
+ * SOURCE_CODE_REPO: string;
+ * SOURCE_GITHUB_BRANCH: string;
+ * GITHUB_TEMPLATE_DOCS_FEEDBACK: string;
+ * }} & NextConfig} NextConfigInput
+
+ * @param {NextConfigInput} nextConfig
+ * @returns {NextConfig}
+ */
function withDocsInfra(nextConfig) {
return {
trailingSlash: true,
@@ -44,6 +57,7 @@ function withDocsInfra(nextConfig) {
BUILD_ONLY_ENGLISH_LOCALE: 'true', // disable translations by default
// production | staging | pull-request | development
DEPLOY_ENV,
+ FEEDBACK_URL: process.env.FEEDBACK_URL,
...nextConfig.env,
// https://docs.netlify.com/configure-builds/environment-variables/#git-metadata
// reference ID (also known as "SHA" or "hash") of the commit we're building.
diff --git a/docs/pages/careers.tsx b/docs/pages/careers.tsx
index e79885ecb1940a..8e2e19c49816a6 100644
--- a/docs/pages/careers.tsx
+++ b/docs/pages/careers.tsx
@@ -174,7 +174,7 @@ const openRolesData = [
title: 'Developer Experience',
roles: [
{
- title: 'Developer Advocate',
+ title: 'Developer Advocate / Content Engineer',
description:
'You will strategize and implement educational initiatives from end to end to help developers build better UIs, faster.',
url: '/careers/developer-advocate/',
diff --git a/docs/pages/careers/developer-advocate.md b/docs/pages/careers/developer-advocate.md
index bb4088a77fe552..ec2497d404fce3 100644
--- a/docs/pages/careers/developer-advocate.md
+++ b/docs/pages/careers/developer-advocate.md
@@ -1,4 +1,4 @@
-# Developer Advocate
+# Developer Advocate / Content Engineer
You will strategize and implement educational initiatives from end to end to help developers build better UIs, faster.
@@ -92,11 +92,13 @@ For the right candidate:
- Hands-on developer and skilled React engineer
- Experience as a teacher, tutor, or mentor
- Experience as a technical writer or content creator
+- Good product design sensibilities
+- Basic design skills
### Nice to have (but not required)
-- Great taste in product design
-- Knowledge of design trends
+- Great product design sensibilities
+- Good design skills
- Experience in open-source
- Experience with design systems
- Experience with MUI products
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index b3282c841557ef..4dc232509b9b8b 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -1,6 +1,6 @@
{
"extends": "../tsconfig.json",
- "include": ["next-env.d.ts", "types", "src", "pages", "data"],
+ "include": ["next-env.d.ts", "types", "src", "pages", "data", "next.config.js"],
"compilerOptions": {
"allowJs": true,
"isolatedModules": true,
diff --git a/package.json b/package.json
index 0d0884978d1a48..1df2942f435120 100644
--- a/package.json
+++ b/package.json
@@ -47,8 +47,9 @@
"size:snapshot": "node --max-old-space-size=4096 ./scripts/sizeSnapshot/create",
"size:why": "pnpm size:snapshot --analyze",
"start": "pnpm install && pnpm docs:dev",
- "t": "node test/cli.js",
- "test": "pnpm eslint && pnpm typescript && pnpm test:coverage",
+ "test": "node scripts/test.mjs",
+ "tc": "node test/cli.js",
+ "test:extended": "pnpm eslint && pnpm typescript && pnpm test:coverage",
"test:coverage": "cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=text mocha 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}'",
"test:coverage:ci": "cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=lcov mocha 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}'",
"test:coverage:html": "cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=html mocha 'packages/**/*.test.{js,ts,tsx}' 'docs/**/*.test.{js,ts,tsx}'",
diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
index 2984fa0dea52de..5e5bfb689a5326 100644
--- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
+++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
@@ -104,9 +104,10 @@ export async function computeApiDescription(
const file = await remark()
.use(function docsLinksAttacher() {
return function transformer(tree) {
- remarkVisit(tree, 'link', (linkNode: Link) => {
- if ((linkNode.url as string).startsWith('/')) {
- linkNode.url = `${host}${linkNode.url}`;
+ remarkVisit(tree, 'link', (linkNode) => {
+ const link = linkNode as Link;
+ if ((link.url as string).startsWith('/')) {
+ link.url = `${host}${link.url}`;
}
});
};
diff --git a/packages/mui-joy/src/ToggleButtonGroup/ToggleButtonGroupProps.ts b/packages/mui-joy/src/ToggleButtonGroup/ToggleButtonGroupProps.ts
index 72460e4ad9097e..9ca96b276023fa 100644
--- a/packages/mui-joy/src/ToggleButtonGroup/ToggleButtonGroupProps.ts
+++ b/packages/mui-joy/src/ToggleButtonGroup/ToggleButtonGroupProps.ts
@@ -111,10 +111,10 @@ export interface ToggleButtonGroupTypeMap<
}
export type ToggleButtonGroupProps<
- TValue extends SupportedValue,
+ TValue extends SupportedValue = SupportedValue,
D extends React.ElementType = ToggleButtonGroupTypeMap
['defaultComponent'],
P = { component?: React.ElementType },
> = OverrideProps, D>;
-export interface ToggleButtonGroupOwnerState
+export interface ToggleButtonGroupOwnerState
extends ApplyColorInversion> {}
diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts
index 8bdbc3558dad16..27a1dd9c826f22 100644
--- a/packages/mui-joy/src/styles/components.d.ts
+++ b/packages/mui-joy/src/styles/components.d.ts
@@ -210,6 +210,11 @@ import {
} from '../StepIndicator/StepIndicatorProps';
import { SvgIconProps, SvgIconOwnerState, SvgIconSlot } from '../SvgIcon/SvgIconProps';
import { SwitchProps, SwitchOwnerState, SwitchSlot } from '../Switch/SwitchProps';
+import {
+ ToggleButtonGroupProps,
+ ToggleButtonGroupOwnerState,
+ ToggleButtonGroupSlot,
+} from '../ToggleButtonGroup/ToggleButtonGroupProps';
import { TabProps, TabOwnerState, TabSlot } from '../Tab/TabProps';
import { TabListProps, TabListOwnerState, TabListSlot } from '../TabList/TabListProps';
import { TabPanelProps, TabPanelOwnerState, TabPanelSlot } from '../TabPanel/TabPanelProps';
@@ -524,6 +529,10 @@ export interface Components {
defaultProps?: Partial;
styleOverrides?: StyleOverrides;
};
+ JoyToggleButtonGroup?: {
+ defaultProps?: Partial;
+ styleOverrides?: StyleOverrides;
+ };
JoyTooltip?: {
defaultProps?: Partial;
styleOverrides?: StyleOverrides;
diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts
index 63e78b73d3a4d5..816d3a03d91464 100644
--- a/packages/mui-joy/src/styles/extendTheme.spec.ts
+++ b/packages/mui-joy/src/styles/extendTheme.spec.ts
@@ -72,6 +72,7 @@ import { TabPanelOwnerState } from '@mui/joy/TabPanel';
import { TabsOwnerState } from '@mui/joy/Tabs';
import { TableOwnerState } from '@mui/joy/Table';
import { TextareaOwnerState } from '@mui/joy/Textarea';
+import { ToggleButtonGroupOwnerState } from '@mui/joy/ToggleButtonGroup';
import { TooltipOwnerState } from '@mui/joy/Tooltip';
import { TypographyOwnerState } from '@mui/joy/Typography';
@@ -1307,6 +1308,21 @@ extendTheme({
},
},
},
+ JoyToggleButtonGroup: {
+ defaultProps: {
+ size: 'sm',
+ variant: 'solid',
+ color: 'primary',
+ },
+ styleOverrides: {
+ root: ({ ownerState }) => {
+ expectType, typeof ownerState>(
+ ownerState,
+ );
+ return {};
+ },
+ },
+ },
JoyTooltip: {
defaultProps: {
size: 'md',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8c85fc078b3001..899327c1ce4300 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8196,13 +8196,13 @@ packages:
/@types/mdast@3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies:
- '@types/unist': 2.0.6
+ '@types/unist': 3.0.2
dev: false
/@types/mdast@4.0.3:
resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
dependencies:
- '@types/unist': 2.0.6
+ '@types/unist': 3.0.2
dev: true
/@types/mime@1.3.2:
@@ -8396,6 +8396,10 @@ packages:
/@types/unist@2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
+ dev: false
+
+ /@types/unist@3.0.2:
+ resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
/@types/use-sync-external-store@0.0.3:
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
diff --git a/scripts/test.mjs b/scripts/test.mjs
new file mode 100644
index 00000000000000..56a43fa60022a5
--- /dev/null
+++ b/scripts/test.mjs
@@ -0,0 +1,34 @@
+/* eslint-disable no-console */
+import { spawn } from 'node:child_process';
+import chalk from 'chalk';
+
+/*
+This script ensures that we can use the same commands to run tests
+when using pnpm as when using Yarn.
+It enables to run `pnpm test` (or `pnpm t`) without any arguments, to run all tests,
+or `pnpm test ` (or `pnpm t `) to run a subset of tests in watch mode.
+
+See https://github.com/mui/material-ui/pull/40430 for more context.
+*/
+
+if (process.argv.length < 3) {
+ console.log('Running ESLint, type checker, and unit tests...');
+ spawn('pnpm', ['test:extended'], {
+ shell: true,
+ stdio: ['inherit', 'inherit', 'inherit'],
+ });
+} else {
+ console.log('Running selected tests in watch mode...');
+ console.log(
+ chalk.yellow(
+ 'Note: run `pnpm tc` to have a better experience (and be able to pass in additional parameters).',
+ ),
+ );
+
+ console.log('cmd', ['tc', ...process.argv.slice(2)]);
+
+ spawn('pnpm', ['tc', ...process.argv.slice(2)], {
+ shell: true,
+ stdio: ['inherit', 'inherit', 'inherit'],
+ });
+}