diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionFooter.js b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.js new file mode 100644 index 00000000000000..c3bf3ed2a24486 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.js @@ -0,0 +1,226 @@ +import * as React from 'react'; + +import AspectRatio from '@mui/joy/AspectRatio'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import Input from '@mui/joy/Input'; +import List from '@mui/joy/List'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItem from '@mui/joy/ListItem'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import ListItemButton from '@mui/joy/ListItemButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import FacebookRoundedIcon from '@mui/icons-material/FacebookRounded'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import SendIcon from '@mui/icons-material/Send'; + +export default function ColorInversionFooter() { + const [color, setColor] = React.useState('neutral'); + return ( + + + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + + + + + + + + + + } + sx={{ ml: 'auto', display: { xs: 'none', md: 'flex' } }} + /> + + + + + + + + + Intro to the MUI ecosystem + + MUI blog + + + + + + Sitemap + + + Services + + + Blog + + + Contact us + + + + + Product + + + + + + + MUI Core + + + + + + + + MUI X + + + + + + + + MUI Toolpad + + BETA + + + + + + + + + Design kits + + + + + + + + Templates + + + + + + + + + by} + > + MUI + + + + Copyright 2022 + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx new file mode 100644 index 00000000000000..a975030b428dcc --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionFooter.tsx @@ -0,0 +1,225 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import AspectRatio from '@mui/joy/AspectRatio'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import Input from '@mui/joy/Input'; +import List from '@mui/joy/List'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItem from '@mui/joy/ListItem'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import ListItemButton from '@mui/joy/ListItemButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import FacebookRoundedIcon from '@mui/icons-material/FacebookRounded'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import SendIcon from '@mui/icons-material/Send'; + +export default function ColorInversionFooter() { + const [color, setColor] = React.useState('neutral'); + return ( + + + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + + + + + + + + + + } + sx={{ ml: 'auto', display: { xs: 'none', md: 'flex' } }} + /> + + + + + + + + + Intro to the MUI ecosystem + + MUI blog + + + + + + Sitemap + + + Services + + + Blog + + + Contact us + + + + + Product + + + + + + + MUI Core + + + + + + + + MUI X + + + + + + + + MUI Toolpad + + BETA + + + + + + + + + Design kits + + + + + + + + Templates + + + + + + + + + by} + > + MUI + + + + Copyright 2022 + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionHeader.js b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.js new file mode 100644 index 00000000000000..266be34b164ae5 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.js @@ -0,0 +1,155 @@ +import * as React from 'react'; + +import Avatar from '@mui/joy/Avatar'; +import Badge from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Input from '@mui/joy/Input'; +import IconButton from '@mui/joy/IconButton'; +import ListDivider from '@mui/joy/ListDivider'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import Chip from '@mui/joy/Chip'; +import AddIcon from '@mui/icons-material/Add'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +export default function ColorInversionHeader() { + const [anchorEl, setAnchorEl] = React.useState(null); + const [color, setColor] = React.useState('primary'); + return ( + + `linear-gradient(to top, ${theme.vars.palette[color][600]}, ${theme.vars.palette[color][500]})`, + }), + }} + > + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + { + setAnchorEl(event.currentTarget); + }} + endDecorator={} + > + Main + + setAnchorEl(null)} + placement="bottom-start" + disablePortal + size="sm" + sx={{ + '--List-decorator-size': '24px', + '--List-item-minHeight': '40px', + '--List-divider-gap': '4px', + minWidth: 200, + }} + > + setAnchorEl(null)}> + + + + Products + + + setAnchorEl(null)}>Pricing + setAnchorEl(null)}> + Case studies{' '} + + `rgba(${theme.vars.palette.primary.mainChannel} / 0.1)`, + }} + > + Beta + + + + + + + + + ⌘K + + } + sx={{ + '--Input-radius': '40px', + '--Input-paddingInline': '12px', + width: 160, + display: { xs: 'none', lg: 'flex' }, + }} + /> + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx new file mode 100644 index 00000000000000..eaf29e3a352e01 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionHeader.tsx @@ -0,0 +1,154 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import Avatar from '@mui/joy/Avatar'; +import Badge from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Input from '@mui/joy/Input'; +import IconButton from '@mui/joy/IconButton'; +import ListDivider from '@mui/joy/ListDivider'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import Chip from '@mui/joy/Chip'; +import AddIcon from '@mui/icons-material/Add'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +export default function ColorInversionHeader() { + const [anchorEl, setAnchorEl] = React.useState(null); + const [color, setColor] = React.useState('primary'); + return ( + + `linear-gradient(to top, ${theme.vars.palette[color][600]}, ${theme.vars.palette[color][500]})`, + }), + }} + > + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + sx={{ borderRadius: '50%' }} + > + + + + { + setAnchorEl(event.currentTarget); + }} + endDecorator={} + > + Main + + setAnchorEl(null)} + placement="bottom-start" + disablePortal + size="sm" + sx={{ + '--List-decorator-size': '24px', + '--List-item-minHeight': '40px', + '--List-divider-gap': '4px', + minWidth: 200, + }} + > + setAnchorEl(null)}> + + + + Products + + + setAnchorEl(null)}>Pricing + setAnchorEl(null)}> + Case studies{' '} + + `rgba(${theme.vars.palette.primary.mainChannel} / 0.1)`, + }} + > + Beta + + + + + + + + + ⌘K + + } + sx={{ + '--Input-radius': '40px', + '--Input-paddingInline': '12px', + width: 160, + display: { xs: 'none', lg: 'flex' }, + }} + /> + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js new file mode 100644 index 00000000000000..c5172e7306d9b5 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.js @@ -0,0 +1,89 @@ +import * as React from 'react'; + +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +/** + * Credit: https://flutter.dev/ + */ +export default function ColorInversionMarketing() { + const [color, setColor] = React.useState('primary'); + return ( + + + Get started + + Instant access to the power of the React UI library + + *': { flexGrow: 1, fontWeight: 'lg' }, + }} + > + + + + + + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx new file mode 100644 index 00000000000000..64d83a0aba8851 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMarketing.tsx @@ -0,0 +1,88 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +/** + * Credit: https://flutter.dev/ + */ +export default function ColorInversionMarketing() { + const [color, setColor] = React.useState('primary'); + return ( + + + Get started + + Instant access to the power of the React UI library + + *': { flexGrow: 1, fontWeight: 'lg' }, + }} + > + + + + + + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js new file mode 100644 index 00000000000000..bfd8891c137a8b --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.js @@ -0,0 +1,61 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionMotivation() { + const demo = ( + + + New + + + + + + Learn how to build super fast websites. + + + + ); + + return ( + + {/* Left: The global variants are applied to children only */} + + {demo} + + One layer
global variants are applied only to the children. +
+
+ + {/* Right: The global variants are applied to both parent and children */} + + {React.cloneElement(demo, { + variant: 'solid', + color: 'primary', + })} + + Two layers +
global variants are applied to the card and children. +
+
+
+ ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx new file mode 100644 index 00000000000000..85a879983c1fb5 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionMotivation.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionMotivation() { + const demo = ( + + + New + + + + + + Learn how to build super fast websites. + + + + ); + return ( + + {/* Left: The global variants are applied to children only */} + + {demo} + + One layer
global variants are applied only to the children. +
+
+ + {/* Right: The global variants are applied to both parent and children */} + + {React.cloneElement(demo, { + variant: 'solid', + color: 'primary', + })} + + Two layers +
global variants are applied to the card and children. +
+
+
+ ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js new file mode 100644 index 00000000000000..af8b73ae0a42f0 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.js @@ -0,0 +1,197 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; +import Badge, { badgeClasses } from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import CircularProgress from '@mui/joy/CircularProgress'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import IconButton from '@mui/joy/IconButton'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItemButton from '@mui/joy/ListItemButton'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Typography from '@mui/joy/Typography'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; +import Sheet from '@mui/joy/Sheet'; +import PieChart from '@mui/icons-material/PieChart'; +import SmsIcon from '@mui/icons-material/Sms'; +import PersonIcon from '@mui/icons-material/Person'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import AddIcon from '@mui/icons-material/Add'; +import SettingsIcon from '@mui/icons-material/Settings'; + +export default function ColorInversionNavigation() { + return ( + + ({ + p: 2, + ml: -3, + my: -3, + background: `linear-gradient(to top, ${theme.vars.palette.info[700]}, ${theme.vars.palette.info[600]} 25%, ${theme.vars.palette.info[500]} 75%)`, + })} + > + + + + + + + Dashboard + + + + Overview + + + + + + Chat + + 5 + + + + + + + Team + + + Shortcuts + + Tasks + Reports + Settings + + + + + + 35% + + + Last update: 22/12/22 + + Active + + + + + + + Jerry Wilson + + + + + + ({ + p: 2, + mr: -3, + my: -3, + display: 'flex', + flexDirection: 'column', + gap: 2, + '& button': { + borderRadius: '50%', + padding: 0, + '&:hover': { + boxShadow: theme.shadow.md, + }, + }, + })} + > + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx new file mode 100644 index 00000000000000..af8b73ae0a42f0 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionNavigation.tsx @@ -0,0 +1,197 @@ +import * as React from 'react'; +import Avatar from '@mui/joy/Avatar'; +import Badge, { badgeClasses } from '@mui/joy/Badge'; +import Box from '@mui/joy/Box'; +import Card from '@mui/joy/Card'; +import CardContent from '@mui/joy/CardContent'; +import CircularProgress from '@mui/joy/CircularProgress'; +import Chip from '@mui/joy/Chip'; +import Divider from '@mui/joy/Divider'; +import IconButton from '@mui/joy/IconButton'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import ListSubheader from '@mui/joy/ListSubheader'; +import ListItemButton from '@mui/joy/ListItemButton'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Typography from '@mui/joy/Typography'; +import Select from '@mui/joy/Select'; +import Option from '@mui/joy/Option'; +import Sheet from '@mui/joy/Sheet'; +import PieChart from '@mui/icons-material/PieChart'; +import SmsIcon from '@mui/icons-material/Sms'; +import PersonIcon from '@mui/icons-material/Person'; +import BubbleChartIcon from '@mui/icons-material/BubbleChart'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import AddIcon from '@mui/icons-material/Add'; +import SettingsIcon from '@mui/icons-material/Settings'; + +export default function ColorInversionNavigation() { + return ( + + ({ + p: 2, + ml: -3, + my: -3, + background: `linear-gradient(to top, ${theme.vars.palette.info[700]}, ${theme.vars.palette.info[600]} 25%, ${theme.vars.palette.info[500]} 75%)`, + })} + > + + + + + + + Dashboard + + + + Overview + + + + + + Chat + + 5 + + + + + + + Team + + + Shortcuts + + Tasks + Reports + Settings + + + + + + 35% + + + Last update: 22/12/22 + + Active + + + + + + + Jerry Wilson + + + + + + ({ + p: 2, + mr: -3, + my: -3, + display: 'flex', + flexDirection: 'column', + gap: 2, + '& button': { + borderRadius: '50%', + padding: 0, + '&:hover': { + boxShadow: theme.shadow.md, + }, + }, + })} + > + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionOverview.js b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.js new file mode 100644 index 00000000000000..df58ca270575cb --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.js @@ -0,0 +1,40 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionOverview() { + return ( + + + New + + + + + + Learn how to build super fast website. + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx new file mode 100644 index 00000000000000..df58ca270575cb --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionOverview.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import Chip from '@mui/joy/Chip'; +import IconButton from '@mui/joy/IconButton'; +import Typography from '@mui/joy/Typography'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +export default function ColorInversionOverview() { + return ( + + + New + + + + + + Learn how to build super fast website. + + + + ); +} diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionPopup.js b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.js new file mode 100644 index 00000000000000..0c9ad35decc01e --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.js @@ -0,0 +1,127 @@ +import * as React from 'react'; + +import Autocomplete from '@mui/joy/Autocomplete'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import IconButton from '@mui/joy/IconButton'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import ListDivider from '@mui/joy/ListDivider'; +import Tooltip from '@mui/joy/Tooltip'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +// disable flip for this demo +// https://popper.js.org/docs/v2/modifiers/flip/ +const modifiers = [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom'], + }, + }, +]; + +export default function ColorInversionPopup() { + const [color, setColor] = React.useState('danger'); + const [menuButton, setMenuButton] = React.useState(null); + return ( + + + + setMenuButton(null)} + > + New tab + New window + + Delete + + + + + + + { + const colors = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} + +const films = [ + { 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, + }, +]; diff --git a/docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx new file mode 100644 index 00000000000000..15a50684fb6abb --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/ColorInversionPopup.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import { ColorPaletteProp } from '@mui/joy/styles'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Button from '@mui/joy/Button'; +import Card from '@mui/joy/Card'; +import IconButton from '@mui/joy/IconButton'; +import Menu from '@mui/joy/Menu'; +import MenuItem from '@mui/joy/MenuItem'; +import ListDivider from '@mui/joy/ListDivider'; +import Tooltip from '@mui/joy/Tooltip'; +import BookmarkOutlinedIcon from '@mui/icons-material/BookmarkOutlined'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; + +// disable flip for this demo +// https://popper.js.org/docs/v2/modifiers/flip/ +const modifiers = [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom'], + }, + }, +]; + +export default function ColorInversionPopup() { + const [color, setColor] = React.useState('danger'); + const [menuButton, setMenuButton] = React.useState(null); + return ( + + + + setMenuButton(null)} + > + New tab + New window + + Delete + + + + + + + { + const colors: ColorPaletteProp[] = [ + 'primary', + 'neutral', + 'danger', + 'info', + 'success', + 'warning', + ]; + const nextColor = colors.indexOf(color); + setColor(colors[nextColor + 1] ?? colors[0]); + }} + > + 🎨 + + + ); +} + +const films = [ + { 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, + }, +]; diff --git a/docs/data/joy/main-features/color-inversion/color-inversion.md b/docs/data/joy/main-features/color-inversion/color-inversion.md new file mode 100644 index 00000000000000..4a7aa26cc14465 --- /dev/null +++ b/docs/data/joy/main-features/color-inversion/color-inversion.md @@ -0,0 +1,138 @@ +# Color inversion + +

Joy UI components can invert their colors to match with the parent's variant.

+ +## Motivation + +[Global variants](/joy-ui/main-features/global-variants/) provide a consistent `variant` prop that lets you control the hierarchy of importance within a group of Joy UI components. However, they are not working as expected when global variants are used in multiple layers. + +The example below (on your right-hand side) shows the problem when the interface has more than one layer that applies the global variants: + +{{"demo": "ColorInversionMotivation.js"}} + +On the **left**, the `Button`'s variant is `solid` which is the highest emphasis level compared to other components. This conforms to the visual appearance on the screen. + +On the **right**, the problem arises when the container's variant becomes `solid`. The button is no longer the highest emphasis element because the it has the same background as the container. Also, the text and the icon button don't have enough contrast to the parent's background. + +The color inversion is implemented to solves this issue, keeping the global variants meaningful when multiple layers of global variants are composed together. + +## Overview + +When color inversion is enabled on the parent component, the children with implicit color will invert their styles to match the parent's background by keeping the same hierarchy of importance based on their variants. + +{{"demo": "ColorInversionOverview.js"}} + +:::info +**Implicit** color refers to components that don't have the `color` prop specified. + +Color inversion has **no effect** on those children that have an **explicit** `color` prop. + +```js +// implicit color. The styles change when color inversion is enabled. + + +// explicit color. Color inversion has no effect. + +``` + +::: + +### Benefits + +- Color inversion reduces a significant amount of styling effort. It handles all of the visual states (hover, active, and focus) on all the children. +- It makes your interface scalable. New components added to the area will just work. +- It works for both client-side and server-side rendering. +- It works for both light and dark mode. +- It can be disabled at any time without impacting the structure of the components. +- It is an opt-in feature. If you don't use it, the extra CSS variables won't be included in the production style sheet. +- It doesn't alter the styles of the children if you explicitly specify the `color` prop on them. + +### Trade-offs + +- If the surface component contains just a few components, the style sheet size of the CSS variables might be **bigger** than customizing the styles of each individual child. +- It doesn't work with browsers that don't support [CSS variables](https://caniuse.com/css-variables). + +## Usage + +To enable the feature set `invertedColors` to true on the surface components: + +```js + + + +``` + +:::info + +- [`Sheet`](/joy-ui/react-sheet/) and [`Card`](/joy-ui/react-card/) are the only components that support this feature. +- The surface component should have `soft` or `solid` variant to enable this feature. + ::: + +### Portal popup + +By default, color inversion has no effect on the popup slot of `Autocomplete`, `Menu`, and `Tooltip`. + +To enable color inversion for those slots, set `disablePortal` to true. + +{{"demo": "ColorInversionPopup.js"}} + +:::info +The popup slot of the `Select` component has `disablePortal` set to true by default. +::: + +## Common examples + +### Header + +{{"demo": "ColorInversionHeader.js"}} + +### Footer + +{{"demo": "ColorInversionFooter.js"}} + +### Side navigation + +{{"demo": "ColorInversionNavigation.js"}} + +### Marketing section + +{{"demo": "ColorInversionMarketing.js"}} + +## How it works + +**Parent component** + +When `invertedColors` is set to true on the surface component, a set of CSS variables are applied to it. The values of those variables comes from `theme.colorInversion[variant][color]` where `variant` and `color` are the component's props. The surface component also creates a React context to tell the children to update their styles. + +```jsx + + +// The component style sheet +{ + // the values of these variables depends on the parent's variant and color. + --variant-softColor: …; + --variant-softBg: …; + --variant-softHoverColor: …; + --variant-softHoverBg: …; + --variant-softActiveBg: …; + … // other variants +} +``` + +**Child component** + +All Joy UI components that support global variants check the React context that contains the color inversion flag. If the flag is true and the child has an implicit color, the internal `color` value will switch to `context` and apply the styles from `theme.variants[variant].context`. + +The styles will match the `--variant-*` variables that the parent has. + +```jsx + + +// Component style sheet +{ + background-color: var(--variant-softBg); + color: var(--variant-softColor); +} +``` + +In summary, the parent creates a React context to tell the children that the feature is enabled, and generates CSS variables that will be used by the children. The children with an implicit color switch their default color value to `context` to get the styles from the theme. diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index 6376203aa10d79..cae4b30a0c11ee 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -14,6 +14,7 @@ const pages = [ subheader: 'main-features', children: [ { pathname: '/joy-ui/main-features/global-variants' }, + { pathname: '/joy-ui/main-features/color-inversion' }, { pathname: '/joy-ui/main-features/automatic-adjustment' }, { pathname: '/joy-ui/main-features/dark-mode-optimization' }, ], diff --git a/docs/pages/experiments/joy/variant-overrides.tsx b/docs/pages/experiments/joy/variant-overrides.tsx deleted file mode 100644 index a1c1ff8a88a4a7..00000000000000 --- a/docs/pages/experiments/joy/variant-overrides.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import * as React from 'react'; -import { GlobalStyles } from '@mui/system'; -import Box from '@mui/joy/Box'; -import Button from '@mui/joy/Button'; -import Divider from '@mui/joy/Divider'; -import Typography from '@mui/joy/Typography'; -import Sheet from '@mui/joy/Sheet'; -import { CssVarsProvider, useColorScheme } from '@mui/joy/styles'; -import Moon from '@mui/icons-material/DarkMode'; -import Sun from '@mui/icons-material/LightMode'; - -function ColorSchemePicker() { - const { mode, setMode } = useColorScheme(); - const [mounted, setMounted] = React.useState(false); - React.useEffect(() => { - setMounted(true); - }, []); - if (!mounted) { - return null; - } - - return ( - - ); -} - -export default function JoyVariant() { - return ( - - - - - - - div': { - display: 'flex', - flexDirection: 'column', - gap: 1, - p: 2.5, - boxShadow: 'md', - borderRadius: 'sm', - }, - }} - > - - - text.primary - text.secondary - - text.tertiary - - - - - - - - - - - - - - - - - - - - - - - - - - - text.primary - text.secondary - - text.tertiary - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/docs/pages/joy-ui/main-features/color-inversion.js b/docs/pages/joy-ui/main-features/color-inversion.js new file mode 100644 index 00000000000000..bd3ed4e7015ff5 --- /dev/null +++ b/docs/pages/joy-ui/main-features/color-inversion.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/joy/main-features/color-inversion/color-inversion.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/mui-joy/src/Alert/Alert.test.js b/packages/mui-joy/src/Alert/Alert.test.js index da5e7bb67c5ecb..c5621ee4fbf6d1 100644 --- a/packages/mui-joy/src/Alert/Alert.test.js +++ b/packages/mui-joy/src/Alert/Alert.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Alert, { alertClasses as classes } from '@mui/joy/Alert'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -26,6 +26,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyAlert', classes }); + describe('prop: variant', () => { it('soft by default', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/Alert/Alert.tsx b/packages/mui-joy/src/Alert/Alert.tsx index ffe87c545874c7..1c70bcf9670a16 100644 --- a/packages/mui-joy/src/Alert/Alert.tsx +++ b/packages/mui-joy/src/Alert/Alert.tsx @@ -1,16 +1,17 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; -import clsx from 'clsx'; -import PropTypes from 'prop-types'; -import * as React from 'react'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import { getAlertUtilityClass } from './alertClasses'; -import { AlertProps, AlertTypeMap } from './AlertProps'; +import { AlertProps, AlertOwnerState, AlertTypeMap } from './AlertProps'; -const useUtilityClasses = (ownerState: AlertProps) => { +const useUtilityClasses = (ownerState: AlertOwnerState) => { const { variant, color, size } = ownerState; const slots = { @@ -31,7 +32,7 @@ const AlertRoot = styled('div', { name: 'JoyAlert', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: AlertOwnerState }>(({ theme, ownerState }) => ({ '--Alert-radius': theme.vars.radius.sm, '--Alert-decorator-childRadius': 'max((var(--Alert-radius) - var(--variant-borderWidth, 0px)) - var(--Alert-padding), min(var(--Alert-padding) / 2, (var(--Alert-radius) - var(--variant-borderWidth, 0px)) / 2))', @@ -76,23 +77,27 @@ const AlertStartDecorator = styled('span', { name: 'JoyAlert', slot: 'StartDecorator', overridesResolver: (props, styles) => styles.startDecorator, -})<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: AlertOwnerState }>(({ theme, ownerState }) => ({ display: 'inherit', flex: 'none', marginInlineEnd: 'var(--Alert-gap)', - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + ...(ownerState.color !== 'context' && { + color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + }), })); const AlertEndDecorator = styled('span', { name: 'JoyAlert', slot: 'EndDecorator', overridesResolver: (props, styles) => styles.endDecorator, -})<{ ownerState: AlertProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: AlertOwnerState }>(({ theme, ownerState }) => ({ display: 'inherit', flex: 'none', marginInlineStart: 'var(--Alert-gap)', marginLeft: 'auto', - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + ...(ownerState.color !== 'context' && { + color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + }), })); const Alert = React.forwardRef(function Alert(inProps, ref) { @@ -104,7 +109,7 @@ const Alert = React.forwardRef(function Alert(inProps, ref) { const { children, className, - color = 'primary', + color: colorProp = 'primary', role = 'alert', variant = 'soft', size = 'md', @@ -112,6 +117,8 @@ const Alert = React.forwardRef(function Alert(inProps, ref) { endDecorator, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Alert/AlertProps.ts b/packages/mui-joy/src/Alert/AlertProps.ts index 34c00ca1da8bde..2f6ebbbf359153 100644 --- a/packages/mui-joy/src/Alert/AlertProps.ts +++ b/packages/mui-joy/src/Alert/AlertProps.ts @@ -1,6 +1,6 @@ -import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AlertSlot = 'root' | 'startDecorator' | 'endDecorator'; @@ -62,4 +62,4 @@ export type AlertProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface AlertOwnerState extends AlertProps {} +export interface AlertOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Alert/alertClasses.ts b/packages/mui-joy/src/Alert/alertClasses.ts index 64bdb5ba4c559d..d2ee0ef9063024 100644 --- a/packages/mui-joy/src/Alert/alertClasses.ts +++ b/packages/mui-joy/src/Alert/alertClasses.ts @@ -15,6 +15,8 @@ export interface AlertClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the endDecorator element if supplied. */ endDecorator: string; /** Styles applied to the root element if `size="sm"`. */ @@ -51,6 +53,7 @@ const alertClasses: AlertClasses = generateUtilityClasses('JoyAlert', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.test.js b/packages/mui-joy/src/AspectRatio/AspectRatio.test.js index 8e4704581efd0d..8ec3c5aadfbcde 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.test.js +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import AspectRatio, { aspectRatioClasses as classes } from '@mui/joy/AspectRatio'; @@ -25,6 +25,11 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion( + , + { muiName: 'JoyAlert', classes }, + ); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx index 3173f05f4e7300..f23cf3746a34d0 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx @@ -6,6 +6,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import useThemeProps from '../styles/useThemeProps'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { getAspectRatioUtilityClass } from './aspectRatioClasses'; import { AspectRatioProps, AspectRatioOwnerState, AspectRatioTypeMap } from './AspectRatioProps'; @@ -95,10 +96,12 @@ const AspectRatio = React.forwardRef(function AspectRatio(inProps, ref) { minHeight, maxHeight, objectFit = 'cover', - color = 'neutral', + color: colorProp = 'neutral', variant = 'soft', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts b/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts index fbd9c4418c3ac0..ffddaf3ec7b431 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts +++ b/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AspectRatioSlot = 'root' | 'content'; @@ -65,4 +65,4 @@ export type AspectRatioProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface AspectRatioOwnerState extends AspectRatioProps {} +export interface AspectRatioOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts b/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts index e1b1034503222d..fb6bbd46be099f 100644 --- a/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts +++ b/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts @@ -17,6 +17,8 @@ export interface AspectRatioClasses { colorSuccess: string; /** Styles applied to the content element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the content element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the content element if `variant="outlined"`. */ @@ -42,6 +44,7 @@ const aspectRatioClasses: AspectRatioClasses = generateUtilityClasses('JoyAspect 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx index ad05a2d2f88889..b338d45dce7246 100644 --- a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx @@ -9,6 +9,7 @@ import { act, fireEvent, strictModeDoubleLoggingSupressed, + describeJoyColorInversion, } from 'test/utils'; import Autocomplete, { autocompleteClasses as classes, @@ -48,6 +49,12 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { + muiName: 'JoyAutocomplete', + classes, + portalSlot: 'listbox', + }); + it('should be customizable in the theme', () => { render( , 'onChange' | 'defaultValue'>; @@ -330,10 +332,11 @@ const Autocomplete = React.forwardRef(function Autocomplete( } = props; const other = excludeUseAutocompleteParams(otherProps); + const { getColor } = useColorInversion(variant); const formControl = React.useContext(FormControlContext); const error = inProps.error ?? formControl?.error ?? errorProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const color = getColor(inProps.color, error ? 'danger' : formControl?.color ?? colorProp); const disabled = disabledProp ?? formControl?.disabled ?? false; const { @@ -402,7 +405,7 @@ const Autocomplete = React.forwardRef(function Autocomplete( key={index} size={size} variant="soft" - color="neutral" + color={color === 'context' ? undefined : 'neutral'} endDecorator={} > {getOptionLabel(option)} @@ -537,7 +540,8 @@ const Autocomplete = React.forwardRef(function Autocomplete( getSlotOwnerState: (mergedProps) => ({ size: mergedProps.size || size, variant: mergedProps.variant || 'outlined', - color: mergedProps.variant || 'neutral', + color: mergedProps.color || 'neutral', + disableColorInversion: !mergedProps.disablePortal, }), additionalProps: { anchorEl, @@ -601,6 +605,7 @@ const Autocomplete = React.forwardRef(function Autocomplete( getSlotOwnerState: (mergedProps) => ({ variant: mergedProps.variant || 'plain', color: mergedProps.color || 'neutral', + disableColorInversion: !listboxProps.disablePortal, }), additionalProps: { as: 'li', @@ -637,6 +642,47 @@ const Autocomplete = React.forwardRef(function Autocomplete( [listboxProps.modifiers], ); + let popup = null; + if (anchorEl) { + popup = ( + + + {groupedOptions.map((option, index) => { + if (groupBy) { + const typedOption = option as AutocompleteGroupedOption; + return renderGroup({ + key: String(typedOption.key), + group: typedOption.group, + children: typedOption.options.map((option2, index2) => + renderListOption(option2, typedOption.index + index2), + ), + }); + } + return renderListOption(option, index); + })} + {loading && groupedOptions.length === 0 ? ( + {loadingText} + ) : null} + {groupedOptions.length === 0 && !freeSolo && !loading ? ( + {noOptionsText} + ) : null} + + + ); + + if (!listboxProps.disablePortal) { + // For portal popup, the children should not inherit color inversion from the upper parent. + popup = {popup}; + } + } + return ( @@ -656,32 +702,7 @@ const Autocomplete = React.forwardRef(function Autocomplete( {popupIcon} ) : null} - {anchorEl ? ( - // `nested` is for grouped options use case. - - - {groupedOptions.map((option, index) => { - if (groupBy) { - const typedOption = option as AutocompleteGroupedOption; - return renderGroup({ - key: String(typedOption.key), - group: typedOption.group, - children: typedOption.options.map((option2, index2) => - renderListOption(option2, typedOption.index + index2), - ), - }); - } - return renderListOption(option, index); - })} - {loading && groupedOptions.length === 0 ? ( - {loadingText} - ) : null} - {groupedOptions.length === 0 && !freeSolo && !loading ? ( - {noOptionsText} - ) : null} - - - ) : null} + {popup} ); }) as AutocompleteComponent; diff --git a/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts b/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts index d995f5e91ade04..7a817c92f7cbc8 100644 --- a/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts +++ b/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts @@ -9,7 +9,7 @@ import { } from '@mui/base/AutocompleteUnstyled'; import { PopperUnstyledOwnProps } from '@mui/base/PopperUnstyled'; import { OverridableStringUnion } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AutocompleteSlot = @@ -305,7 +305,7 @@ export interface AutocompleteOwnerState< Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, -> extends AutocompleteOwnProps { +> extends ApplyColorInversion> { focused?: boolean; hasClearIcon?: boolean; hasPopupIcon?: boolean; diff --git a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts index e48385d4651176..22ced03f1bc388 100644 --- a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts +++ b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts @@ -53,6 +53,8 @@ export interface AutocompleteClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -102,6 +104,7 @@ const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('JoyAuto 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx index c1dce2700792d3..d2c36e7be137a8 100644 --- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import AutocompleteListbox, { autocompleteListboxClasses as classes, @@ -21,6 +21,11 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { + muiName: 'JoyAutocompleteListbox', + classes, + }); + it('should have ul tag', () => { const { container } = render(); expect(container.firstChild).to.have.tagName('ul'); diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx index bd58846f76264e..0aba3162179d35 100644 --- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx @@ -14,6 +14,7 @@ import { import listItemClasses from '../ListItem/listItemClasses'; import listClasses from '../List/listClasses'; import { scopedVariables } from '../List/ListProvider'; +import { useColorInversion } from '../styles/ColorInversion'; const useUtilityClasses = (ownerState: AutocompleteListboxOwnerState) => { const { variant, color, size } = ownerState; @@ -93,11 +94,13 @@ const AutocompleteListbox = React.forwardRef(function AutocompleteListbox(inProp children, className, component, - color = 'neutral', + color: colorProp = 'neutral', variant = 'outlined', size = 'md', ...otherProps } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts index d48cfd219c1d0a..f14f02d67f74d7 100644 --- a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts @@ -1,27 +1,30 @@ import * as React from 'react'; -import { OverrideProps } from '@mui/types'; -import { ListProps } from '../List/ListProps'; -import { SxProps } from '../styles/types'; +import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type AutocompleteListboxSlot = 'root'; +export interface AutocompleteListboxPropsSizeOverrides {} +export interface AutocompleteListboxPropsColorOverrides {} +export interface AutocompleteListboxPropsVariantOverrides {} + export interface AutocompleteListboxTypeMap

{ props: P & { /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' */ - color?: ListProps['color']; + color?: OverridableStringUnion; /** * The variant to use. * @default 'outlined' */ - variant?: ListProps['variant']; + variant?: OverridableStringUnion; /** * The size of the component (affect other nested list* components). * @default 'md' */ - size?: ListProps['size']; + size?: OverridableStringUnion<'sm' | 'md' | 'lg', AutocompleteListboxPropsSizeOverrides>; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -37,4 +40,5 @@ export type AutocompleteListboxProps< }, > = OverrideProps, D>; -export interface AutocompleteListboxOwnerState extends AutocompleteListboxProps {} +export interface AutocompleteListboxOwnerState + extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts index d3fa27a8717fe7..a8a348abcaa19a 100644 --- a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts +++ b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts @@ -21,6 +21,8 @@ export interface AutocompleteListboxClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -50,6 +52,7 @@ const autocompleteListboxClasses: AutocompleteListboxClasses = generateUtilityCl 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js index 205980e72aa61f..546fc62e96954f 100644 --- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import AutocompleteOption, { autocompleteOptionClasses as classes, @@ -21,6 +21,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyAutocompleteOption', classes }); + it('should have li tag', () => { const { getByRole } = render(); expect(getByRole('option')).to.have.tagName('li'); diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx index b54098681d529d..7c8ad4f5b13397 100644 --- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx @@ -10,6 +10,7 @@ import autocompleteOptionClasses, { getAutocompleteOptionUtilityClass, } from './autocompleteOptionClasses'; import { AutocompleteOptionOwnerState, AutocompleteOptionTypeMap } from './AutocompleteOptionProps'; +import { useColorInversion } from '../styles/ColorInversion'; const useUtilityClasses = (ownerState: AutocompleteOptionOwnerState) => { const { color, variant } = ownerState; @@ -33,8 +34,10 @@ export const StyledAutocompleteOption = styled(StyledListItemButton as unknown a }, '&[aria-disabled="true"]': theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], '&[aria-selected="true"]': { - color: theme.vars.palette.primary.softColor, - backgroundColor: theme.vars.palette.primary.softBg, + color: theme.variants.soft?.[ownerState.color === 'context' ? 'context' : 'primary']?.color, + backgroundColor: + theme.variants.soft?.[ownerState.color === 'context' ? 'context' : 'primary'] + ?.backgroundColor, fontWeight: theme.vars.fontWeight.md, }, [`&.${autocompleteOptionClasses.focused}:not([aria-selected="true"]):not(:hover)`]: { @@ -59,11 +62,13 @@ const AutocompleteOption = React.forwardRef(function AutocompleteOption(inProps, const { children, component = 'li', - color = 'neutral', + color: colorProp = 'neutral', variant = 'plain', className, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts index 4d2e672dc482b4..4146e121d861fa 100644 --- a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts @@ -1,22 +1,24 @@ import * as React from 'react'; -import { OverrideProps } from '@mui/types'; -import { ListItemButtonProps } from '../ListItemButton/ListItemButtonProps'; -import { SxProps } from '../styles/types'; +import { OverrideProps, OverridableStringUnion } from '@mui/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type AutocompleteOptionSlot = 'root'; +export interface AutocompleteOptionPropsColorOverrides {} +export interface AutocompleteOptionPropsVariantOverrides {} + export interface AutocompleteOptionTypeMap

{ props: P & { /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' */ - color?: ListItemButtonProps['color']; + color?: OverridableStringUnion; /** * The variant to use. * @default 'plain' */ - variant?: ListItemButtonProps['variant']; + variant?: OverridableStringUnion; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -32,4 +34,5 @@ export type AutocompleteOptionProps< }, > = OverrideProps, D>; -export interface AutocompleteOptionOwnerState extends AutocompleteOptionProps {} +export interface AutocompleteOptionOwnerState + extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts index 711ee09fc73203..f393cf0e6e91c9 100644 --- a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts +++ b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts @@ -19,6 +19,8 @@ export interface AutocompleteOptionClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -47,6 +49,7 @@ const autocompleteOptionClasses: AutocompleteOptionClasses = generateUtilityClas 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/Avatar/Avatar.test.js b/packages/mui-joy/src/Avatar/Avatar.test.js index 1c3539c39bd6b6..166c290800ccd2 100644 --- a/packages/mui-joy/src/Avatar/Avatar.test.js +++ b/packages/mui-joy/src/Avatar/Avatar.test.js @@ -1,7 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Avatar, { avatarClasses as classes } from '@mui/joy/Avatar'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -28,6 +33,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyAvatar', classes }); + describe('prop: variant', () => { it('soft by default', () => { const { getByTestId } = render(); diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx index 8871021bfeed1d..ac99fdfed9a804 100644 --- a/packages/mui-joy/src/Avatar/Avatar.tsx +++ b/packages/mui-joy/src/Avatar/Avatar.tsx @@ -6,6 +6,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { useThemeProps } from '../styles'; import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import Person from '../internal/svg-icons/Person'; import { getAvatarUtilityClass } from './avatarClasses'; import { AvatarProps, AvatarOwnerState, AvatarTypeMap } from './AvatarProps'; @@ -155,8 +156,9 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { children: childrenProp, ...other } = props; - const color = inProps.color || groupContext?.color || colorProp; const variant = inProps.variant || groupContext?.variant || variantProp; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color || groupContext?.color, colorProp); const size = inProps.size || groupContext?.size || sizeProp; let children = null; diff --git a/packages/mui-joy/src/Avatar/AvatarProps.ts b/packages/mui-joy/src/Avatar/AvatarProps.ts index a1b43d176c93dd..8f211b7444e4ca 100644 --- a/packages/mui-joy/src/Avatar/AvatarProps.ts +++ b/packages/mui-joy/src/Avatar/AvatarProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type AvatarSlot = 'root' | 'img' | 'fallback'; @@ -76,7 +76,7 @@ export type AvatarProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface AvatarOwnerState extends AvatarProps { +export interface AvatarOwnerState extends ApplyColorInversion { /** * The avatar is wrapped by AvatarGroup component. */ diff --git a/packages/mui-joy/src/Avatar/avatarClasses.ts b/packages/mui-joy/src/Avatar/avatarClasses.ts index 4d42d4c9d2d8ea..2094f9c82dcb8e 100644 --- a/packages/mui-joy/src/Avatar/avatarClasses.ts +++ b/packages/mui-joy/src/Avatar/avatarClasses.ts @@ -15,6 +15,8 @@ export interface AvatarClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the fallback icon. */ fallback: string; /** Styles applied to the root element if `size="sm"`. */ @@ -47,6 +49,7 @@ const avatarClasses: AvatarClasses = generateUtilityClasses('JoyAvatar', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'fallback', 'sizeSm', 'sizeMd', diff --git a/packages/mui-joy/src/Badge/Badge.test.js b/packages/mui-joy/src/Badge/Badge.test.js index 0841b89083f6c7..c2fb3c81d51ebd 100644 --- a/packages/mui-joy/src/Badge/Badge.test.js +++ b/packages/mui-joy/src/Badge/Badge.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Badge, { badgeClasses as classes } from '@mui/joy/Badge'; @@ -42,6 +42,13 @@ describe('', () => { }), ); + describeJoyColorInversion( + + , { muiName: 'JoyButton', classes }); + it('by default, should render with the root, variantSolid, sizeMd and colorPrimary classes', () => { const { getByRole } = render(); const button = getByRole('button'); diff --git a/packages/mui-joy/src/Button/Button.tsx b/packages/mui-joy/src/Button/Button.tsx index e7c59022f78e64..af12b54e24d790 100644 --- a/packages/mui-joy/src/Button/Button.tsx +++ b/packages/mui-joy/src/Button/Button.tsx @@ -190,7 +190,10 @@ const Button = React.forwardRef(function Button(inProps, ref) { }); const loadingIndicator = loadingIndicatorProp ?? ( - + ); React.useImperativeHandle( diff --git a/packages/mui-joy/src/Button/ButtonProps.ts b/packages/mui-joy/src/Button/ButtonProps.ts index 898ce397d3552f..5046d6ad1b62ec 100644 --- a/packages/mui-joy/src/Button/ButtonProps.ts +++ b/packages/mui-joy/src/Button/ButtonProps.ts @@ -5,7 +5,7 @@ import { OverridableTypeMap, OverrideProps, } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; export type ButtonSlot = 'root' | 'startDecorator' | 'endDecorator' | 'loadingIndicatorCenter'; @@ -114,8 +114,7 @@ export type ButtonProps< }, > = OverrideProps, D>; -export interface ButtonOwnerState extends Omit { - color: ButtonProps['color'] | 'context'; +export interface ButtonOwnerState extends ApplyColorInversion { /** * If `true`, the button's focus is visible. */ diff --git a/packages/mui-joy/src/Button/buttonClasses.ts b/packages/mui-joy/src/Button/buttonClasses.ts index 405baa4fe22bb6..a47e6f895db806 100644 --- a/packages/mui-joy/src/Button/buttonClasses.ts +++ b/packages/mui-joy/src/Button/buttonClasses.ts @@ -15,6 +15,8 @@ export interface ButtonClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -59,6 +61,7 @@ const buttonClasses: ButtonClasses = generateUtilityClasses('JoyButton', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Card/Card.test.js b/packages/mui-joy/src/Card/Card.test.js index 08c0239c769001..c81d64825bab0c 100644 --- a/packages/mui-joy/src/Card/Card.test.js +++ b/packages/mui-joy/src/Card/Card.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Card, { cardClasses as classes } from '@mui/joy/Card'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -21,6 +21,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyCard', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/Card/Card.tsx b/packages/mui-joy/src/Card/Card.tsx index 73c9813f331fff..1d35f6c933ff24 100644 --- a/packages/mui-joy/src/Card/Card.tsx +++ b/packages/mui-joy/src/Card/Card.tsx @@ -9,12 +9,13 @@ import { } from '@mui/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { ColorInversionProvider, useColorInversion } from '../styles/ColorInversion'; import { getCardUtilityClass } from './cardClasses'; -import { CardProps, CardTypeMap } from './CardProps'; +import { CardProps, CardOwnerState, CardTypeMap } from './CardProps'; import { resolveSxValue } from '../styles/styleUtils'; import { CardRowContext } from './CardContext'; -const useUtilityClasses = (ownerState: CardProps) => { +const useUtilityClasses = (ownerState: CardOwnerState) => { const { size, variant, color, row } = ownerState; const slots = { @@ -34,7 +35,7 @@ const CardRoot = styled('div', { name: 'JoyCard', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: CardProps }>(({ theme, ownerState }) => [ +})<{ ownerState: CardOwnerState }>(({ theme, ownerState }) => [ { // a context variable for any child component '--Card-childRadius': @@ -82,6 +83,9 @@ const CardRoot = styled('div', { flexDirection: ownerState.row ? 'row' : 'column', }, theme.variants[ownerState.variant!]?.[ownerState.color!], + ownerState.color !== 'context' && + ownerState.invertedColors && + theme.colorInversion[ownerState.variant!]?.[ownerState.color!], ]); const Card = React.forwardRef(function Card(inProps, ref) { @@ -92,14 +96,17 @@ const Card = React.forwardRef(function Card(inProps, ref) { const { className, - color = 'neutral', + color: colorProp = 'neutral', component = 'div', + invertedColors = false, size = 'md', variant = 'plain', children, row = false, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, @@ -112,7 +119,7 @@ const Card = React.forwardRef(function Card(inProps, ref) { const classes = useUtilityClasses(ownerState); - return ( + const result = ( ); + + if (invertedColors) { + return {result}; + } + return result; }) as OverridableComponent; Card.propTypes /* remove-proptypes */ = { @@ -173,6 +185,11 @@ Card.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, + /** + * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. + * @default false + */ + invertedColors: PropTypes.bool, /** * If `true`, flex direction is set to 'row'. * @default false diff --git a/packages/mui-joy/src/Card/CardProps.ts b/packages/mui-joy/src/Card/CardProps.ts index 5aa19039932eb9..cd3619d30e5c60 100644 --- a/packages/mui-joy/src/Card/CardProps.ts +++ b/packages/mui-joy/src/Card/CardProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type CardSlot = 'root'; @@ -20,6 +20,11 @@ export interface CardTypeMap

{ * @default 'neutral' */ color?: OverridableStringUnion; + /** + * If `true`, the children with an implicit color prop invert their colors to match the component's variant and color. + * @default false + */ + invertedColors?: boolean; /** * If `true`, flex direction is set to 'row'. * @default false @@ -49,4 +54,4 @@ export type CardProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface CardOwnerState extends CardProps {} +export interface CardOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Card/cardClasses.ts b/packages/mui-joy/src/Card/cardClasses.ts index 687062fdf18d50..6fae87bdfe765f 100644 --- a/packages/mui-joy/src/Card/cardClasses.ts +++ b/packages/mui-joy/src/Card/cardClasses.ts @@ -15,6 +15,8 @@ export interface CardClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -47,6 +49,7 @@ const cardClasses: CardClasses = generateUtilityClasses('JoyCard', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.test.js b/packages/mui-joy/src/CardOverflow/CardOverflow.test.js index f5433675915d12..3843b7c68adf01 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflow.test.js +++ b/packages/mui-joy/src/CardOverflow/CardOverflow.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import CardOverflow, { cardOverflowClasses as classes } from '@mui/joy/CardOverflow'; @@ -21,6 +21,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyCardOverflow', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx index bda7cd29a9d2da..8e30b37b16b619 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx +++ b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx @@ -6,11 +6,16 @@ import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { getCardOverflowUtilityClass } from './cardOverflowClasses'; -import { CardOverflowProps, CardOverflowTypeMap } from './CardOverflowProps'; +import { + CardOverflowProps, + CardOverflowOwnerState, + CardOverflowTypeMap, +} from './CardOverflowProps'; import { CardRowContext } from '../Card/CardContext'; -const useUtilityClasses = (ownerState: CardOverflowProps) => { +const useUtilityClasses = (ownerState: CardOverflowOwnerState) => { const { variant, color } = ownerState; const slots = { root: [ @@ -28,7 +33,7 @@ const CardOverflowRoot = styled('div', { slot: 'Root', overridesResolver: (props, styles) => styles.root, })<{ - ownerState: CardOverflowProps & { + ownerState: CardOverflowOwnerState & { row: boolean; 'data-first-child'?: string; 'data-last-child'?: string; @@ -95,10 +100,12 @@ const CardOverflow = React.forwardRef(function CardOverflow(inProps, ref) { className, component = 'div', children, - color = 'neutral', + color: colorProp = 'neutral', variant = 'plain', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts b/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts index 9063abb4b44a6a..7b69d67fa0545d 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts +++ b/packages/mui-joy/src/CardOverflow/CardOverflowProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type CardOverflowSlot = 'root'; @@ -37,4 +37,4 @@ export type CardOverflowProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface CardOverflowOwnerState extends CardOverflowProps {} +export interface CardOverflowOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts index dd445e4e83e4d2..14f85734a87023 100644 --- a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts +++ b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts @@ -15,6 +15,8 @@ export interface CardOverflowClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -39,6 +41,7 @@ const aspectRatioClasses: CardOverflowClasses = generateUtilityClasses('JoyCardO 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Checkbox/Checkbox.test.js b/packages/mui-joy/src/Checkbox/Checkbox.test.js index 31433c05d9ddaf..48731c34a5c7a9 100644 --- a/packages/mui-joy/src/Checkbox/Checkbox.test.js +++ b/packages/mui-joy/src/Checkbox/Checkbox.test.js @@ -1,6 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, act, createRenderer, fireEvent } from 'test/utils'; +import { + describeConformance, + act, + createRenderer, + fireEvent, + describeJoyColorInversion, +} from 'test/utils'; import Checkbox, { checkboxClasses as classes } from '@mui/joy/Checkbox'; import { ThemeProvider } from '@mui/joy/styles'; @@ -26,6 +32,8 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot', 'propsSpread', 'themeVariants'], })); + describeJoyColorInversion(, { muiName: 'JoyCheckbox', classes }); + it('should have the classes required for Checkbox', () => { expect(classes).to.include.all.keys(['root', 'checked', 'disabled']); }); diff --git a/packages/mui-joy/src/Checkbox/Checkbox.tsx b/packages/mui-joy/src/Checkbox/Checkbox.tsx index 6852f48fd3e29b..f8a82b7786d8ef 100644 --- a/packages/mui-joy/src/Checkbox/Checkbox.tsx +++ b/packages/mui-joy/src/Checkbox/Checkbox.tsx @@ -5,6 +5,7 @@ import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useSwitch } from '@mui/base/SwitchUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import checkboxClasses, { getCheckboxUtilityClass } from './checkboxClasses'; import { CheckboxOwnerState, CheckboxTypeMap } from './CheckboxProps'; @@ -64,15 +65,15 @@ const CheckboxRoot = styled('span', { position: ownerState.overlay ? 'initial' : 'relative', display: 'inline-flex', fontFamily: theme.vars.fontFamily.body, - lineHeight: 'var(--Checkbox-size)', // prevent label from having larger height than the checkbox + lineHeight: 'var(--Checkbox-size)', color: theme.vars.palette.text.primary, [`&.${checkboxClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.plainDisabledColor, + color: theme.variants.plainDisabled?.[ownerState.color!]?.color, }, ...(ownerState.disableIcon && { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, [`&.${checkboxClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }, }), })); @@ -204,7 +205,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { required, value, color: colorProp, - variant, + variant: variantProp, size: sizeProp = 'md', ...other } = props; @@ -212,7 +213,6 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { const formControl = React.useContext(FormControlContext); const disabledProp = inProps.disabled ?? formControl?.disabled ?? disabledExternalProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = formControl?.error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; if (process.env.NODE_ENV !== 'production') { const registerEffect = formControl?.registerEffect; @@ -241,10 +241,17 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { const { getInputProps, checked, disabled, focusVisible } = useSwitch(useCheckboxProps); const isCheckboxActive = checked || indeterminate; + const activeVariant = variantProp || 'solid'; + const inactiveVariant = variantProp || 'outlined'; + const variant = isCheckboxActive ? activeVariant : inactiveVariant; + const { getColor } = useColorInversion(variant); + const color = getColor( + inProps.color, + formControl?.error ? 'danger' : formControl?.color ?? colorProp, + ); + const activeColor = color || 'primary'; const inactiveColor = color || 'neutral'; - const activeVariant = variant || 'solid'; - const inactiveVariant = variant || 'outlined'; const ownerState = { ...props, @@ -254,7 +261,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { overlay, focusVisible, color: isCheckboxActive ? activeColor : inactiveColor, - variant: isCheckboxActive ? activeVariant : inactiveVariant, + variant, size, }; diff --git a/packages/mui-joy/src/Checkbox/CheckboxProps.ts b/packages/mui-joy/src/Checkbox/CheckboxProps.ts index 70e493bbc92da4..f8908b4b2fc01a 100644 --- a/packages/mui-joy/src/Checkbox/CheckboxProps.ts +++ b/packages/mui-joy/src/Checkbox/CheckboxProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { SlotProps, CreateSlotsAndSlotProps } from '../utils/types'; export type CheckboxSlot = 'root' | 'checkbox' | 'action' | 'input' | 'label'; @@ -105,7 +105,7 @@ export type CheckboxProps< }, > = OverrideProps, D>; -export interface CheckboxOwnerState extends CheckboxProps { +export interface CheckboxOwnerState extends ApplyColorInversion { /** * If `true`, the checkbox's focus is visible. */ diff --git a/packages/mui-joy/src/Checkbox/checkboxClasses.ts b/packages/mui-joy/src/Checkbox/checkboxClasses.ts index 41c5a8725fdfcb..0d612a729a6759 100644 --- a/packages/mui-joy/src/Checkbox/checkboxClasses.ts +++ b/packages/mui-joy/src/Checkbox/checkboxClasses.ts @@ -31,6 +31,8 @@ export interface CheckboxClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -67,6 +69,7 @@ const checkboxClasses: CheckboxClasses = generateUtilityClasses('JoyCheckbox', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Chip/Chip.test.js b/packages/mui-joy/src/Chip/Chip.test.js index ba26272452aeb7..7e6548bd2cd823 100644 --- a/packages/mui-joy/src/Chip/Chip.test.js +++ b/packages/mui-joy/src/Chip/Chip.test.js @@ -1,7 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Chip, { chipClasses as classes } from '@mui/joy/Chip'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -34,6 +39,8 @@ describe('', () => { }), ); + describeJoyColorInversion(, { muiName: 'JoyChip', classes }); + it('renders children', () => { const { getByText } = render( diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index a0eed3a96d5103..291428b3771604 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -6,6 +6,7 @@ import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import chipClasses, { getChipUtilityClass } from './chipClasses'; import { ChipProps, ChipOwnerState, ChipTypeMap } from './ChipProps'; import ChipContext from './ChipContext'; @@ -94,7 +95,7 @@ const ChipRoot = styled('div', { verticalAlign: 'middle', boxSizing: 'border-box', [`&.${chipClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }, }, ...(!ownerState.clickable @@ -108,7 +109,7 @@ const ChipRoot = styled('div', { : [ { '--variant-borderWidth': '0px', - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, }, ]), ]; @@ -202,7 +203,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const { children, className, - color = 'primary', + color: colorProp = 'primary', slotProps = {}, onClick, disabled = false, @@ -212,6 +213,8 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { endDecorator, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const clickable = !!onClick || !!slotProps.action; const ownerState: ChipOwnerState = { @@ -284,7 +287,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { }); const chipContextValue = React.useMemo( - () => ({ disabled, variant, color }), + () => ({ disabled, variant, color: color === 'context' ? undefined : color }), [color, disabled, variant], ); diff --git a/packages/mui-joy/src/Chip/ChipProps.ts b/packages/mui-joy/src/Chip/ChipProps.ts index 848c6aff25ca8e..4a39ec66b9615c 100644 --- a/packages/mui-joy/src/Chip/ChipProps.ts +++ b/packages/mui-joy/src/Chip/ChipProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type ChipSlot = 'root' | 'label' | 'action' | 'startDecorator' | 'endDecorator'; @@ -80,7 +80,7 @@ export type ChipProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ChipOwnerState extends ChipProps { +export interface ChipOwnerState extends ApplyColorInversion { /** * If `true`, the chip is clickable. */ diff --git a/packages/mui-joy/src/Chip/chipClasses.ts b/packages/mui-joy/src/Chip/chipClasses.ts index 6c70e90450b5a0..53ecbf73b7d765 100644 --- a/packages/mui-joy/src/Chip/chipClasses.ts +++ b/packages/mui-joy/src/Chip/chipClasses.ts @@ -15,6 +15,8 @@ export interface ChipClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `disabled={true}`. */ disabled: string; /** Styles applied to the endDecorator element if supplied. */ @@ -62,6 +64,7 @@ const chipClasses: ChipClasses = generateUtilityClasses('JoyChip', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'disabled', 'endDecorator', 'focusVisible', diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.test.js b/packages/mui-joy/src/ChipDelete/ChipDelete.test.js index 55fd5647ce561c..ad332a90a290dc 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.test.js +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.test.js @@ -1,7 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, act, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + act, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Chip from '@mui/joy/Chip'; import ChipDelete, { chipDeleteClasses as classes } from '@mui/joy/ChipDelete'; @@ -22,6 +28,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyChipDelete', classes }); + describe('Chip context', () => { it('disabled', () => { const { getByRole } = render( diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx index 19b5309dc467e9..afc5009e1dd622 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx @@ -6,6 +6,7 @@ import { unstable_composeClasses as composeClasses, useButton } from '@mui/base' import { useSlotProps } from '@mui/base/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import Cancel from '../internal/svg-icons/Cancel'; import chipDeleteClasses, { getChipDeleteUtilityClass } from './chipDeleteClasses'; import { ChipDeleteProps, ChipDeleteOwnerState, ChipDeleteTypeMap } from './ChipDeleteProps'; @@ -82,8 +83,9 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) { ...other } = props; const chipContext = React.useContext(ChipContext); - const color = colorProp || chipContext.color || 'primary'; const variant = variantProp || chipVariantMapping[chipContext.variant!] || 'solid'; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp || chipContext.color || 'primary'); const disabled = disabledProp ?? chipContext.disabled; const buttonRef = React.useRef(null); diff --git a/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts b/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts index f94220f58e1001..870dafd8db3405 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts +++ b/packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type ChipDeleteSlot = 'root'; @@ -49,7 +49,7 @@ export type ChipDeleteProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ChipDeleteOwnerState extends ChipDeleteProps { +export interface ChipDeleteOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts index 6b44ed513cc84e..da156ca19badc4 100644 --- a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts +++ b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts @@ -19,6 +19,8 @@ export interface ChipDeleteClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="solid"`. */ @@ -43,6 +45,7 @@ const chipDeleteClasses: ChipDeleteClasses = generateUtilityClasses('JoyChipDele 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSolid', 'variantSoft', diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx index 99422dcd1fb5e1..9f1d8f49f646c4 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import CircularProgress, { circularProgressClasses as classes } from '@mui/joy/CircularProgress'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -28,6 +28,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyCircularProgress', classes }); + describe('prop: determinate', () => { it('should render a determinate circular progress', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index 9477c90cdeadb5..f239e32603fa20 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -7,6 +7,7 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; import { css, keyframes } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import { getCircularProgressUtilityClass } from './circularProgressClasses'; import { @@ -206,7 +207,7 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref const { children, className, - color = 'primary', + color: colorProp = 'primary', size = 'md', variant = 'soft', thickness, @@ -214,6 +215,8 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref value = determinate ? 0 : 25, // `25` is the 1/4 of the circle. ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts index f1e053c23dc860..215e3f7f25c41d 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts +++ b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type CircularProgressSlot = 'root' | 'svg' | 'track' | 'progress'; @@ -68,7 +68,7 @@ export type CircularProgressProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface CircularProgressOwnerState extends CircularProgressProps { +export interface CircularProgressOwnerState extends ApplyColorInversion { /** * @internal the explicit size on the instance: */ diff --git a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts index fa3065de409a7a..deec0ccf079ea6 100644 --- a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts +++ b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts @@ -23,6 +23,8 @@ export interface CircularProgressClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -59,6 +61,7 @@ const circularProgressClasses: CircularProgressClasses = generateUtilityClasses( 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/IconButton/IconButton.test.js b/packages/mui-joy/src/IconButton/IconButton.test.js index 164e66081a72ec..033f9d85ec8657 100644 --- a/packages/mui-joy/src/IconButton/IconButton.test.js +++ b/packages/mui-joy/src/IconButton/IconButton.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import IconButton, { iconButtonClasses as classes } from '@mui/joy/IconButton'; import { ThemeProvider } from '@mui/joy/styles'; @@ -21,6 +21,8 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyIconButton', classes }); + it('by default, should render with the root, variantSolid, sizeMd and colorPrimary classes', () => { const { getByRole } = render(Hello World); const button = getByRole('button'); diff --git a/packages/mui-joy/src/IconButton/IconButton.tsx b/packages/mui-joy/src/IconButton/IconButton.tsx index 5ba634b7627f70..2afbe401ffd568 100644 --- a/packages/mui-joy/src/IconButton/IconButton.tsx +++ b/packages/mui-joy/src/IconButton/IconButton.tsx @@ -4,6 +4,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } import { useButton } from '@mui/base/ButtonUnstyled'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import iconButtonClasses, { getIconButtonUtilityClass } from './iconButtonClasses'; import { IconButtonOwnerState, IconButtonTypeMap, ExtendIconButton } from './IconButtonProps'; @@ -102,11 +103,13 @@ const IconButton = React.forwardRef(function IconButton(inProps, ref) { children, action, component = 'button', - color = 'primary', + color: colorProp = 'primary', variant = 'soft', size = 'md', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const buttonRef = React.useRef(null); const handleRef = useForkRef(buttonRef, ref); diff --git a/packages/mui-joy/src/IconButton/IconButtonProps.ts b/packages/mui-joy/src/IconButton/IconButtonProps.ts index 3aab381f8f04f8..80929e799bd854 100644 --- a/packages/mui-joy/src/IconButton/IconButtonProps.ts +++ b/packages/mui-joy/src/IconButton/IconButtonProps.ts @@ -5,7 +5,7 @@ import { OverridableTypeMap, OverrideProps, } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type IconButtonSlot = 'root'; @@ -73,7 +73,7 @@ export type IconButtonProps< }, > = OverrideProps, D>; -export interface IconButtonOwnerState extends IconButtonProps { +export interface IconButtonOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/IconButton/iconButtonClasses.ts b/packages/mui-joy/src/IconButton/iconButtonClasses.ts index 2d89f71fc67376..3028400351d277 100644 --- a/packages/mui-joy/src/IconButton/iconButtonClasses.ts +++ b/packages/mui-joy/src/IconButton/iconButtonClasses.ts @@ -15,6 +15,8 @@ export interface IconButtonClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -49,6 +51,7 @@ const iconButtonClasses: IconButtonClasses = generateUtilityClasses('JoyIconButt 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Input/Input.test.js b/packages/mui-joy/src/Input/Input.test.js index 5f876f581a5302..77d37fa9f47e35 100644 --- a/packages/mui-joy/src/Input/Input.test.js +++ b/packages/mui-joy/src/Input/Input.test.js @@ -1,7 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { describeConformance, createRenderer, screen, act } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + createRenderer, + screen, + act, +} from 'test/utils'; import Input, { inputClasses as classes } from '@mui/joy/Input'; import { ThemeProvider } from '@mui/joy/styles'; @@ -26,6 +32,8 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyInput', classes }); + it('should have error classes', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.error); diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx index 3d4c6c6ceb4eac..1bd0a414566767 100644 --- a/packages/mui-joy/src/Input/Input.tsx +++ b/packages/mui-joy/src/Input/Input.tsx @@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { EventHandlers } from '@mui/base/utils'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import { InputTypeMap, InputProps, InputOwnerState } from './InputProps'; import inputClasses, { getInputUtilityClass } from './inputClasses'; @@ -289,7 +290,8 @@ const Input = React.forwardRef(function Input(inProps, ref) { const error = inProps.error ?? formControl?.error ?? errorProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, error ? 'danger' : formControl?.color ?? colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Input/inputClasses.ts b/packages/mui-joy/src/Input/inputClasses.ts index 8d11b6abbdcec8..e69e7b353ea00c 100644 --- a/packages/mui-joy/src/Input/inputClasses.ts +++ b/packages/mui-joy/src/Input/inputClasses.ts @@ -25,6 +25,8 @@ export interface InputClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -68,6 +70,7 @@ const inputClasses: InputClasses = generateUtilityClasses('JoyInput', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx index f5c135e0ecb978..721fd5912a9485 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import LinearProgress, { linearProgressClasses as classes } from '@mui/joy/LinearProgress'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -19,6 +19,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyLinearProgress', classes }); + describe('prop: determinate', () => { it('should render a determinate circular progress', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx index bfc14be306af41..66e897e7429416 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx @@ -7,6 +7,7 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; import { css, keyframes } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import { getLinearProgressUtilityClass } from './linearProgressClasses'; import { LinearProgressOwnerState, @@ -146,7 +147,7 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { children, className, component, - color = 'primary', + color: colorProp = 'primary', size = 'md', variant = 'soft', thickness, @@ -155,6 +156,8 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { style, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts index b72f310b3000fe..090e9687da2dd0 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts +++ b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type LinearProgressSlot = 'root'; @@ -56,7 +56,7 @@ export type LinearProgressProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface LinearProgressOwnerState extends LinearProgressProps { +export interface LinearProgressOwnerState extends ApplyColorInversion { /** * @internal the explicit size on the instance: */ diff --git a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts index 9df29535022d2e..0e1437aa8bd3ad 100644 --- a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts +++ b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts @@ -17,6 +17,8 @@ export interface LinearProgressClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -48,6 +50,7 @@ const linearProgressClasses: LinearProgressClasses = generateUtilityClasses('Joy 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Link/Link.test.js b/packages/mui-joy/src/Link/Link.test.js index 858c8875e34b89..e7bb048c3d4739 100644 --- a/packages/mui-joy/src/Link/Link.test.js +++ b/packages/mui-joy/src/Link/Link.test.js @@ -1,7 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { act, createRenderer, fireEvent, describeConformance } from 'test/utils'; +import { + act, + createRenderer, + fireEvent, + describeConformance, + describeJoyColorInversion, +} from 'test/utils'; import Link, { linkClasses as classes } from '@mui/joy/Link'; import Typography from '@mui/joy/Typography'; import { ThemeProvider } from '@mui/joy/styles'; @@ -40,6 +46,8 @@ describe('', () => { }), ); + describeJoyColorInversion(, { muiName: 'JoyLink', classes }); + it('should render children', () => { const { queryByText } = render(Home); diff --git a/packages/mui-joy/src/Link/Link.tsx b/packages/mui-joy/src/Link/Link.tsx index cf2336f9bba2c8..8b6e35cd0b8fee 100644 --- a/packages/mui-joy/src/Link/Link.tsx +++ b/packages/mui-joy/src/Link/Link.tsx @@ -10,6 +10,7 @@ import { import { unstable_extendSxProp as extendSxProp } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import linkClasses, { getLinkUtilityClass } from './linkClasses'; import { LinkProps, LinkOwnerState, LinkTypeMap } from './LinkProps'; @@ -94,9 +95,11 @@ const LinkRoot = styled('a', { borderRadius: theme.vars.radius.xs, padding: 0, // Remove the padding in Firefox cursor: 'pointer', - textDecorationColor: `rgba(${ - theme.vars.palette[ownerState.color!]?.mainChannel - } / var(--Link-underlineOpacity, 0.72))`, + ...(ownerState.color !== 'context' && { + textDecorationColor: `rgba(${ + theme.vars.palette[ownerState.color!]?.mainChannel + } / var(--Link-underlineOpacity, 0.72))`, + }), ...(ownerState.variant ? { paddingBlock: 'min(0.15em, 4px)', @@ -106,10 +109,14 @@ const LinkRoot = styled('a', { }), } : { - color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`, + ...(ownerState.color !== 'context' && { + color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)`, + }), [`&.${linkClasses.disabled}`]: { pointerEvents: 'none', - color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`, + ...(ownerState.color !== 'context' && { + color: `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 0.6)`, + }), }, }), userSelect: 'none', @@ -153,14 +160,17 @@ const LinkRoot = styled('a', { const Link = React.forwardRef(function Link(inProps, ref) { const { - color = 'primary', + color: colorProp = 'primary', textColor, + variant, ...themeProps } = useThemeProps({ props: inProps, name: 'JoyLink', }); + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const nested = React.useContext(TypographyContext); const props = extendSxProp({ ...themeProps, color: textColor }) as LinkProps; @@ -173,7 +183,6 @@ const Link = React.forwardRef(function Link(inProps, ref) { level: levelProp = 'body1', overlay = false, underline = 'hover', - variant, endDecorator, startDecorator, ...other diff --git a/packages/mui-joy/src/Link/LinkProps.ts b/packages/mui-joy/src/Link/LinkProps.ts index 9362780a4e396b..93e5ec8857cd2a 100644 --- a/packages/mui-joy/src/Link/LinkProps.ts +++ b/packages/mui-joy/src/Link/LinkProps.ts @@ -4,6 +4,7 @@ import { ColorPaletteProp, SxProps, SystemProps, + ApplyColorInversion, TypographySystem, VariantProp, } from '../styles/types'; @@ -91,7 +92,7 @@ export type LinkProps< }, > = OverrideProps, D>; -export interface LinkOwnerState extends LinkProps { +export interface LinkOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/Link/linkClasses.ts b/packages/mui-joy/src/Link/linkClasses.ts index 0fa388bdc91d78..bcde0b06ab16c3 100644 --- a/packages/mui-joy/src/Link/linkClasses.ts +++ b/packages/mui-joy/src/Link/linkClasses.ts @@ -15,6 +15,8 @@ export interface LinkClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `disabled={true}`. */ disabled: string; /** State class applied to the root element if the link is keyboard focused. */ @@ -73,6 +75,7 @@ const linkClasses: LinkClasses = generateUtilityClasses('JoyLink', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'focusVisible', 'variantPlain', 'variantOutlined', diff --git a/packages/mui-joy/src/List/List.test.js b/packages/mui-joy/src/List/List.test.js index 6ed684e29fa6ad..948f7d06762ade 100644 --- a/packages/mui-joy/src/List/List.test.js +++ b/packages/mui-joy/src/List/List.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import List, { listClasses as classes } from '@mui/joy/List'; import ListItem from '@mui/joy/ListItem'; @@ -24,6 +24,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyList', classes }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx index b4af0a8cde9116..477b4d3d6f89a7 100644 --- a/packages/mui-joy/src/List/List.tsx +++ b/packages/mui-joy/src/List/List.tsx @@ -7,6 +7,7 @@ import composeClasses from '@mui/base/composeClasses'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import { SelectUnstyledContext } from '@mui/base/SelectUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { ListProps, ListOwnerState, ListTypeMap } from './ListProps'; import { getListUtilityClass } from './listClasses'; import NestedListContext from './NestedListContext'; @@ -160,10 +161,12 @@ const List = React.forwardRef(function List(inProps, ref) { row = false, wrap = false, variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', role: roleProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); let role; if (menuContext || selectContext) { diff --git a/packages/mui-joy/src/List/ListProps.ts b/packages/mui-joy/src/List/ListProps.ts index 4eb6123163ee63..e0c5636694edc1 100644 --- a/packages/mui-joy/src/List/ListProps.ts +++ b/packages/mui-joy/src/List/ListProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type ListSlot = 'root'; @@ -56,7 +56,7 @@ export type ListProps< }, > = OverrideProps, D>; -export interface ListOwnerState extends ListProps { +export interface ListOwnerState extends ApplyColorInversion { /** * @internal * The explicit size specified on the element instance. diff --git a/packages/mui-joy/src/List/listClasses.ts b/packages/mui-joy/src/List/listClasses.ts index 574d3ce2c3220a..431c29214f62c3 100644 --- a/packages/mui-joy/src/List/listClasses.ts +++ b/packages/mui-joy/src/List/listClasses.ts @@ -27,6 +27,8 @@ export interface ListClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -57,6 +59,7 @@ const listClasses: ListClasses = generateUtilityClasses('JoyList', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/ListItem/ListItem.test.js b/packages/mui-joy/src/ListItem/ListItem.test.js index ec32e3140f1660..bc10d7db269953 100644 --- a/packages/mui-joy/src/ListItem/ListItem.test.js +++ b/packages/mui-joy/src/ListItem/ListItem.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import MenuList from '@mui/joy/MenuList'; import List from '@mui/joy/List'; @@ -27,6 +27,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyListItem', classes }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/ListItem/ListItem.tsx b/packages/mui-joy/src/ListItem/ListItem.tsx index b3f7dd57e38124..715af23dd0c435 100644 --- a/packages/mui-joy/src/ListItem/ListItem.tsx +++ b/packages/mui-joy/src/ListItem/ListItem.tsx @@ -9,8 +9,9 @@ import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; -import { ListItemProps, ListItemOwnerState, ListItemTypeMap } from './ListItemProps'; +import { ListItemOwnerState, ListItemTypeMap } from './ListItemProps'; import { getListItemUtilityClass } from './listItemClasses'; import NestedListContext from '../List/NestedListContext'; import RowListContext from '../List/RowListContext'; @@ -112,7 +113,7 @@ const ListItemStartAction = styled('div', { name: 'JoyListItem', slot: 'StartAction', overridesResolver: (props, styles) => styles.startAction, -})<{ ownerState: ListItemProps }>(({ ownerState }) => ({ +})<{ ownerState: ListItemOwnerState }>(({ ownerState }) => ({ display: 'inherit', position: 'absolute', top: ownerState.nested ? 'calc(var(--List-item-minHeight) / 2)' : '50%', @@ -125,7 +126,7 @@ const ListItemEndAction = styled('div', { name: 'JoyListItem', slot: 'StartAction', overridesResolver: (props, styles) => styles.startAction, -})<{ ownerState: ListItemProps }>(({ ownerState }) => ({ +})<{ ownerState: ListItemOwnerState }>(({ ownerState }) => ({ display: 'inherit', position: 'absolute', top: ownerState.nested ? 'calc(var(--List-item-minHeight) / 2)' : '50%', @@ -153,12 +154,14 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { nested = false, sticky = false, variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', startAction, endAction, role: roleProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const [subheaderId, setSubheaderId] = React.useState(''); @@ -178,6 +181,7 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { } const ownerState = { + ...props, sticky, startAction, endAction, @@ -189,7 +193,6 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { nested, component, role, - ...props, }; const classes = useUtilityClasses(ownerState); diff --git a/packages/mui-joy/src/ListItem/ListItemProps.ts b/packages/mui-joy/src/ListItem/ListItemProps.ts index 171328792a7888..2dc941b5e8e9ba 100644 --- a/packages/mui-joy/src/ListItem/ListItemProps.ts +++ b/packages/mui-joy/src/ListItem/ListItemProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type ListItemSlot = 'root' | 'startAction' | 'endAction'; @@ -67,7 +67,7 @@ export type ListItemProps< }, > = OverrideProps, D>; -export interface ListItemOwnerState extends ListItemProps { +export interface ListItemOwnerState extends ApplyColorInversion { /** * If `true`, the element is rendered in a horizontal list. * @internal diff --git a/packages/mui-joy/src/ListItem/listItemClasses.ts b/packages/mui-joy/src/ListItem/listItemClasses.ts index f5feeaebd3fdb7..0dd5c74faa9c56 100644 --- a/packages/mui-joy/src/ListItem/listItemClasses.ts +++ b/packages/mui-joy/src/ListItem/listItemClasses.ts @@ -25,6 +25,8 @@ export interface ListItemClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -54,6 +56,7 @@ const listItemClasses: ListItemClasses = generateUtilityClasses('JoyListItem', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.test.js b/packages/mui-joy/src/ListItemButton/ListItemButton.test.js index abacdc0b318c0d..b3073e17e32d43 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.test.js +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.test.js @@ -1,6 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, act, fireEvent } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + createRenderer, + act, + fireEvent, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import ListItemButton, { listItemButtonClasses as classes } from '@mui/joy/ListItemButton'; @@ -19,6 +25,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyListItemButton', classes }); + it('should render with the selected class', () => { const { getByRole } = render(); expect(getByRole('button')).to.have.class(classes.selected); diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx index a80e2aab288491..1489bb92218028 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } import composeClasses from '@mui/base/composeClasses'; import { useButton } from '@mui/base/ButtonUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { ListItemButtonOwnerState, ExtendListItemButton, @@ -45,7 +46,7 @@ export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOw }), ...(ownerState.disabled && { '--List-decorator-color': - theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + theme.variants?.[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }), WebkitTapHighlightColor: 'transparent', boxSizing: 'border-box', @@ -120,11 +121,14 @@ const ListItemButton = React.forwardRef(function ListItemButton(inProps, ref) { orientation = 'horizontal', role, selected = false, - color = selected ? 'primary' : 'neutral', + color: colorProp = selected ? 'primary' : 'neutral', variant = 'plain', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); + const buttonRef = React.useRef(null); const handleRef = useForkRef(buttonRef, ref); diff --git a/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts b/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts index ecbfdd857f73b5..853bd6a41b7036 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts +++ b/packages/mui-joy/src/ListItemButton/ListItemButtonProps.ts @@ -5,7 +5,7 @@ import { OverridableTypeMap, OverrideProps, } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; import { ListItemButtonClasses } from './listItemButtonClasses'; export type ListItemButtonSlot = 'root'; @@ -94,7 +94,7 @@ export type ListItemButtonProps< }, > = OverrideProps, D>; -export interface ListItemButtonOwnerState extends ListItemButtonProps { +export interface ListItemButtonOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx index e2624ce6d676cd..ff7eb82c9ef59e 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx +++ b/packages/mui-joy/src/ListSubheader/ListSubheader.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import ListSubheader, { listSubheaderClasses as classes } from '@mui/joy/ListSubheader'; import ListSubheaderDispatch from './ListSubheaderContext'; @@ -21,6 +21,11 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { + muiName: 'JoyListSubheader', + classes, + }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx index 0ac1ce43fe88e0..1d34452f0b114c 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx +++ b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx @@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types'; import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { ListSubheaderOwnerState, ListSubheaderTypeMap } from './ListSubheaderProps'; import { getListSubheaderUtilityClass } from './listSubheaderClasses'; import ListSubheaderDispatch from './ListSubheaderContext'; @@ -47,9 +48,10 @@ const ListSubheaderRoot = styled('div', { zIndex: 1, background: 'var(--List-item-stickyBackground)', }), - color: ownerState.color - ? `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)` - : theme.vars.palette.text.tertiary, + color: + ownerState.color && ownerState.color !== 'context' + ? `rgba(${theme.vars.palette[ownerState.color!]?.mainChannel} / 1)` + : theme.vars.palette.text.tertiary, ...theme.variants[ownerState.variant!]?.[ownerState.color!], })); @@ -66,9 +68,11 @@ const ListSubheader = React.forwardRef(function ListSubheader(inProps, ref) { id: idOverride, sticky = false, variant, - color, + color: colorProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const id = useId(idOverride); const setSubheaderId = React.useContext(ListSubheaderDispatch); diff --git a/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts b/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts index 049653316bd689..c0e5a2e18d33e2 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts +++ b/packages/mui-joy/src/ListSubheader/ListSubheaderProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type ListSubheaderSlot = 'root'; @@ -42,4 +42,4 @@ export type ListSubheaderProps< }, > = OverrideProps, D>; -export interface ListSubheaderOwnerState extends ListSubheaderProps {} +export interface ListSubheaderOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts index 5662fa2210b053..de8f9045987e8a 100644 --- a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts +++ b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts @@ -17,6 +17,8 @@ export interface ListSubheaderClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -42,6 +44,7 @@ const listSubheaderClasses: ListSubheaderClasses = generateUtilityClasses('JoyLi 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/Menu/Menu.test.js b/packages/mui-joy/src/Menu/Menu.test.js index 0af5376984aa75..5887bc3724a3e2 100644 --- a/packages/mui-joy/src/Menu/Menu.test.js +++ b/packages/mui-joy/src/Menu/Menu.test.js @@ -1,7 +1,14 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { act, createRenderer, describeConformance, screen, fireEvent } from 'test/utils'; +import { + act, + createRenderer, + describeConformance, + screen, + fireEvent, + describeJoyColorInversion, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Menu, { menuClasses as classes } from '@mui/joy/Menu'; import MenuItem from '@mui/joy/MenuItem'; @@ -30,6 +37,20 @@ describe('Joy

', () => { ], })); + describeJoyColorInversion( + document.createElement('div')} + data-testid="test-element" + />, + { + muiName: 'JoyMenu', + classes, + portalSlot: 'root', + }, + ); + it('should pass onClose prop to Popover', () => { const handleClose = spy(); render( diff --git a/packages/mui-joy/src/Menu/Menu.tsx b/packages/mui-joy/src/Menu/Menu.tsx index 0f1f6dcff328bb..30d228154e76a1 100644 --- a/packages/mui-joy/src/Menu/Menu.tsx +++ b/packages/mui-joy/src/Menu/Menu.tsx @@ -9,10 +9,11 @@ import PopperUnstyled from '@mui/base/PopperUnstyled'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import { styled, useThemeProps } from '../styles'; -import { MenuTypeMap, MenuProps, MenuOwnerState } from './MenuProps'; +import ColorInversion, { useColorInversion } from '../styles/ColorInversion'; +import { MenuTypeMap, MenuOwnerState } from './MenuProps'; import { getMenuUtilityClass } from './menuClasses'; -const useUtilityClasses = (ownerState: MenuProps) => { +const useUtilityClasses = (ownerState: MenuOwnerState) => { const { open, variant, color, size } = ownerState; const slots = { root: [ @@ -62,7 +63,7 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { anchorEl, children, component, - color = 'neutral', + color: colorProp = 'neutral', disablePortal = false, keepMounted = false, id, @@ -73,6 +74,8 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { size = 'md', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = disablePortal ? getColor(inProps.color, colorProp) : colorProp; const { registerItem, @@ -128,17 +131,6 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { ownerState, }); - const contextValue: MenuUnstyledContextType = React.useMemo( - () => ({ - registerItem, - unregisterItem, - getItemState, - getItemProps, - open, - }), - [getItemProps, getItemState, open, registerItem, unregisterItem], - ); - const modifiers = React.useMemo( () => [ { @@ -152,13 +144,38 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { [modifiersProp], ); - return ( + const contextValue: MenuUnstyledContextType = React.useMemo( + () => ({ + registerItem, + unregisterItem, + getItemState, + getItemProps, + open, + }), + [getItemProps, getItemState, open, registerItem, unregisterItem], + ); + + const result = ( - {children} + + {disablePortal ? ( + children + ) : ( + // For portal popup, the children should not inherit color inversion from the upper parent. + {children} + )} + ); + + return disablePortal ? ( + result + ) : ( + // For portal popup, the children should not inherit color inversion from the upper parent. + {result} + ); }) as OverridableComponent; Menu.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/Menu/MenuProps.ts b/packages/mui-joy/src/Menu/MenuProps.ts index 40ec92b94d3553..737ad7697ca151 100644 --- a/packages/mui-joy/src/Menu/MenuProps.ts +++ b/packages/mui-joy/src/Menu/MenuProps.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { OverrideProps, OverridableStringUnion } from '@mui/types'; import { PopperUnstyledProps } from '@mui/base/PopperUnstyled'; import { MenuUnstyledActions } from '@mui/base/MenuUnstyled'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type MenuSlot = 'root'; @@ -56,4 +56,4 @@ export type MenuProps< P = {}, > = OverrideProps, D>; -export interface MenuOwnerState extends MenuProps {} +export interface MenuOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Menu/menuClasses.ts b/packages/mui-joy/src/Menu/menuClasses.ts index d5ca76e710c0bb..89ff38f2e5fc36 100644 --- a/packages/mui-joy/src/Menu/menuClasses.ts +++ b/packages/mui-joy/src/Menu/menuClasses.ts @@ -17,6 +17,8 @@ export interface MenuClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -48,6 +50,7 @@ const menuClasses: MenuClasses = generateUtilityClasses('JoyMenu', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/MenuItem/MenuItem.test.js b/packages/mui-joy/src/MenuItem/MenuItem.test.js index 9ddb0782e50ea7..b4bccbc0ae3297 100644 --- a/packages/mui-joy/src/MenuItem/MenuItem.test.js +++ b/packages/mui-joy/src/MenuItem/MenuItem.test.js @@ -1,7 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { act, describeConformance, createRenderer, fireEvent, screen } from 'test/utils'; +import { + act, + describeConformance, + createRenderer, + fireEvent, + screen, + describeJoyColorInversion, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import MenuItem, { menuItemClasses as classes } from '@mui/joy/MenuItem'; @@ -52,6 +59,14 @@ describe('Joy ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { + muiName: 'JoyMenuItem', + classes, + wrapper: (node) => ( + {node} + ), + }); + it('should render with the variant class', () => { const { getByRole } = render(); expect(getByRole('menuitem')).to.have.class(classes.variantOutlined); diff --git a/packages/mui-joy/src/MenuItem/MenuItem.tsx b/packages/mui-joy/src/MenuItem/MenuItem.tsx index 5d7598cbf394f5..86cf9605281e91 100644 --- a/packages/mui-joy/src/MenuItem/MenuItem.tsx +++ b/packages/mui-joy/src/MenuItem/MenuItem.tsx @@ -6,16 +6,12 @@ import { useSlotProps } from '@mui/base/utils'; import { useMenuItem } from '@mui/base/MenuItemUnstyled'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { getMenuItemUtilityClass } from './menuItemClasses'; -import { - MenuItemProps, - MenuItemOwnerState, - ExtendMenuItem, - MenuItemTypeMap, -} from './MenuItemProps'; +import { MenuItemOwnerState, ExtendMenuItem, MenuItemTypeMap } from './MenuItemProps'; import RowListContext from '../List/RowListContext'; -const useUtilityClasses = (ownerState: MenuItemProps & { focusVisible?: boolean }) => { +const useUtilityClasses = (ownerState: MenuItemOwnerState) => { const { focusVisible, disabled, selected, color, variant } = ownerState; const slots = { root: [ @@ -52,10 +48,12 @@ const MenuItem = React.forwardRef(function MenuItem(inProps, ref) { disabled: disabledProp = false, component = 'li', selected = false, - color = selected ? 'primary' : 'neutral', + color: colorProp = selected ? 'primary' : 'neutral', variant = 'plain', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { getRootProps, disabled, focusVisible } = useMenuItem({ disabled: disabledProp, diff --git a/packages/mui-joy/src/MenuItem/MenuItemProps.ts b/packages/mui-joy/src/MenuItem/MenuItemProps.ts index 0a3c6e690b4c11..50c41b04f2466c 100644 --- a/packages/mui-joy/src/MenuItem/MenuItemProps.ts +++ b/packages/mui-joy/src/MenuItem/MenuItemProps.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { OverridableComponent, OverridableTypeMap, OverrideProps } from '@mui/types'; +import { ApplyColorInversion } from '../styles/types'; import { ListItemButtonProps } from '../ListItemButton/ListItemButtonProps'; export type MenuItemSlot = 'root'; @@ -25,7 +26,7 @@ export type MenuItemProps< }, > = OverrideProps, D>; -export interface MenuItemOwnerState extends MenuItemProps { +export interface MenuItemOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/MenuItem/menuItemClasses.ts b/packages/mui-joy/src/MenuItem/menuItemClasses.ts index 603847d79d2a76..19d99c80a7144c 100644 --- a/packages/mui-joy/src/MenuItem/menuItemClasses.ts +++ b/packages/mui-joy/src/MenuItem/menuItemClasses.ts @@ -21,6 +21,8 @@ export interface MenuItemClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** State class applied to the root element if `variant="plain"`. */ variantPlain: string; /** State class applied to the root element if `variant="soft"`. */ @@ -48,6 +50,7 @@ const menuItemClasses: MenuItemClasses = generateUtilityClasses('JoyMenuItem', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantSoft', 'variantOutlined', diff --git a/packages/mui-joy/src/MenuList/MenuList.test.js b/packages/mui-joy/src/MenuList/MenuList.test.js index a438cbedcaac3a..b51130624181fe 100644 --- a/packages/mui-joy/src/MenuList/MenuList.test.js +++ b/packages/mui-joy/src/MenuList/MenuList.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import MenuList, { menuListClasses as classes } from '@mui/joy/MenuList'; @@ -19,6 +19,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoyMenuList', classes }); + it('should have root className', () => { const { container } = render(); expect(container.firstChild).to.have.class(classes.root); diff --git a/packages/mui-joy/src/MenuList/MenuList.tsx b/packages/mui-joy/src/MenuList/MenuList.tsx index 2187714c21ec02..a5ac9f13d38c5b 100644 --- a/packages/mui-joy/src/MenuList/MenuList.tsx +++ b/packages/mui-joy/src/MenuList/MenuList.tsx @@ -6,12 +6,13 @@ import composeClasses from '@mui/base/composeClasses'; import { useSlotProps } from '@mui/base/utils'; import { useMenu, MenuUnstyledContext, MenuUnstyledContextType } from '@mui/base/MenuUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; -import { MenuListProps, MenuListOwnerState, MenuListTypeMap } from './MenuListProps'; +import { MenuListOwnerState, MenuListTypeMap } from './MenuListProps'; import { getMenuListUtilityClass } from './menuListClasses'; -const useUtilityClasses = (ownerState: MenuListProps) => { +const useUtilityClasses = (ownerState: MenuListOwnerState) => { const { variant, color, size } = ownerState; const slots = { root: [ @@ -60,9 +61,11 @@ const MenuList = React.forwardRef(function MenuList(inProps, ref) { children, size = 'md', variant = 'outlined', - color = 'neutral', + color: colorProp = 'neutral', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { registerItem, diff --git a/packages/mui-joy/src/MenuList/MenuListProps.ts b/packages/mui-joy/src/MenuList/MenuListProps.ts index 2d4b47180c585f..0c9ae59317953b 100644 --- a/packages/mui-joy/src/MenuList/MenuListProps.ts +++ b/packages/mui-joy/src/MenuList/MenuListProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { OverrideProps, OverridableStringUnion } from '@mui/types'; import { MenuUnstyledActions } from '@mui/base/MenuUnstyled'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type MenuListSlot = 'root'; @@ -47,4 +47,4 @@ export type MenuListProps< }, > = OverrideProps, D>; -export interface MenuListOwnerState extends MenuListProps {} +export interface MenuListOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/MenuList/menuListClasses.ts b/packages/mui-joy/src/MenuList/menuListClasses.ts index 3323c394b6651a..7d80b26fdbaeab 100644 --- a/packages/mui-joy/src/MenuList/menuListClasses.ts +++ b/packages/mui-joy/src/MenuList/menuListClasses.ts @@ -21,6 +21,8 @@ export interface MenuListClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -49,6 +51,7 @@ const menuClasses: MenuListClasses = generateUtilityClasses('JoyMenuList', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Modal/modalClasses.ts b/packages/mui-joy/src/Modal/modalClasses.ts index 9eec63c1a2ca20..8c89001fde5e54 100644 --- a/packages/mui-joy/src/Modal/modalClasses.ts +++ b/packages/mui-joy/src/Modal/modalClasses.ts @@ -5,26 +5,6 @@ export interface ModalClasses { root: string; /** Styles applied to the backdrop element. */ backdrop: string; - /** Styles applied to the root element if `color="primary"`. */ - colorPrimary: string; - /** Styles applied to the root element if `color="neutral"`. */ - colorNeutral: string; - /** Styles applied to the root element if `color="danger"`. */ - colorDanger: string; - /** Styles applied to the root element if `color="info"`. */ - colorInfo: string; - /** Styles applied to the root element if `color="success"`. */ - colorSuccess: string; - /** Styles applied to the root element if `color="warning"`. */ - colorWarning: string; - /** Styles applied to the root element if `variant="plain"`. */ - variantPlain: string; - /** Styles applied to the root element if `variant="outlined"`. */ - variantOutlined: string; - /** Styles applied to the root element if `variant="soft"`. */ - variantSoft: string; - /** Styles applied to the root element if `variant="solid"`. */ - variantSolid: string; } export type ModalClassKey = keyof ModalClasses; @@ -33,19 +13,6 @@ export function getModalUtilityClass(slot: string): string { return generateUtilityClass('JoyModal', slot); } -const modalClasses: ModalClasses = generateUtilityClasses('JoyModal', [ - 'root', - 'backdrop', - 'colorPrimary', - 'colorNeutral', - 'colorDanger', - 'colorInfo', - 'colorSuccess', - 'colorWarning', - 'variantPlain', - 'variantOutlined', - 'variantSoft', - 'variantSolid', -]); +const modalClasses: ModalClasses = generateUtilityClasses('JoyModal', ['root', 'backdrop']); export default modalClasses; diff --git a/packages/mui-joy/src/ModalClose/ModalClose.test.tsx b/packages/mui-joy/src/ModalClose/ModalClose.test.tsx index 5102ea7571143e..869942344145a8 100644 --- a/packages/mui-joy/src/ModalClose/ModalClose.test.tsx +++ b/packages/mui-joy/src/ModalClose/ModalClose.test.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { createRenderer, describeConformance, fireEvent } from 'test/utils'; +import { + createRenderer, + describeConformance, + describeJoyColorInversion, + fireEvent, +} from 'test/utils'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Modal from '@mui/joy/Modal'; @@ -22,6 +27,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyModalClose', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/ModalClose/ModalClose.tsx b/packages/mui-joy/src/ModalClose/ModalClose.tsx index 48dd83236fca04..4431277dc69833 100644 --- a/packages/mui-joy/src/ModalClose/ModalClose.tsx +++ b/packages/mui-joy/src/ModalClose/ModalClose.tsx @@ -6,15 +6,16 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { useSlotProps } from '@mui/base/utils'; import { useButton } from '@mui/base/ButtonUnstyled'; import { useThemeProps, styled } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { StyledIconButton } from '../IconButton/IconButton'; import { getModalCloseUtilityClass } from './modalCloseClasses'; -import { ModalCloseProps, ModalCloseTypeMap } from './ModalCloseProps'; +import { ModalCloseProps, ModalCloseOwnerState, ModalCloseTypeMap } from './ModalCloseProps'; import CloseIcon from '../internal/svg-icons/Close'; import CloseModalContext from '../Modal/CloseModalContext'; import ModalDialogSizeContext from '../ModalDialog/ModalDialogSizeContext'; import ModalDialogVariantColorContext from '../ModalDialog/ModalDialogVariantColorContext'; -const useUtilityClasses = (ownerState: ModalCloseProps & { focusVisible?: boolean }) => { +const useUtilityClasses = (ownerState: ModalCloseOwnerState) => { const { variant, color, disabled, focusVisible, size } = ownerState; const slots = { @@ -35,7 +36,7 @@ export const ModalCloseRoot = styled(StyledIconButton, { name: 'JoyModalClose', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: ModalCloseProps }>(({ ownerState, theme }) => ({ +})<{ ownerState: ModalCloseOwnerState }>(({ ownerState, theme }) => ({ ...(ownerState.size === 'sm' && { '--IconButton-size': '28px', }), @@ -79,9 +80,10 @@ const ModalClose = React.forwardRef(function ModalClose(inProps, ref) { const closeModalContext = React.useContext(CloseModalContext); const modalDialogVariantColor = React.useContext(ModalDialogVariantColorContext); - const color = inProps.color ?? modalDialogVariantColor?.color ?? colorProp; const variant = inProps.variant ?? modalDialogVariantMapping[modalDialogVariantColor?.variant!] ?? variantProp; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, modalDialogVariantColor?.color ?? colorProp); const modalDialogSize = React.useContext(ModalDialogSizeContext); const size = inProps.size ?? modalDialogSize ?? sizeProp; diff --git a/packages/mui-joy/src/ModalClose/ModalCloseProps.ts b/packages/mui-joy/src/ModalClose/ModalCloseProps.ts index 3c156fff66a8da..2ce8c7bd0b2734 100644 --- a/packages/mui-joy/src/ModalClose/ModalCloseProps.ts +++ b/packages/mui-joy/src/ModalClose/ModalCloseProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type ModalCloseSlot = 'root'; @@ -38,4 +38,6 @@ export type ModalCloseProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ModalCloseOwnerState extends ModalCloseProps {} +export interface ModalCloseOwnerState extends ApplyColorInversion { + focusVisible?: boolean; +} diff --git a/packages/mui-joy/src/ModalClose/modalCloseClasses.ts b/packages/mui-joy/src/ModalClose/modalCloseClasses.ts index 6b2be6e619c8f1..7127b7989791f2 100644 --- a/packages/mui-joy/src/ModalClose/modalCloseClasses.ts +++ b/packages/mui-joy/src/ModalClose/modalCloseClasses.ts @@ -15,6 +15,8 @@ export interface ModalCloseClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -45,6 +47,7 @@ const modalCloseClasses: ModalCloseClasses = generateUtilityClasses('JoyModalClo 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx index e6671c5eaafc34..856f392338d706 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx +++ b/packages/mui-joy/src/ModalDialog/ModalDialog.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import ModalDialog, { modalDialogClasses as classes } from '@mui/joy/ModalDialog'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -20,6 +20,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoyModalDialog', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByRole } = render(); diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx index 078b57a1d372e2..7caaab82668fb8 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx +++ b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx @@ -8,13 +8,14 @@ import { unstable_isMuiElement as isMuiElement, } from '@mui/utils'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { SheetRoot } from '../Sheet/Sheet'; import { getModalDialogUtilityClass } from './modalDialogClasses'; -import { ModalDialogProps, ModalDialogTypeMap } from './ModalDialogProps'; +import { ModalDialogProps, ModalDialogOwnerState, ModalDialogTypeMap } from './ModalDialogProps'; import ModalDialogSizeContext from './ModalDialogSizeContext'; import ModalDialogVariantColorContext from './ModalDialogVariantColorContext'; -const useUtilityClasses = (ownerState: ModalDialogProps) => { +const useUtilityClasses = (ownerState: ModalDialogOwnerState) => { const { variant, color, size, layout } = ownerState; const slots = { @@ -34,7 +35,7 @@ const ModalDialogRoot = styled(SheetRoot, { name: 'JoyModalDialog', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: ModalDialogProps }>(({ theme, ownerState }) => ({ +})<{ ownerState: ModalDialogOwnerState }>(({ theme, ownerState }) => ({ // Divider integration '--Divider-inset': 'calc(-1 * var(--ModalDialog-padding))', '--ModalClose-radius': @@ -90,13 +91,15 @@ const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) { const { className, children, - color = 'neutral', + color: colorProp = 'neutral', component = 'div', variant = 'outlined', size = 'md', layout = 'center', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, @@ -109,7 +112,10 @@ const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) { const classes = useUtilityClasses(ownerState); - const contextValue = React.useMemo(() => ({ variant, color }), [color, variant]); + const contextValue = React.useMemo( + () => ({ variant, color: color === 'context' ? undefined : color }), + [color, variant], + ); return ( diff --git a/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts b/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts index 26ed4589bb60ce..9ff71da92425dd 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts +++ b/packages/mui-joy/src/ModalDialog/ModalDialogProps.ts @@ -1,6 +1,6 @@ -import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type ModalDialogSlot = 'root'; @@ -48,4 +48,4 @@ export type ModalDialogProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface ModalDialogOwnerState extends ModalDialogProps {} +export interface ModalDialogOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts b/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts index 6b83d66ae7fb85..409b21143c7bf0 100644 --- a/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts +++ b/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts @@ -15,6 +15,8 @@ export interface ModalDialogClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -49,6 +51,7 @@ const modalDialogClasses: ModalDialogClasses = generateUtilityClasses('JoyModalD 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Option/Option.tsx b/packages/mui-joy/src/Option/Option.tsx index c7a8ab4683f327..da68b786665160 100644 --- a/packages/mui-joy/src/Option/Option.tsx +++ b/packages/mui-joy/src/Option/Option.tsx @@ -6,6 +6,7 @@ import { useSlotProps } from '@mui/base/utils'; import { SelectUnstyledContext } from '@mui/base/SelectUnstyled'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import { OptionOwnerState, ExtendOption, OptionTypeMap } from './OptionProps'; import optionClasses, { getOptionUtilityClass } from './optionClasses'; import RowListContext from '../List/RowListContext'; @@ -24,11 +25,14 @@ const OptionRoot = styled(StyledListItemButton as unknown as 'button', { name: 'JoyOption', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: OptionOwnerState }>(({ theme, ownerState }) => ({ - [`&.${optionClasses.highlighted}`]: { - backgroundColor: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}HoverBg`], - }, -})); +})<{ ownerState: OptionOwnerState }>(({ theme, ownerState }) => { + const variantStyle = theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!]; + return { + [`&.${optionClasses.highlighted}`]: { + backgroundColor: variantStyle?.backgroundColor, + }, + }; +}); const Option = React.forwardRef(function Option(inProps, ref) { const props = useThemeProps({ @@ -64,10 +68,8 @@ const Option = React.forwardRef(function Option(inProps, ref) { const optionProps = selectContext.getOptionProps(selectOption); const listboxRef = selectContext.listboxRef; - let color = colorProp; - if (optionState.selected && !inProps.color) { - color = 'primary'; - } + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, optionState.selected ? 'primary' : colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Option/OptionProps.ts b/packages/mui-joy/src/Option/OptionProps.ts index 384f067fc8158c..23fd1e214f3988 100644 --- a/packages/mui-joy/src/Option/OptionProps.ts +++ b/packages/mui-joy/src/Option/OptionProps.ts @@ -6,7 +6,7 @@ import { OverrideProps, } from '@mui/types'; import { OptionState } from '@mui/base/ListboxUnstyled'; -import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types'; export type OptionSlot = 'root'; @@ -64,7 +64,9 @@ export type OptionProps< }, > = OverrideProps, D>; -export interface OptionOwnerState extends Omit, OptionState {} +export interface OptionOwnerState + extends ApplyColorInversion>, + OptionState {} export type ExtendOption = (( props: OverrideProps, 'a'>, diff --git a/packages/mui-joy/src/Radio/Radio.test.js b/packages/mui-joy/src/Radio/Radio.test.js index 05dc10742a9edc..a3460d781e2260 100644 --- a/packages/mui-joy/src/Radio/Radio.test.js +++ b/packages/mui-joy/src/Radio/Radio.test.js @@ -1,6 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, act, createRenderer, fireEvent } from 'test/utils'; +import { + describeConformance, + act, + createRenderer, + fireEvent, + describeJoyColorInversion, +} from 'test/utils'; import Radio, { radioClasses as classes } from '@mui/joy/Radio'; import { ThemeProvider } from '@mui/joy/styles'; @@ -27,6 +33,8 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot', 'propsSpread'], })); + describeJoyColorInversion(, { muiName: 'JoyRadio', classes }); + it('should have the classes required for Radio', () => { expect(classes).to.include.all.keys(['root', 'checked', 'disabled']); }); diff --git a/packages/mui-joy/src/Radio/Radio.tsx b/packages/mui-joy/src/Radio/Radio.tsx index 2cf0c965c9d0ec..56e986738383a1 100644 --- a/packages/mui-joy/src/Radio/Radio.tsx +++ b/packages/mui-joy/src/Radio/Radio.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useSwitch } from '@mui/base/SwitchUnstyled'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import radioClasses, { getRadioUtilityClass } from './radioClasses'; import { RadioOwnerState, RadioTypeMap } from './RadioProps'; @@ -80,12 +81,12 @@ const RadioRoot = styled('span', { lineHeight: 'var(--Radio-size)', // prevent label from having larger height than the checkbox color: theme.vars.palette.text.primary, [`&.${radioClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.plainDisabledColor, + color: theme.variants.plainDisabled?.[ownerState.color!]?.color, }, ...(ownerState.disableIcon && { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}Color`], + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, [`&.${radioClasses.disabled}`]: { - color: theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, }, }), ...(ownerState['data-parent'] === 'RadioGroup' && @@ -236,13 +237,14 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { onFocusVisible, readOnly, required, - color, + color: colorProp, variant = 'outlined', size: sizeProp = 'md', uncheckedIcon, value, ...other } = props; + const { getColor } = useColorInversion(variant); const formControl = React.useContext(FormControlContext); @@ -262,10 +264,10 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { const radioGroup = React.useContext(RadioGroupContext); const activeColor = formControl?.error ? 'danger' - : inProps.color ?? formControl?.color ?? color ?? 'primary'; + : inProps.color ?? formControl?.color ?? colorProp ?? 'primary'; const inactiveColor = formControl?.error ? 'danger' - : inProps.color ?? formControl?.color ?? color ?? 'neutral'; + : inProps.color ?? formControl?.color ?? colorProp ?? 'neutral'; const size = inProps.size || formControl?.size || radioGroup?.size || sizeProp; const name = inProps.name || radioGroup?.name || nameProp; const disableIcon = inProps.disableIcon || radioGroup?.disableIcon || disableIconProp; @@ -287,12 +289,14 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { const { getInputProps, checked, disabled, focusVisible } = useSwitch(useRadioProps); + const color = getColor(inProps.color, checked ? activeColor : inactiveColor); + const ownerState = { ...props, checked, disabled, focusVisible, - color: checked ? activeColor : inactiveColor, + color, variant, size, disableIcon, diff --git a/packages/mui-joy/src/Radio/RadioProps.ts b/packages/mui-joy/src/Radio/RadioProps.ts index 79071df41384dc..8dab90b9e4eb65 100644 --- a/packages/mui-joy/src/Radio/RadioProps.ts +++ b/packages/mui-joy/src/Radio/RadioProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type RadioSlot = 'root' | 'radio' | 'icon' | 'action' | 'input' | 'label'; @@ -93,7 +93,7 @@ export type RadioProps< }, > = OverrideProps, D>; -export interface RadioOwnerState extends RadioProps { +export interface RadioOwnerState extends ApplyColorInversion { /** * If `true`, the element's focus is visible. */ diff --git a/packages/mui-joy/src/Radio/radioClasses.ts b/packages/mui-joy/src/Radio/radioClasses.ts index 1e7ed3a12deb13..876afacb826e33 100644 --- a/packages/mui-joy/src/Radio/radioClasses.ts +++ b/packages/mui-joy/src/Radio/radioClasses.ts @@ -31,6 +31,8 @@ export interface RadioClasses { colorSuccess: string; /** Class name applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Class name applied to the root element if `size="sm"`. */ sizeSm: string; /** Class name applied to the root element if `size="md"`. */ @@ -67,6 +69,7 @@ const radioClasses: RadioClasses = generateUtilityClasses('JoyRadio', [ 'colorNeutral', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Select/Select.test.tsx b/packages/mui-joy/src/Select/Select.test.tsx index 1a262a6a4cd660..c0d030a576ac89 100644 --- a/packages/mui-joy/src/Select/Select.test.tsx +++ b/packages/mui-joy/src/Select/Select.test.tsx @@ -1,7 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy, stub } from 'sinon'; -import { describeConformance, act, createRenderer, fireEvent, screen } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + act, + createRenderer, + fireEvent, + screen, +} from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Select, { selectClasses as classes, SelectOption } from '@mui/joy/Select'; import Option from '@mui/joy/Option'; @@ -30,6 +37,12 @@ describe('Joy , { + muiName: 'JoySelect', + classes, + portalSlot: 'listbox', + }); + it('should be able to mount the component', () => { render( } diff --git a/packages/mui-joy/src/Select/SelectProps.ts b/packages/mui-joy/src/Select/SelectProps.ts index 33b6be5fc3e2ae..6aad3fd7f8e70f 100644 --- a/packages/mui-joy/src/Select/SelectProps.ts +++ b/packages/mui-joy/src/Select/SelectProps.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; import { PopperUnstyledOwnProps } from '@mui/base/PopperUnstyled'; import { SelectOption, SelectUnstyledCommonProps } from '@mui/base/SelectUnstyled'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type { SelectOption } from '@mui/base/SelectUnstyled'; @@ -127,7 +127,8 @@ export type SelectOwnProps = SelectStaticProps & value?: TValue | null; }; -export interface SelectOwnerState extends SelectOwnProps { +export interface SelectOwnerState + extends ApplyColorInversion> { /** * If `true`, the select button is active. */ diff --git a/packages/mui-joy/src/Select/selectClasses.ts b/packages/mui-joy/src/Select/selectClasses.ts index 3f50c14c13fe04..abee1ec07e1a77 100644 --- a/packages/mui-joy/src/Select/selectClasses.ts +++ b/packages/mui-joy/src/Select/selectClasses.ts @@ -27,6 +27,8 @@ export interface SelectClasses { colorSuccess: string; /** Styles applied to the root slot if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root slot if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root slot if `variant="outlined"`. */ @@ -69,6 +71,7 @@ const selectClasses: SelectClasses = generateUtilityClasses('JoySelect', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Sheet/Sheet.test.js b/packages/mui-joy/src/Sheet/Sheet.test.js index f8b08e15209bdd..a94817da4dd7e3 100644 --- a/packages/mui-joy/src/Sheet/Sheet.test.js +++ b/packages/mui-joy/src/Sheet/Sheet.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, describeConformance } from 'test/utils'; +import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Sheet, { sheetClasses as classes } from '@mui/joy/Sheet'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -21,6 +21,8 @@ describe('', () => { skip: ['classesRoot', 'componentsProp'], })); + describeJoyColorInversion(, { muiName: 'JoySheet', classes }); + describe('prop: variant', () => { it('plain by default', () => { const { getByTestId } = render(Hello World); diff --git a/packages/mui-joy/src/Sheet/Sheet.tsx b/packages/mui-joy/src/Sheet/Sheet.tsx index 7cdc3737c3692e..e5387555386955 100644 --- a/packages/mui-joy/src/Sheet/Sheet.tsx +++ b/packages/mui-joy/src/Sheet/Sheet.tsx @@ -11,7 +11,7 @@ import { getSheetUtilityClass } from './sheetClasses'; import { SheetProps, SheetOwnerState, SheetTypeMap } from './SheetProps'; import { ColorInversionProvider, useColorInversion } from '../styles/ColorInversion'; -const useUtilityClasses = (ownerState: SheetProps) => { +const useUtilityClasses = (ownerState: SheetOwnerState) => { const { variant, color } = ownerState; const slots = { @@ -50,7 +50,9 @@ export const SheetRoot = styled('div', { position: 'relative', }, variantStyle, - ownerState.invertedColors && theme.colorInversion[ownerState.variant!]?.[ownerState.color!], + ownerState.color !== 'context' && + ownerState.invertedColors && + theme.colorInversion[ownerState.variant!]?.[ownerState.color!], ]; }); diff --git a/packages/mui-joy/src/Sheet/SheetProps.ts b/packages/mui-joy/src/Sheet/SheetProps.ts index d00a6cff339621..92efe7faf452ac 100644 --- a/packages/mui-joy/src/Sheet/SheetProps.ts +++ b/packages/mui-joy/src/Sheet/SheetProps.ts @@ -1,6 +1,6 @@ import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type SheetSlot = 'root'; @@ -41,4 +41,4 @@ export type SheetProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SheetOwnerState extends SheetProps {} +export interface SheetOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Sheet/sheetClasses.ts b/packages/mui-joy/src/Sheet/sheetClasses.ts index 7e255adfd4659e..55ced58d433eef 100644 --- a/packages/mui-joy/src/Sheet/sheetClasses.ts +++ b/packages/mui-joy/src/Sheet/sheetClasses.ts @@ -15,6 +15,8 @@ export interface SheetClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `variant="plain"`. */ variantPlain: string; /** Styles applied to the root element if `variant="outlined"`. */ @@ -39,6 +41,7 @@ const sheetClasses: SheetClasses = generateUtilityClasses('JoySheet', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Slider/Slider.test.js b/packages/mui-joy/src/Slider/Slider.test.js index aef1b287b00913..29ddaaf2f97ecb 100644 --- a/packages/mui-joy/src/Slider/Slider.test.js +++ b/packages/mui-joy/src/Slider/Slider.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer } from 'test/utils'; +import { describeConformance, createRenderer, describeJoyColorInversion } from 'test/utils'; import Slider, { sliderClasses as classes } from '@mui/joy/Slider'; import { ThemeProvider } from '@mui/joy/styles'; @@ -36,6 +36,8 @@ describe('', () => { }), ); + describeJoyColorInversion(, { muiName: 'JoySlider', classes }); + it('should render the rail as the first child of the Slider', () => { const { container: { firstChild: root }, diff --git a/packages/mui-joy/src/Slider/Slider.tsx b/packages/mui-joy/src/Slider/Slider.tsx index 5b1647aa98a7a4..771748831a9ce5 100644 --- a/packages/mui-joy/src/Slider/Slider.tsx +++ b/packages/mui-joy/src/Slider/Slider.tsx @@ -9,6 +9,7 @@ import { OverridableComponent } from '@mui/types'; import { useSlider } from '@mui/base/SliderUnstyled'; import { isHostComponent } from '@mui/base/utils'; import { useThemeProps, styled, Theme } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import useSlot from '../utils/useSlot'; import sliderClasses, { getSliderUtilityClass } from './sliderClasses'; import { SliderTypeMap, SliderOwnerState } from './SliderProps'; @@ -251,6 +252,7 @@ const SliderThumb = styled('span', { }), '&::before': { // use pseudo element to create thumb's ring + boxSizing: 'border-box', content: '""', display: 'block', position: 'absolute', @@ -425,11 +427,13 @@ const Slider = React.forwardRef(function Slider(inProps, ref) { valueLabelDisplay = 'off', valueLabelFormat = Identity, isRtl = false, - color = 'primary', + color: colorProp = 'primary', size = 'md', variant = 'solid', ...other } = props; + const { getColor } = useColorInversion('solid'); + const color = getColor(inProps.color, colorProp); const ownerState = { ...props, diff --git a/packages/mui-joy/src/Slider/SliderProps.ts b/packages/mui-joy/src/Slider/SliderProps.ts index 841fa06613cebc..7fb6fcd843503f 100644 --- a/packages/mui-joy/src/Slider/SliderProps.ts +++ b/packages/mui-joy/src/Slider/SliderProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { SliderUnstyledOwnProps } from '@mui/base/SliderUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type SliderSlot = @@ -65,7 +65,7 @@ export type SliderProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SliderOwnerState extends SliderProps { +export interface SliderOwnerState extends ApplyColorInversion { /** * If `true`, the thumb is in dragging state. */ diff --git a/packages/mui-joy/src/Slider/sliderClasses.ts b/packages/mui-joy/src/Slider/sliderClasses.ts index 6ef6a2c0d152fb..286229b5239959 100644 --- a/packages/mui-joy/src/Slider/sliderClasses.ts +++ b/packages/mui-joy/src/Slider/sliderClasses.ts @@ -45,6 +45,16 @@ export interface SliderClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; + /** Styles applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** Styles applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** Styles applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** Styles applied to the root element if `variant="solid"`. */ + variantSolid: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -84,6 +94,11 @@ const sliderClasses: SliderClasses = generateUtilityClasses('JoySlider', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', + 'variantPlain', + 'variantOutlined', + 'variantSoft', + 'variantSolid', 'disabled', 'sizeSm', 'sizeMd', diff --git a/packages/mui-joy/src/Switch/Switch.test.js b/packages/mui-joy/src/Switch/Switch.test.js index 43f17439620928..89de7a679355a2 100644 --- a/packages/mui-joy/src/Switch/Switch.test.js +++ b/packages/mui-joy/src/Switch/Switch.test.js @@ -1,6 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, act, createRenderer, fireEvent, screen } from 'test/utils'; +import { + describeConformance, + describeJoyColorInversion, + act, + createRenderer, + fireEvent, + screen, +} from 'test/utils'; import Switch, { switchClasses as classes } from '@mui/joy/Switch'; import { ThemeProvider } from '@mui/joy/styles'; @@ -33,6 +40,8 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(, { muiName: 'JoySwitch', classes }); + it('should pass `slotProps` down to slots', () => { const { container } = render( { const switchColorVariables = ({ theme, ownerState }: { theme: Theme; ownerState: SwitchOwnerState }) => (data: { state?: 'Hover' | 'Disabled' } = {}) => { - const variant = ownerState.variant; - const color = ownerState.color; + const styles = + theme.variants[`${ownerState.variant!}${data.state || ''}`]?.[ownerState.color!] || {}; return { - '--Switch-track-background': theme.vars.palette[color!]?.[`${variant!}${data.state || ''}Bg`], - '--Switch-track-color': theme.vars.palette[color!]?.[`${variant!}Color`], + '--Switch-track-background': styles.backgroundColor, + '--Switch-track-color': styles.color, '--Switch-track-borderColor': - variant === 'outlined' - ? theme.vars.palette[color!]?.[`${variant!}${data.state || ''}Border`] - : 'currentColor', - '--Switch-thumb-background': - theme.vars.palette[color!]?.[`${variant!}${data.state || ''}Color`], - '--Switch-thumb-color': theme.vars.palette[color!]?.[`${variant!}Bg`], + ownerState.variant === 'outlined' ? styles.borderColor : 'currentColor', + '--Switch-thumb-background': styles.color, + '--Switch-thumb-color': styles.backgroundColor, }; }; @@ -256,7 +254,11 @@ const Switch = React.forwardRef(function Switch(inProps, ref) { const disabledProp = inProps.disabled ?? formControl?.disabled ?? disabledExternalProp; const size = inProps.size ?? formControl?.size ?? sizeProp; - const color = formControl?.error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const { getColor } = useColorInversion(variant); + const color = getColor( + inProps.color, + formControl?.error ? 'danger' : formControl?.color ?? colorProp, + ); const useSwitchProps = { checked: checkedProp, diff --git a/packages/mui-joy/src/Switch/SwitchProps.ts b/packages/mui-joy/src/Switch/SwitchProps.ts index b4a4dfa0eecc0b..b74a26de10f069 100644 --- a/packages/mui-joy/src/Switch/SwitchProps.ts +++ b/packages/mui-joy/src/Switch/SwitchProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export type SwitchSlot = @@ -70,7 +70,7 @@ export type SwitchProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface SwitchOwnerState extends SwitchProps { +export interface SwitchOwnerState extends ApplyColorInversion { /** * If `true`, the switch's focus is visible. */ diff --git a/packages/mui-joy/src/Switch/switchClasses.ts b/packages/mui-joy/src/Switch/switchClasses.ts index fe503acf7b9803..35c8b26356fb1b 100644 --- a/packages/mui-joy/src/Switch/switchClasses.ts +++ b/packages/mui-joy/src/Switch/switchClasses.ts @@ -29,6 +29,8 @@ export interface SwitchClasses { colorSuccess: string; /** Styles applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Styles applied to the root element if `size="sm"`. */ sizeSm: string; /** Styles applied to the root element if `size="md"`. */ @@ -68,6 +70,7 @@ const switchClasses: SwitchClasses = generateUtilityClasses('JoySwitch', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'sizeSm', 'sizeMd', 'sizeLg', diff --git a/packages/mui-joy/src/Tab/Tab.test.tsx b/packages/mui-joy/src/Tab/Tab.test.tsx index b1eb018ef31e3f..eb5b1585a47462 100644 --- a/packages/mui-joy/src/Tab/Tab.test.tsx +++ b/packages/mui-joy/src/Tab/Tab.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { TabsContext, useTabs, TabsUnstyledProps } from '@mui/base/TabsUnstyled'; import { ThemeProvider } from '@mui/joy/styles'; import Tab, { tabClasses as classes } from '@mui/joy/Tab'; @@ -26,6 +26,12 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { + muiName: 'JoyTab', + classes, + wrapper: (node) => {node}, + }); + it('prop: variant', () => { render( diff --git a/packages/mui-joy/src/Tab/Tab.tsx b/packages/mui-joy/src/Tab/Tab.tsx index 883c4134e7f0f5..0281613caa9e72 100644 --- a/packages/mui-joy/src/Tab/Tab.tsx +++ b/packages/mui-joy/src/Tab/Tab.tsx @@ -8,6 +8,7 @@ import { useSlotProps } from '@mui/base/utils'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { getTabUtilityClass } from './tabClasses'; import { TabOwnerState, TabTypeMap } from './TabProps'; import RowListContext from '../List/RowListContext'; @@ -44,9 +45,9 @@ const TabRoot = styled(StyledListItemButton, { boxShadow: theme.shadow.sm, fontWeight: 'initial', ...(!variantStyle?.backgroundColor && { - backgroundColor: theme.vars.palette.background.body, + backgroundColor: theme.vars.palette.background.surface, '&:hover': { - backgroundColor: theme.vars.palette.background.body, + backgroundColor: theme.vars.palette.background.surface, }, }), }), @@ -72,9 +73,11 @@ const Tab = React.forwardRef(function Tab(inProps, ref) { component = 'button', orientation = 'horizontal', variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const tabRef = React.useRef(); const handleRef = useForkRef(tabRef, ref); diff --git a/packages/mui-joy/src/Tab/TabProps.ts b/packages/mui-joy/src/Tab/TabProps.ts index f8baf034183b6a..6a5463e7973a16 100644 --- a/packages/mui-joy/src/Tab/TabProps.ts +++ b/packages/mui-joy/src/Tab/TabProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type TabSlot = 'root'; @@ -56,7 +56,7 @@ export type TabProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface TabOwnerState extends TabProps { +export interface TabOwnerState extends ApplyColorInversion { /** * If `true`, the tab is activated by mouse or keyboard. */ diff --git a/packages/mui-joy/src/Tab/tabClasses.ts b/packages/mui-joy/src/Tab/tabClasses.ts index ae8cd8c5cf673f..0dea454def0396 100644 --- a/packages/mui-joy/src/Tab/tabClasses.ts +++ b/packages/mui-joy/src/Tab/tabClasses.ts @@ -25,6 +25,8 @@ export interface TabClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -54,6 +56,7 @@ const tabListClasses: TabClasses = generateUtilityClasses('JoyTab', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/TabList/TabList.test.tsx b/packages/mui-joy/src/TabList/TabList.test.tsx index da319153b72610..31ce745a4155f8 100644 --- a/packages/mui-joy/src/TabList/TabList.test.tsx +++ b/packages/mui-joy/src/TabList/TabList.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { TabsContext, useTabs, TabsUnstyledProps } from '@mui/base/TabsUnstyled'; import { ThemeProvider } from '@mui/joy/styles'; import Tabs from '@mui/joy/Tabs'; @@ -27,6 +27,12 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { + muiName: 'JoyTabList', + classes, + wrapper: (node) => {node}, + }); + describe('size', () => { it('uses size from Tabs', () => { render( diff --git a/packages/mui-joy/src/TabList/TabList.tsx b/packages/mui-joy/src/TabList/TabList.tsx index 810c97ac88260a..8a497b9931c20d 100644 --- a/packages/mui-joy/src/TabList/TabList.tsx +++ b/packages/mui-joy/src/TabList/TabList.tsx @@ -7,6 +7,7 @@ import { useTabsList } from '@mui/base/TabsListUnstyled'; import { useSlotProps } from '@mui/base/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; +import { useColorInversion } from '../styles/ColorInversion'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import SizeTabsContext from '../Tabs/SizeTabsContext'; @@ -54,10 +55,12 @@ const TabList = React.forwardRef(function TabList(inProps, ref) { component = 'div', children, variant = 'soft', - color = 'neutral', + color: colorProp = 'neutral', size: sizeProp, ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { isRtl, orientation, getRootProps, processChildren } = useTabsList({ ...props, ref }); diff --git a/packages/mui-joy/src/TabList/TabListProps.ts b/packages/mui-joy/src/TabList/TabListProps.ts index be0783ba9671bf..d0bb454301cbfa 100644 --- a/packages/mui-joy/src/TabList/TabListProps.ts +++ b/packages/mui-joy/src/TabList/TabListProps.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type TabListSlot = 'root'; @@ -44,7 +44,7 @@ export type TabListProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export type TabListOwnerState = TabListProps & { +export interface TabListOwnerState extends ApplyColorInversion { /** * If `true`, the Tabs' direction is "rtl". */ @@ -53,4 +53,4 @@ export type TabListOwnerState = TabListProps & { * The orientation of the Tabs. */ orientation: 'horizontal' | 'vertical'; -}; +} diff --git a/packages/mui-joy/src/TabList/tabListClasses.ts b/packages/mui-joy/src/TabList/tabListClasses.ts index 6f7d8dd4b76d34..083a6ba599a11d 100644 --- a/packages/mui-joy/src/TabList/tabListClasses.ts +++ b/packages/mui-joy/src/TabList/tabListClasses.ts @@ -15,6 +15,8 @@ export interface TabListClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -45,6 +47,7 @@ const tabListClasses: TabListClasses = generateUtilityClasses('JoyTabList', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/Tabs/Tabs.test.tsx b/packages/mui-joy/src/Tabs/Tabs.test.tsx index c70cdde9ef1fb4..30aca601bf5b6b 100644 --- a/packages/mui-joy/src/Tabs/Tabs.test.tsx +++ b/packages/mui-joy/src/Tabs/Tabs.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, createRenderer, screen } from 'test/utils'; +import { describeConformance, createRenderer, screen, describeJoyColorInversion } from 'test/utils'; import { ThemeProvider } from '@mui/joy/styles'; import Tabs, { tabsClasses as classes } from '@mui/joy/Tabs'; import SizeTabsContext from './SizeTabsContext'; @@ -20,6 +20,8 @@ describe('Joy ', () => { skip: ['componentsProp', 'classesRoot', 'reactTestRenderer'], })); + describeJoyColorInversion(, { muiName: 'JoyTabs', classes }); + it('prop: variant', () => { render(); expect(screen.getByLabelText('Tabs')).to.have.class(classes.variantOutlined); diff --git a/packages/mui-joy/src/Tabs/Tabs.tsx b/packages/mui-joy/src/Tabs/Tabs.tsx index 11b15e8d22e18d..a6526f4a7ca4a0 100644 --- a/packages/mui-joy/src/Tabs/Tabs.tsx +++ b/packages/mui-joy/src/Tabs/Tabs.tsx @@ -7,6 +7,7 @@ import { useTabs, TabsContext } from '@mui/base/TabsUnstyled'; import { useSlotProps } from '@mui/base/utils'; import { SheetRoot } from '../Sheet/Sheet'; import { styled, useThemeProps } from '../styles'; +import { useColorInversion } from '../styles/ColorInversion'; import SizeTabsContext from './SizeTabsContext'; import { getTabsUtilityClass } from './tabsClasses'; import { TabsOwnerState, TabsTypeMap } from './TabsProps'; @@ -64,10 +65,12 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) { onChange, selectionFollowsFocus, variant = 'plain', - color = 'neutral', + color: colorProp = 'neutral', size = 'md', ...other } = props; + const { getColor } = useColorInversion(variant); + const color = getColor(inProps.color, colorProp); const { tabsContextValue } = useTabs({ ...props, orientation }); diff --git a/packages/mui-joy/src/Tabs/TabsProps.ts b/packages/mui-joy/src/Tabs/TabsProps.ts index 5ff21ba0a7f391..ceb58f89920ab3 100644 --- a/packages/mui-joy/src/Tabs/TabsProps.ts +++ b/packages/mui-joy/src/Tabs/TabsProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; import { TabsUnstyledOwnProps } from '@mui/base/TabsUnstyled'; -import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; +import { ColorPaletteProp, SxProps, VariantProp, ApplyColorInversion } from '../styles/types'; export type TabsSlot = 'root'; @@ -41,4 +41,4 @@ export type TabsProps< P = { component?: React.ElementType }, > = OverrideProps, D>; -export interface TabsOwnerState extends TabsProps {} +export interface TabsOwnerState extends ApplyColorInversion {} diff --git a/packages/mui-joy/src/Tabs/tabsClasses.ts b/packages/mui-joy/src/Tabs/tabsClasses.ts index d5b0b29481384c..b3db18a28400a1 100644 --- a/packages/mui-joy/src/Tabs/tabsClasses.ts +++ b/packages/mui-joy/src/Tabs/tabsClasses.ts @@ -19,6 +19,8 @@ export interface TabsClasses { colorSuccess: string; /** Classname applied to the root element if `color="warning"`. */ colorWarning: string; + /** Styles applied to the root element when color inversion is triggered. */ + colorContext: string; /** Classname applied to the root element if `variant="plain"`. */ variantPlain: string; /** Classname applied to the root element if `variant="outlined"`. */ @@ -51,6 +53,7 @@ const tabListClasses: TabsClasses = generateUtilityClasses('JoyTabs', [ 'colorInfo', 'colorSuccess', 'colorWarning', + 'colorContext', 'variantPlain', 'variantOutlined', 'variantSoft', diff --git a/packages/mui-joy/src/TextField/TextField.tsx b/packages/mui-joy/src/TextField/TextField.tsx index ca57f339c8efa1..4059e705590488 100644 --- a/packages/mui-joy/src/TextField/TextField.tsx +++ b/packages/mui-joy/src/TextField/TextField.tsx @@ -118,6 +118,7 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { ref, className: clsx(classes.root, className), elementType: TextFieldRoot, + // @ts-ignore internal logic externalForwardedProps: { ...other, component, slots, slotProps }, ownerState, }); @@ -125,6 +126,7 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { const Input = slots.input || JoyInput; return ( + // @ts-ignore neglect 'context' color {label && ( ', () => { skip: ['propsSpread', 'componentsProp', 'classesRoot'], })); + describeJoyColorInversion(