Skip to content

Commit 5b86aee

Browse files
committed
feat(theme): provide a React context to control the dark theme
1 parent 31f8a99 commit 5b86aee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+422
-306
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `ThemeProvider` & `useTheme`: add context util to facilitate propagation of theme.
13+
1014
## [3.10.0][] - 2025-01-16
1115

1216
### Changed

packages/lumx-react/src/components/autocomplete/Autocomplete.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
88
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
99
import { useFocus } from '@lumx/react/hooks/useFocus';
1010
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
11+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
1112

1213
/**
1314
* Defines the props of the component.
@@ -200,6 +201,7 @@ const DEFAULT_PROPS: Partial<AutocompleteProps> = {
200201
* @return React element.
201202
*/
202203
export const Autocomplete: Comp<AutocompleteProps, HTMLDivElement> = forwardRef((props, ref) => {
204+
const defaultTheme = useTheme();
203205
const {
204206
anchorToInput,
205207
children,
@@ -231,7 +233,7 @@ export const Autocomplete: Comp<AutocompleteProps, HTMLDivElement> = forwardRef(
231233
placeholder,
232234
placement,
233235
shouldFocusOnClose,
234-
theme,
236+
theme = defaultTheme,
235237
value,
236238
textFieldProps = {},
237239
focusAnchorOnClose,

packages/lumx-react/src/components/autocomplete/AutocompleteMultiple.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/classNam
66

77
import classNames from 'classnames';
88
import React, { forwardRef, ReactNode } from 'react';
9+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
910

1011
/**
1112
* Defines the props of the component.
@@ -66,6 +67,7 @@ const DEFAULT_PROPS: Partial<AutocompleteMultipleProps> = {
6667
* @return React element.
6768
*/
6869
export const AutocompleteMultiple: Comp<AutocompleteMultipleProps, HTMLDivElement> = forwardRef((props, ref) => {
70+
const defaultTheme = useTheme();
6971
const {
7072
anchorToInput,
7173
children,
@@ -99,7 +101,7 @@ export const AutocompleteMultiple: Comp<AutocompleteMultipleProps, HTMLDivElemen
99101
placement,
100102
selectedChipRender,
101103
shouldFocusOnClose,
102-
theme,
104+
theme = defaultTheme,
103105
type,
104106
value,
105107
values,

packages/lumx-react/src/components/avatar/Avatar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AspectRatio, Size, Theme, Thumbnail, ThumbnailProps } from '@lumx/react
66

77
import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
88
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
9+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
910

1011
/**
1112
* Avatar sizes.
@@ -56,7 +57,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
5657
*/
5758
const DEFAULT_PROPS: Partial<AvatarProps> = {
5859
size: Size.m,
59-
theme: Theme.light,
6060
};
6161

6262
/**
@@ -67,6 +67,7 @@ const DEFAULT_PROPS: Partial<AvatarProps> = {
6767
* @return React element.
6868
*/
6969
export const Avatar: Comp<AvatarProps, HTMLDivElement> = forwardRef((props, ref) => {
70+
const defaultTheme = useTheme() || Theme.light;
7071
const {
7172
actions,
7273
alt,
@@ -78,7 +79,7 @@ export const Avatar: Comp<AvatarProps, HTMLDivElement> = forwardRef((props, ref)
7879
onClick,
7980
onKeyPress,
8081
size,
81-
theme,
82+
theme = defaultTheme,
8283
thumbnailProps,
8384
...forwardedProps
8485
} = props;

packages/lumx-react/src/components/button/Button.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import isEmpty from 'lodash/isEmpty';
66
import { Emphasis, Icon, Size, Theme, Text } from '@lumx/react';
77
import { Comp, isComponent } from '@lumx/react/utils/type';
88
import { getBasicClass, getRootClassName } from '@lumx/react/utils/className';
9+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
910
import { BaseButtonProps, ButtonRoot } from './ButtonRoot';
1011

1112
/**
@@ -44,7 +45,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
4445
const DEFAULT_PROPS: Partial<ButtonProps> = {
4546
emphasis: Emphasis.high,
4647
size: Size.m,
47-
theme: Theme.light,
4848
};
4949

5050
/**
@@ -55,7 +55,8 @@ const DEFAULT_PROPS: Partial<ButtonProps> = {
5555
* @return React element.
5656
*/
5757
export const Button: Comp<ButtonProps, HTMLButtonElement | HTMLAnchorElement> = forwardRef((props, ref) => {
58-
const { children, className, emphasis, leftIcon, rightIcon, size, theme, ...forwardedProps } = props;
58+
const defaultTheme = useTheme() || Theme.light;
59+
const { children, className, emphasis, leftIcon, rightIcon, size, theme = defaultTheme, ...forwardedProps } = props;
5960

6061
const buttonClassName = classNames(
6162
className,

packages/lumx-react/src/components/button/IconButton.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Emphasis, Icon, Size, Theme, Tooltip, TooltipProps } from '@lumx/react'
44
import { BaseButtonProps, ButtonRoot } from '@lumx/react/components/button/ButtonRoot';
55
import { Comp } from '@lumx/react/utils/type';
66
import { getRootClassName } from '@lumx/react/utils/className';
7+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
78

89
export interface IconButtonProps extends BaseButtonProps {
910
/**
@@ -46,7 +47,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
4647
const DEFAULT_PROPS: Partial<IconButtonProps> = {
4748
emphasis: Emphasis.high,
4849
size: Size.m,
49-
theme: Theme.light,
5050
};
5151

5252
/**
@@ -57,7 +57,18 @@ const DEFAULT_PROPS: Partial<IconButtonProps> = {
5757
* @return React element.
5858
*/
5959
export const IconButton: Comp<IconButtonProps, HTMLButtonElement> = forwardRef((props, ref) => {
60-
const { emphasis, image, icon, label, size, theme, tooltipProps, hideTooltip, ...forwardedProps } = props;
60+
const defaultTheme = useTheme() || Theme.light;
61+
const {
62+
emphasis,
63+
image,
64+
icon,
65+
label,
66+
size,
67+
theme = defaultTheme,
68+
tooltipProps,
69+
hideTooltip,
70+
...forwardedProps
71+
} = props;
6172

6273
return (
6374
<Tooltip label={hideTooltip ? '' : label} {...tooltipProps}>

packages/lumx-react/src/components/checkbox/Checkbox.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
99
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
1010
import { useId } from '@lumx/react/hooks/useId';
1111
import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
12+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
1213

1314
/**
1415
* Intermediate state of checkbox.
@@ -54,9 +55,7 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
5455
/**
5556
* Component default props.
5657
*/
57-
const DEFAULT_PROPS: Partial<CheckboxProps> = {
58-
theme: Theme.light,
59-
};
58+
const DEFAULT_PROPS: Partial<CheckboxProps> = {};
6059

6160
/**
6261
* Checkbox component.
@@ -66,6 +65,7 @@ const DEFAULT_PROPS: Partial<CheckboxProps> = {
6665
* @return React element.
6766
*/
6867
export const Checkbox: Comp<CheckboxProps, HTMLDivElement> = forwardRef((props, ref) => {
68+
const defaultTheme = useTheme() || Theme.light;
6969
const {
7070
checked,
7171
className,
@@ -78,7 +78,7 @@ export const Checkbox: Comp<CheckboxProps, HTMLDivElement> = forwardRef((props,
7878
label,
7979
name,
8080
onChange,
81-
theme,
81+
theme = defaultTheme,
8282
value,
8383
inputProps = {},
8484
...forwardedProps

packages/lumx-react/src/components/chip/Chip.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import classNames from 'classnames';
99

1010
import isFunction from 'lodash/isFunction';
1111
import React, { forwardRef, MouseEventHandler, ReactNode } from 'react';
12+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
1213

1314
/**
1415
* Chip sizes.
@@ -56,7 +57,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
5657
*/
5758
const DEFAULT_PROPS: Partial<ChipProps> = {
5859
size: Size.m,
59-
theme: Theme.light,
6060
};
6161

6262
/**
@@ -67,6 +67,7 @@ const DEFAULT_PROPS: Partial<ChipProps> = {
6767
* @return React element.
6868
*/
6969
export const Chip: Comp<ChipProps, HTMLAnchorElement> = forwardRef((props, ref) => {
70+
const defaultTheme = useTheme() || Theme.light;
7071
const {
7172
after,
7273
before,
@@ -82,7 +83,7 @@ export const Chip: Comp<ChipProps, HTMLAnchorElement> = forwardRef((props, ref)
8283
onBeforeClick,
8384
onClick,
8485
size,
85-
theme,
86+
theme = defaultTheme,
8687
href,
8788
onKeyDown,
8889
...forwardedProps

packages/lumx-react/src/components/comment-block/CommentBlock.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Avatar, Size, Theme, Tooltip } from '@lumx/react';
66
import { Comp, GenericProps, HasTheme, ValueOf } from '@lumx/react/utils/type';
77
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
88

9+
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
910
import { AvatarProps } from '../avatar/Avatar';
1011

1112
/**
@@ -76,7 +77,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
7677
* Component default props.
7778
*/
7879
const DEFAULT_PROPS: Partial<CommentBlockProps> = {
79-
theme: Theme.light,
8080
variant: CommentBlockVariant.indented,
8181
};
8282

@@ -88,6 +88,7 @@ const DEFAULT_PROPS: Partial<CommentBlockProps> = {
8888
* @return React element.
8989
*/
9090
export const CommentBlock: Comp<CommentBlockProps, HTMLDivElement> = forwardRef((props, ref) => {
91+
const defaultTheme = useTheme() || Theme.light;
9192
const {
9293
actions,
9394
avatarProps,
@@ -104,7 +105,7 @@ export const CommentBlock: Comp<CommentBlockProps, HTMLDivElement> = forwardRef(
104105
onMouseEnter,
105106
onMouseLeave,
106107
text,
107-
theme,
108+
theme = defaultTheme,
108109
variant,
109110
...forwardedProps
110111
} = props;

packages/lumx-react/src/components/dialog/Dialog.tsx

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { mergeRefs } from '@lumx/react/utils/mergeRefs';
1818

1919
import { useDisableBodyScroll } from '@lumx/react/hooks/useDisableBodyScroll';
2020
import { useTransitionVisibility } from '@lumx/react/hooks/useTransitionVisibility';
21+
import { ThemeProvider } from '@lumx/react/utils/theme/ThemeContext';
2122

2223
/**
2324
* Defines the props of the component.
@@ -208,65 +209,67 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
208209
>
209210
<div className={`${CLASSNAME}__overlay`} />
210211

211-
<section className={`${CLASSNAME}__container`} role="dialog" aria-modal="true" {...dialogProps}>
212-
<ClickAwayProvider
213-
callback={!shouldPreventCloseOnClickAway && onClose}
214-
childrenRefs={clickAwayRefs}
215-
parentRef={rootRef}
216-
>
217-
<div className={`${CLASSNAME}__wrapper`} ref={wrapperRef}>
218-
{(header || headerChildContent) && (
219-
<header
220-
{...headerChildProps}
221-
className={classNames(
222-
`${CLASSNAME}__header`,
223-
(forceHeaderDivider || hasTopIntersection) &&
224-
`${CLASSNAME}__header--has-divider`,
225-
headerChildProps?.className,
226-
)}
227-
>
228-
{header}
229-
{headerChildContent}
230-
</header>
231-
)}
232-
233-
<div ref={mergeRefs(contentRef, localContentRef)} className={`${CLASSNAME}__content`}>
234-
<div
235-
className={`${CLASSNAME}__sentinel ${CLASSNAME}__sentinel--top`}
236-
ref={setSentinelTop}
237-
/>
238-
239-
{content}
240-
241-
<div
242-
className={`${CLASSNAME}__sentinel ${CLASSNAME}__sentinel--bottom`}
243-
ref={setSentinelBottom}
244-
/>
245-
</div>
246-
247-
{(footer || footerChildContent) && (
248-
<footer
249-
{...footerChildProps}
250-
className={classNames(
251-
`${CLASSNAME}__footer`,
252-
(forceFooterDivider || hasBottomIntersection) &&
253-
`${CLASSNAME}__footer--has-divider`,
254-
footerChildProps?.className,
255-
)}
256-
>
257-
{footer}
258-
{footerChildContent}
259-
</footer>
260-
)}
261-
262-
{isLoading && (
263-
<div className={`${CLASSNAME}__progress-overlay`}>
264-
<Progress variant={ProgressVariant.circular} />
212+
<ThemeProvider value={undefined}>
213+
<section className={`${CLASSNAME}__container`} role="dialog" aria-modal="true" {...dialogProps}>
214+
<ClickAwayProvider
215+
callback={!shouldPreventCloseOnClickAway && onClose}
216+
childrenRefs={clickAwayRefs}
217+
parentRef={rootRef}
218+
>
219+
<div className={`${CLASSNAME}__wrapper`} ref={wrapperRef}>
220+
{(header || headerChildContent) && (
221+
<header
222+
{...headerChildProps}
223+
className={classNames(
224+
`${CLASSNAME}__header`,
225+
(forceHeaderDivider || hasTopIntersection) &&
226+
`${CLASSNAME}__header--has-divider`,
227+
headerChildProps?.className,
228+
)}
229+
>
230+
{header}
231+
{headerChildContent}
232+
</header>
233+
)}
234+
235+
<div ref={mergeRefs(contentRef, localContentRef)} className={`${CLASSNAME}__content`}>
236+
<div
237+
className={`${CLASSNAME}__sentinel ${CLASSNAME}__sentinel--top`}
238+
ref={setSentinelTop}
239+
/>
240+
241+
{content}
242+
243+
<div
244+
className={`${CLASSNAME}__sentinel ${CLASSNAME}__sentinel--bottom`}
245+
ref={setSentinelBottom}
246+
/>
265247
</div>
266-
)}
267-
</div>
268-
</ClickAwayProvider>
269-
</section>
248+
249+
{(footer || footerChildContent) && (
250+
<footer
251+
{...footerChildProps}
252+
className={classNames(
253+
`${CLASSNAME}__footer`,
254+
(forceFooterDivider || hasBottomIntersection) &&
255+
`${CLASSNAME}__footer--has-divider`,
256+
footerChildProps?.className,
257+
)}
258+
>
259+
{footer}
260+
{footerChildContent}
261+
</footer>
262+
)}
263+
264+
{isLoading && (
265+
<div className={`${CLASSNAME}__progress-overlay`}>
266+
<Progress variant={ProgressVariant.circular} />
267+
</div>
268+
)}
269+
</div>
270+
</ClickAwayProvider>
271+
</section>
272+
</ThemeProvider>
270273
</div>,
271274
document.body,
272275
)

0 commit comments

Comments
 (0)