From 4e7d6388057bd1f28c3c6db8a1f0f54079ec27ae Mon Sep 17 00:00:00 2001 From: Roman Zhuravlov Date: Wed, 20 Mar 2024 11:18:08 +0200 Subject: [PATCH 1/6] [382] Allow passing `open` and `onOpenChange` props to `ThemePanel` component --- .../src/components/theme-panel.tsx | 1076 ++++++++--------- 1 file changed, 532 insertions(+), 544 deletions(-) diff --git a/packages/radix-ui-themes/src/components/theme-panel.tsx b/packages/radix-ui-themes/src/components/theme-panel.tsx index c6633a18..2fd40cdf 100644 --- a/packages/radix-ui-themes/src/components/theme-panel.tsx +++ b/packages/radix-ui-themes/src/components/theme-panel.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useCallbackRef } from '@radix-ui/react-use-callback-ref'; +import { useControllableState } from '@radix-ui/react-use-controllable-state'; import { AccessibleIcon, Box, @@ -23,102 +24,100 @@ import { themePropDefs } from '../props/index.js'; import type { ComponentPropsWithout, RemovedProps } from '../helpers/index.js'; import type { GetPropDefTypes } from '../props/index.js'; -interface ThemePanelProps extends Omit { - defaultOpen?: boolean; -} -const ThemePanel = React.forwardRef( - ({ defaultOpen = true, ...props }, forwardedRef) => { - const [open, setOpen] = React.useState(defaultOpen); - return ; - } -); -ThemePanel.displayName = 'ThemePanel'; +type ThemePanelElement = React.ElementRef<'div'>; -type ThemePanelImplElement = React.ElementRef<'div'>; -interface ThemePanelImplPrivateProps { - open: boolean; - onOpenChange: (open: boolean) => void; -} -interface ThemePanelImplProps - extends ComponentPropsWithout<'div', RemovedProps>, - ThemePanelImplPrivateProps { +interface ThemePanelProps extends ComponentPropsWithout<'div', RemovedProps> { + open?: boolean; + onOpenChange?: (open: boolean) => void; + defaultOpen?: boolean; onAppearanceChange?: (value: 'light' | 'dark') => void; } -const ThemePanelImpl = React.forwardRef( - (props, forwardedRef) => { - const { open, onOpenChange, onAppearanceChange: onAppearanceChangeProp, ...panelProps } = props; - const themeContext = useThemeContext(); - const { - appearance, - onAppearanceChange, - accentColor, - onAccentColorChange, - grayColor, - onGrayColorChange, - panelBackground, - onPanelBackgroundChange, - radius, - onRadiusChange, - scaling, - onScalingChange, - } = themeContext; - - const hasOnAppearanceChangeProp = onAppearanceChangeProp !== undefined; - const handleAppearanceChangeProp = useCallbackRef(onAppearanceChangeProp); - const handleAppearanceChange = React.useCallback( - (value: 'light' | 'dark') => { - const cleanup = disableAnimation(); - - if (appearance !== 'inherit') { - onAppearanceChange(value); - return; - } +const ThemePanel = React.forwardRef((props, forwardedRef) => { + const { + open: openProp, + defaultOpen, + onOpenChange, + onAppearanceChange: onAppearanceChangeProp, + ...panelProps + } = props; + const themeContext = useThemeContext(); + const { + appearance, + onAppearanceChange, + accentColor, + onAccentColorChange, + grayColor, + onGrayColorChange, + panelBackground, + onPanelBackgroundChange, + radius, + onRadiusChange, + scaling, + onScalingChange, + } = themeContext; + + const [open = false, setOpen] = useControllableState({ + prop: openProp, + defaultProp: defaultOpen, + onChange: onOpenChange, + }); + + const hasOnAppearanceChangeProp = onAppearanceChangeProp !== undefined; + const handleAppearanceChangeProp = useCallbackRef(onAppearanceChangeProp); + const handleAppearanceChange = React.useCallback( + (value: 'light' | 'dark') => { + const cleanup = disableAnimation(); + + if (appearance !== 'inherit') { + onAppearanceChange(value); + return; + } - if (hasOnAppearanceChangeProp) { - handleAppearanceChangeProp(value); - } else { - setResolvedAppearance(value); - updateRootAppearanceClass(value); - } + if (hasOnAppearanceChangeProp) { + handleAppearanceChangeProp(value); + } else { + setResolvedAppearance(value); + updateRootAppearanceClass(value); + } - cleanup(); - }, - [appearance, onAppearanceChange, hasOnAppearanceChangeProp, handleAppearanceChangeProp] - ); - - const autoMatchedGray = getMatchingGrayColor(accentColor); - const resolvedGrayColor = grayColor === 'auto' ? autoMatchedGray : grayColor; - - const [copyState, setCopyState] = React.useState<'idle' | 'copying' | 'copied'>('idle'); - async function handleCopyThemeConfig() { - const theme = { - appearance: appearance === themePropDefs.appearance.default ? undefined : appearance, - accentColor: accentColor === themePropDefs.accentColor.default ? undefined : accentColor, - grayColor: grayColor === themePropDefs.grayColor.default ? undefined : grayColor, - panelBackground: - panelBackground === themePropDefs.panelBackground.default ? undefined : panelBackground, - radius: radius === themePropDefs.radius.default ? undefined : radius, - scaling: scaling === themePropDefs.scaling.default ? undefined : scaling, - } satisfies GetPropDefTypes; - - const props = Object.keys(theme) - .filter((key) => theme[key as keyof typeof theme] !== undefined) - .map((key) => `${key}="${theme[key as keyof typeof theme]}"`) - .join(' '); - - const textToCopy = props ? `` : ''; - - setCopyState('copying'); - await navigator.clipboard.writeText(textToCopy); - setCopyState('copied'); - setTimeout(() => setCopyState('idle'), 2000); - } + cleanup(); + }, + [appearance, onAppearanceChange, hasOnAppearanceChangeProp, handleAppearanceChangeProp] + ); - const [resolvedAppearance, setResolvedAppearance] = React.useState<'light' | 'dark' | null>( - appearance === 'inherit' ? null : appearance - ); + const autoMatchedGray = getMatchingGrayColor(accentColor); + const resolvedGrayColor = grayColor === 'auto' ? autoMatchedGray : grayColor; + + const [copyState, setCopyState] = React.useState<'idle' | 'copying' | 'copied'>('idle'); + async function handleCopyThemeConfig() { + const theme = { + appearance: appearance === themePropDefs.appearance.default ? undefined : appearance, + accentColor: accentColor === themePropDefs.accentColor.default ? undefined : accentColor, + grayColor: grayColor === themePropDefs.grayColor.default ? undefined : grayColor, + panelBackground: + panelBackground === themePropDefs.panelBackground.default ? undefined : panelBackground, + radius: radius === themePropDefs.radius.default ? undefined : radius, + scaling: scaling === themePropDefs.scaling.default ? undefined : scaling, + } satisfies GetPropDefTypes; + + const props = Object.keys(theme) + .filter((key) => theme[key as keyof typeof theme] !== undefined) + .map((key) => `${key}="${theme[key as keyof typeof theme]}"`) + .join(' '); + + const textToCopy = props ? `` : ''; + + setCopyState('copying'); + await navigator.clipboard.writeText(textToCopy); + setCopyState('copied'); + setTimeout(() => setCopyState('idle'), 2000); + } + + const [resolvedAppearance, setResolvedAppearance] = React.useState<'light' | 'dark' | null>( + appearance === 'inherit' ? null : appearance + ); - const keyboardInputElement = ` + const keyboardInputElement = ` [contenteditable], [role="combobox"], [role="listbox"], @@ -128,500 +127,489 @@ const ThemePanelImpl = React.forwardRef { - function handleKeydown(event: KeyboardEvent) { - const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; - const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); - const isKeyT = event.key?.toUpperCase() === 'T' && !isModifierActive; - if (isKeyT && !isKeyboardInputActive) { - onOpenChange(!open); - } - } - document.addEventListener('keydown', handleKeydown); - return () => document.removeEventListener('keydown', handleKeydown); - }, [onOpenChange, open, keyboardInputElement]); - - // quickly toggle appearance using "D" keypress - React.useEffect(() => { - function handleKeydown(event: KeyboardEvent) { - const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; - const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); - const isKeyD = event.key?.toUpperCase() === 'D' && !isModifierActive; - if (isKeyD && !isKeyboardInputActive) { - handleAppearanceChange(resolvedAppearance === 'light' ? 'dark' : 'light'); - } + // quickly show/hide using "T" keypress + React.useEffect(() => { + function handleKeydown(event: KeyboardEvent) { + const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; + const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); + const isKeyT = event.key?.toUpperCase() === 'T' && !isModifierActive; + if (isKeyT && !isKeyboardInputActive) { + setOpen(!open); } - document.addEventListener('keydown', handleKeydown); - return () => document.removeEventListener('keydown', handleKeydown); - }, [handleAppearanceChange, resolvedAppearance, keyboardInputElement]); - - React.useEffect(() => { - const root = document.documentElement; - const body = document.body; - - function update() { - const hasDarkClass = - root.classList.contains('dark') || - root.classList.contains('dark-theme') || - body.classList.contains('dark') || - body.classList.contains('dark-theme'); - - if (appearance === 'inherit') { - setResolvedAppearance(hasDarkClass ? 'dark' : 'light'); - } else { - setResolvedAppearance(appearance); - } + } + document.addEventListener('keydown', handleKeydown); + return () => document.removeEventListener('keydown', handleKeydown); + }, [setOpen, open, keyboardInputElement]); + + // quickly toggle appearance using "D" keypress + React.useEffect(() => { + function handleKeydown(event: KeyboardEvent) { + const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; + const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); + const isKeyD = event.key?.toUpperCase() === 'D' && !isModifierActive; + if (isKeyD && !isKeyboardInputActive) { + handleAppearanceChange(resolvedAppearance === 'light' ? 'dark' : 'light'); } + } + document.addEventListener('keydown', handleKeydown); + return () => document.removeEventListener('keydown', handleKeydown); + }, [handleAppearanceChange, resolvedAppearance, keyboardInputElement]); - const classNameObserver = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - if (mutation.attributeName === 'class') { - update(); - } - }); - }); + React.useEffect(() => { + const root = document.documentElement; + const body = document.body; - update(); + function update() { + const hasDarkClass = + root.classList.contains('dark') || + root.classList.contains('dark-theme') || + body.classList.contains('dark') || + body.classList.contains('dark-theme'); - // Observe and for `class` changes only when the appearance is inherited from them if (appearance === 'inherit') { - classNameObserver.observe(root, { attributes: true }); - classNameObserver.observe(body, { attributes: true }); + setResolvedAppearance(hasDarkClass ? 'dark' : 'light'); + } else { + setResolvedAppearance(appearance); } + } + + const classNameObserver = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + if (mutation.attributeName === 'class') { + update(); + } + }); + }); + + update(); + + // Observe and for `class` changes only when the appearance is inherited from them + if (appearance === 'inherit') { + classNameObserver.observe(root, { attributes: true }); + classNameObserver.observe(body, { attributes: true }); + } + + return () => classNameObserver.disconnect(); + }, [appearance]); + + return ( + + + + + + + + + + + + + + Theme + + + + Accent color + - return () => classNameObserver.disconnect(); - }, [appearance]); - - return ( - - - - - - + {themePropDefs.accentColor.values.map((color) => ( + - - - - Theme - - - - Accent color + + + onAccentColorChange(event.target.value as typeof accentColor) + } + /> + + + ))} + + + + + Gray color + - - {themePropDefs.accentColor.values.map((color) => ( + + {themePropDefs.grayColor.values.map((gray) => ( + - ))} - - - - - Gray color - - - - - {themePropDefs.grayColor.values.map((gray) => ( - - + + + Appearance + + + + {(['light', 'dark'] as const).map((value) => ( + + + + ) : ( + + + + )} + + {upperFirst(value)} + - ))} - - - - Appearance - - - - {(['light', 'dark'] as const).map((value) => ( - + ))} + + + + Radius + + + + {themePropDefs.radius.values.map((value) => ( + + - ))} - - - - Radius - - - - {themePropDefs.radius.values.map((value) => ( - - - - - - - + + + + + + + + + ))} + + + + Scaling + + + + {themePropDefs.scaling.values.map((value) => ( + + + ))} + - - Scaling + + + Panel background - - {themePropDefs.scaling.values.map((value) => ( - - ))} - - - - - Panel background - - - - - - - + + + + + + + + + + + + + Whether Card and Table panels are translucent, showing some of the background + behind them. + + + + + + + {themePropDefs.panelBackground.values.map((value) => ( + - - ); - } -); -ThemePanelImpl.displayName = 'ThemePanelImpl'; + + + ))} + + + + + + + + ); +}); +ThemePanel.displayName = 'ThemePanel'; // https://github.com/pacocoursey/next-themes/blob/main/packages/next-themes/src/index.tsx#L285 function disableAnimation() { From 2f3c6802c74b70db28ed388ebd9bf55bfc7c045a Mon Sep 17 00:00:00 2001 From: Roman Zhuravlov Date: Wed, 20 Mar 2024 11:33:24 +0200 Subject: [PATCH 2/6] [382] Move common hotkey logic into a custom `useHotKey` hook --- .../src/components/theme-panel.tsx | 77 +++++++++++-------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/packages/radix-ui-themes/src/components/theme-panel.tsx b/packages/radix-ui-themes/src/components/theme-panel.tsx index 2fd40cdf..a3c77ac1 100644 --- a/packages/radix-ui-themes/src/components/theme-panel.tsx +++ b/packages/radix-ui-themes/src/components/theme-panel.tsx @@ -24,6 +24,42 @@ import { themePropDefs } from '../props/index.js'; import type { ComponentPropsWithout, RemovedProps } from '../helpers/index.js'; import type { GetPropDefTypes } from '../props/index.js'; +const keyboardInputElement = ` + [contenteditable], + [role="combobox"], + [role="listbox"], + [role="menu"], + input:not([type="radio"], [type="checkbox"]), + select, + textarea +`; + +/** Listen to keydown events and fire a callback when a hotkey is pressed */ +const useHotKey = ( + key: string, + callback: () => void, + options?: { + /** Determines whether the hotkey listener is active or not */ + enabled?: boolean; + } +) => { + const { enabled = true } = options ?? {}; + const callbackRef = useCallbackRef(callback); + React.useEffect(() => { + if (!enabled) return; + function handleKeydown(event: KeyboardEvent) { + const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; + const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); + const isKeyActive = event.key?.toUpperCase() === key; + if (isKeyActive && !isModifierActive && !isKeyboardInputActive) { + callbackRef(); + } + } + document.addEventListener('keydown', handleKeydown); + return () => document.removeEventListener('keydown', handleKeydown); + }, [key, callbackRef, enabled]); +}; + type ThemePanelElement = React.ElementRef<'div'>; interface ThemePanelProps extends ComponentPropsWithout<'div', RemovedProps> { @@ -117,43 +153,16 @@ const ThemePanel = React.forwardRef((props, appearance === 'inherit' ? null : appearance ); - const keyboardInputElement = ` - [contenteditable], - [role="combobox"], - [role="listbox"], - [role="menu"], - input:not([type="radio"], [type="checkbox"]), - select, - textarea - `; - // quickly show/hide using "T" keypress - React.useEffect(() => { - function handleKeydown(event: KeyboardEvent) { - const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; - const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); - const isKeyT = event.key?.toUpperCase() === 'T' && !isModifierActive; - if (isKeyT && !isKeyboardInputActive) { - setOpen(!open); - } - } - document.addEventListener('keydown', handleKeydown); - return () => document.removeEventListener('keydown', handleKeydown); - }, [setOpen, open, keyboardInputElement]); + const toggleOpen = React.useCallback(() => setOpen((prev: boolean) => !prev), [setOpen]); + useHotKey('T', toggleOpen); // quickly toggle appearance using "D" keypress - React.useEffect(() => { - function handleKeydown(event: KeyboardEvent) { - const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; - const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); - const isKeyD = event.key?.toUpperCase() === 'D' && !isModifierActive; - if (isKeyD && !isKeyboardInputActive) { - handleAppearanceChange(resolvedAppearance === 'light' ? 'dark' : 'light'); - } - } - document.addEventListener('keydown', handleKeydown); - return () => document.removeEventListener('keydown', handleKeydown); - }, [handleAppearanceChange, resolvedAppearance, keyboardInputElement]); + const toggleAppearance = React.useCallback( + () => handleAppearanceChange(resolvedAppearance === 'light' ? 'dark' : 'light'), + [handleAppearanceChange, resolvedAppearance] + ); + useHotKey('D', toggleAppearance); React.useEffect(() => { const root = document.documentElement; From 39f77913b0cd2f039acebc7a4a4f60d824ece9f7 Mon Sep 17 00:00:00 2001 From: Roman Zhuravlov Date: Wed, 20 Mar 2024 11:50:23 +0200 Subject: [PATCH 3/6] [382] Allow extending/disabling default `openHotkey` and `toogleAppearanceHotkey` --- .../src/components/theme-panel.tsx | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/radix-ui-themes/src/components/theme-panel.tsx b/packages/radix-ui-themes/src/components/theme-panel.tsx index a3c77ac1..26aa3075 100644 --- a/packages/radix-ui-themes/src/components/theme-panel.tsx +++ b/packages/radix-ui-themes/src/components/theme-panel.tsx @@ -36,7 +36,7 @@ const keyboardInputElement = ` /** Listen to keydown events and fire a callback when a hotkey is pressed */ const useHotKey = ( - key: string, + key: string | null | false, callback: () => void, options?: { /** Determines whether the hotkey listener is active or not */ @@ -46,11 +46,12 @@ const useHotKey = ( const { enabled = true } = options ?? {}; const callbackRef = useCallbackRef(callback); React.useEffect(() => { - if (!enabled) return; + if (!key) return; + if (enabled === false) return; function handleKeydown(event: KeyboardEvent) { const isModifierActive = event.altKey || event.ctrlKey || event.shiftKey || event.metaKey; const isKeyboardInputActive = document.activeElement?.closest(keyboardInputElement); - const isKeyActive = event.key?.toUpperCase() === key; + const isKeyActive = event.key?.toUpperCase() === (key as string).toUpperCase(); if (isKeyActive && !isModifierActive && !isKeyboardInputActive) { callbackRef(); } @@ -67,13 +68,26 @@ interface ThemePanelProps extends ComponentPropsWithout<'div', RemovedProps> { onOpenChange?: (open: boolean) => void; defaultOpen?: boolean; onAppearanceChange?: (value: 'light' | 'dark') => void; + /** A hotkey to quickly show/hide the panel. + * Set to `null` or `false` to disable the hotkey. + * @default "T" + */ + openHotkey?: string | null | false; + /** A hotkey to quickly toggle the appearance. + * Set to `null` or `false` to disable the hotkey. + * @default "D" + * */ + toogleAppearanceHotkey?: string | null | false; } + const ThemePanel = React.forwardRef((props, forwardedRef) => { const { open: openProp, defaultOpen, onOpenChange, onAppearanceChange: onAppearanceChangeProp, + openHotkey = 'T', + toogleAppearanceHotkey = 'D', ...panelProps } = props; const themeContext = useThemeContext(); @@ -155,14 +169,14 @@ const ThemePanel = React.forwardRef((props, // quickly show/hide using "T" keypress const toggleOpen = React.useCallback(() => setOpen((prev: boolean) => !prev), [setOpen]); - useHotKey('T', toggleOpen); + useHotKey(openHotkey, toggleOpen); // quickly toggle appearance using "D" keypress const toggleAppearance = React.useCallback( () => handleAppearanceChange(resolvedAppearance === 'light' ? 'dark' : 'light'), [handleAppearanceChange, resolvedAppearance] ); - useHotKey('D', toggleAppearance); + useHotKey(toogleAppearanceHotkey, toggleAppearance); React.useEffect(() => { const root = document.documentElement; @@ -231,13 +245,21 @@ const ThemePanel = React.forwardRef((props, > - - - - - - - + {!!openHotkey && ( + + + + + + + + )} Theme @@ -618,6 +640,7 @@ const ThemePanel = React.forwardRef((props, ); }); + ThemePanel.displayName = 'ThemePanel'; // https://github.com/pacocoursey/next-themes/blob/main/packages/next-themes/src/index.tsx#L285 From 82426307f9512e32084282fc6fa3930fd054239a Mon Sep 17 00:00:00 2001 From: Roman Zhuravlov Date: Wed, 20 Mar 2024 12:13:17 +0200 Subject: [PATCH 4/6] [382] Fix ts error --- packages/radix-ui-themes/src/components/theme-panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/radix-ui-themes/src/components/theme-panel.tsx b/packages/radix-ui-themes/src/components/theme-panel.tsx index 26aa3075..b2816172 100644 --- a/packages/radix-ui-themes/src/components/theme-panel.tsx +++ b/packages/radix-ui-themes/src/components/theme-panel.tsx @@ -168,7 +168,7 @@ const ThemePanel = React.forwardRef((props, ); // quickly show/hide using "T" keypress - const toggleOpen = React.useCallback(() => setOpen((prev: boolean) => !prev), [setOpen]); + const toggleOpen = React.useCallback(() => setOpen((prev) => !prev), [setOpen]); useHotKey(openHotkey, toggleOpen); // quickly toggle appearance using "D" keypress From 612ea701613a1fae163424acbcef86dc269e7ac5 Mon Sep 17 00:00:00 2001 From: Roman Zhuravlov Date: Wed, 20 Mar 2024 12:18:40 +0200 Subject: [PATCH 5/6] [382] Set `defaultOpen` to `true` --- packages/radix-ui-themes/src/components/theme-panel.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/radix-ui-themes/src/components/theme-panel.tsx b/packages/radix-ui-themes/src/components/theme-panel.tsx index b2816172..6c45af92 100644 --- a/packages/radix-ui-themes/src/components/theme-panel.tsx +++ b/packages/radix-ui-themes/src/components/theme-panel.tsx @@ -66,6 +66,10 @@ type ThemePanelElement = React.ElementRef<'div'>; interface ThemePanelProps extends ComponentPropsWithout<'div', RemovedProps> { open?: boolean; onOpenChange?: (open: boolean) => void; + /** Whether the theme panel is open by default. + * Doesn't have any effect if `open` is also set. + * @default true + */ defaultOpen?: boolean; onAppearanceChange?: (value: 'light' | 'dark') => void; /** A hotkey to quickly show/hide the panel. @@ -83,7 +87,7 @@ interface ThemePanelProps extends ComponentPropsWithout<'div', RemovedProps> { const ThemePanel = React.forwardRef((props, forwardedRef) => { const { open: openProp, - defaultOpen, + defaultOpen = true, onOpenChange, onAppearanceChange: onAppearanceChangeProp, openHotkey = 'T', From be0856e4d2490a17c7175c029287bf6e3d4d22cb Mon Sep 17 00:00:00 2001 From: Roman Zhuravlov Date: Wed, 20 Mar 2024 12:22:54 +0200 Subject: [PATCH 6/6] [382] Move where `defaultOpen` is defined --- packages/radix-ui-themes/src/components/theme-panel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/radix-ui-themes/src/components/theme-panel.tsx b/packages/radix-ui-themes/src/components/theme-panel.tsx index 6c45af92..dd972c81 100644 --- a/packages/radix-ui-themes/src/components/theme-panel.tsx +++ b/packages/radix-ui-themes/src/components/theme-panel.tsx @@ -87,7 +87,7 @@ interface ThemePanelProps extends ComponentPropsWithout<'div', RemovedProps> { const ThemePanel = React.forwardRef((props, forwardedRef) => { const { open: openProp, - defaultOpen = true, + defaultOpen, onOpenChange, onAppearanceChange: onAppearanceChangeProp, openHotkey = 'T', @@ -110,7 +110,7 @@ const ThemePanel = React.forwardRef((props, onScalingChange, } = themeContext; - const [open = false, setOpen] = useControllableState({ + const [open = true, setOpen] = useControllableState({ prop: openProp, defaultProp: defaultOpen, onChange: onOpenChange,