Make twenty-ui theming a consumer-facing API (#22007)

**What**
- Add `useTheme()` and `useThemeColorScheme()` as the public theme
accessors, and migrate the 88 internal `useContext(ThemeContext)` call
sites to them. `ThemeContext` stays exported.
- Make `ThemeProvider` overridable and scopeable: new `applyToRoot`
(default `true`), `overrides` (a `--t-*` map), and `className` props.
When scoping is requested it renders a `display: contents` wrapper that
also serves as the themed portal container, exposed via
`ThemeScopeContext` / `useThemeContainer()`. `AppTooltip` and `Modal`
portal into that container, falling back to `document.body`.
- Document the `--t-*` override contract in the README; barrels
regenerated.

**Why**
Consumers had no stable theme accessor (they reached into the raw
context) and no supported way to re-theme. This adds both without
behavior change.

**Reviewer notes**
- The default path is unchanged: `applyToRoot` defaults to `true`, so
the colorScheme class still lands on `<html>` and portaled overlays
(tooltips, dropdowns, modals) stay themed. The global class is
load-bearing for body portals; scoping is opt-in.
- `twenty-front` is untouched (migrating its consumers is a separate
follow-up).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22007?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-06-23 14:16:09 +02:00
committed by GitHub
parent 85406a58fb
commit 0f451897cf
97 changed files with 330 additions and 319 deletions
+24
View File
@@ -122,6 +122,30 @@ diff cleanly against their sources).
- Tokens live in `src/theme/` (`THEME_LIGHT` / `THEME_DARK`); the `--t-*` CSS variables and the `themeCssVariables` accessor are static files mirrored token-for-token from `twenty-ui-deprecated` (matching its own static-CSS approach).
- A theme parity test asserts the theme CSS and `themeCssVariables` stay identical to `twenty-ui-deprecated`'s `--t-*` values.
### Consuming the theme
- Wrap the app once in `ThemeProvider` and import `twenty-ui/theme-light.css` and `twenty-ui/theme-dark.css`.
- Read theme values with the public hooks, not the raw context:
- `useTheme()` returns the resolved `ThemeType` (numeric tokens such as `theme.icon.size.sm`). Outside a provider it yields the default light theme.
- `useThemeColorScheme()` returns the active `'light' | 'dark'`.
- Colors are consumed as `var(--t-*)` strings via `themeCssVariables` (the browser resolves them against the in-scope `--t-*`), so overriding the variables re-themes color automatically.
### Customizing the theme (`--t-*` override contract)
The supported way to re-theme is to override the `--t-*` CSS variables. Their names are exactly the keys declared in `theme-light.css` / `theme-dark.css`. Two mechanisms:
1. **Globally** — ship a stylesheet that redefines `--t-*` under `.light` / `.dark`. This covers the whole document, including overlays portaled to `document.body`.
2. **Per `ThemeProvider`** — pass `overrides` (a `ThemeOverrides` map of `--t-*` name to value). The variables are applied to the provider's subtree.
`ThemeProvider` props:
- `colorScheme: 'light' | 'dark'` — required.
- `applyToRoot?: boolean` (default `true`) — toggles the `.light` / `.dark` class on `document.documentElement`. Keep it `true` so the whole document, including portaled overlays (tooltips, dropdowns, modals) rendered to `document.body`, stays themed. Set it `false` when the host manages its own color-scheme class or when mounting more than one provider, so the providers don't clobber the root.
- `overrides?: ThemeOverrides``--t-*` variable overrides scoped to this provider's subtree.
- `className?: string` — extra class on the scoped wrapper.
**Scoped theming and portals.** When `applyToRoot` is `false` or `overrides` is set, the provider scopes the variables to a wrapper element. Overlays portal to `document.body` (outside that wrapper), so for them to inherit the scoped variables the provider also exposes a themed portal container via `ThemeScopeContext`; read it with `useThemeContainer()` and pass it as the overlay's portal container. The library's `AppTooltip` and `Modal` already do this, falling back to `document.body` when no scoped provider is present (the default global path is unchanged).
## Component migration map
Components split into two buckets. *(The migration is complete; see
@@ -1,6 +1,5 @@
import { motion } from 'framer-motion';
import { useContext } from 'react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
export type AnimatedCheckmarkProps = React.ComponentProps<
typeof motion.path
@@ -17,7 +16,7 @@ export const AnimatedCheckmark = ({
duration = 0.5,
size = 28,
}: AnimatedCheckmarkProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<svg
@@ -1,12 +1,12 @@
import { isNonEmptyString, isNull, isUndefined } from '@sniptt/guards';
import { clsx } from 'clsx';
import { useContext, useState } from 'react';
import { useState } from 'react';
import { handleClickableElementKeyDown } from '@ui/accessibility/utils/handleClickableElementKeyDown';
import { type AvatarSize } from '@ui/data-display/Avatar/types/AvatarSize';
import { type AvatarType } from '@ui/data-display/Avatar/types/AvatarType';
import { type IconComponent } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { stringToThemeColorP3String } from '@ui/utilities';
import { type Nullable } from '@ui/utilities/types/Nullable';
@@ -41,7 +41,7 @@ export const Avatar = ({
backgroundColor,
borderColor,
}: AvatarProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const [erroredAvatarImageURI, setErroredAvatarImageURI] = useState<
string | null
@@ -4,9 +4,8 @@ import { handleClickableElementKeyDown } from '@ui/accessibility/utils/handleCli
import { Avatar } from '@ui/data-display/Avatar/Avatar';
import { type AvatarType } from '@ui/data-display/Avatar/types/AvatarType';
import { type IconComponent } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { type Nullable } from '@ui/utilities';
import { useContext } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import styles from './AvatarOrIcon.module.scss';
@@ -34,7 +33,7 @@ export const AvatarOrIcon = ({
IconBackgroundColor,
onClick,
}: AvatarOrIconProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
if (!isDefined(Icon)) {
return (
@@ -1,9 +1,9 @@
import React, { useContext } from 'react';
import React from 'react';
import { clsx } from 'clsx';
import { IconCheck } from '@ui/icon/components/TablerIcons';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './Checkmark.module.scss';
@@ -12,7 +12,7 @@ export type CheckmarkProps = React.ComponentPropsWithoutRef<'div'> & {
};
export const Checkmark = ({ className }: CheckmarkProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<div className={clsx(styles.root, className)}>
@@ -1,9 +1,8 @@
import { type IconComponent } from '@ui/icon/types/IconComponent';
import { OverflowingTextWithTooltip } from '@ui/surfaces/OverflowingTextWithTooltip/OverflowingTextWithTooltip';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { themeCssVariables, useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import { useContext } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import styles from './Tag.module.scss';
@@ -35,7 +34,7 @@ export const Tag = ({
preventShrink,
preventPadding,
}: TagProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const tagBackground =
color === 'transparent'
@@ -2,8 +2,7 @@ import { type IconComponent } from '@ui/icon/types/IconComponent';
import { StyledTintedIconTileContainer } from '@ui/data-display/StyledTintedIconTileContainer/StyledTintedIconTileContainer';
import { getIconTileColorShades } from '@ui/data-display/TintedIconTile/utils/getIconTileColorShades';
import { DEFAULT_THEME_COLOR_FALLBACK } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import { isDefined } from '@ui/utilities/utils/isDefined';
export type TintedIconTileProps = {
@@ -19,7 +18,7 @@ export const TintedIconTile = ({
size: sizeFromProps,
stroke: strokeFromProps,
}: TintedIconTileProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const style = getIconTileColorShades(color);
const iconSize = sizeFromProps ?? theme.icon.size.md;
const iconStroke = strokeFromProps ?? theme.icon.stroke.md;
@@ -1,12 +1,12 @@
import { clsx } from 'clsx';
import { animate, motion, useMotionValue, useTransform } from 'framer-motion';
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { BACKGROUND } from '@ui/feedback/AnimatedPlaceholder/constants/Background';
import { DARK_BACKGROUND } from '@ui/feedback/AnimatedPlaceholder/constants/DarkBackground';
import { DARK_MOVING_IMAGE } from '@ui/feedback/AnimatedPlaceholder/constants/DarkMovingImage';
import { MOVING_IMAGE } from '@ui/feedback/AnimatedPlaceholder/constants/MovingImage';
import { ThemeContext } from '@ui/theme-constants';
import { useThemeColorScheme } from '@ui/theme-constants';
import styles from './AnimatedPlaceholder.module.scss';
@@ -22,7 +22,7 @@ type AnimatedPlaceholderProps = {
};
export const AnimatedPlaceholder = ({ type }: AnimatedPlaceholderProps) => {
const { colorScheme } = useContext(ThemeContext);
const colorScheme = useThemeColorScheme();
const x = useMotionValue(window.innerWidth / 2);
const y = useMotionValue(window.innerHeight / 2);
@@ -1,9 +1,9 @@
import { clsx } from 'clsx';
import { IconInfoCircle } from '@ui/icon/components/TablerIcons';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { Button } from '@ui/input/Button/Button';
import React, { useContext } from 'react';
import React from 'react';
import { Link } from 'react-router-dom';
import styles from './Info.module.scss';
@@ -29,7 +29,7 @@ export const Info = ({
onClick,
to,
}: InfoProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<div className={clsx(styles.info, INFO_ACCENT_CLASS_NAMES[accent])}>
@@ -1,6 +1,5 @@
import { clsx } from 'clsx';
import { useContext } from 'react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { Button } from '@ui/input/Button/Button';
import { type IconComponent } from '@ui/icon/types/IconComponent';
import { IconInfoCircle } from '@ui/icon/components/TablerIcons';
@@ -27,7 +26,7 @@ export const InlineBanner = ({
LeftIcon = IconInfoCircle,
className,
}: InlineBannerProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<Banner
@@ -1,8 +1,6 @@
import { useContext } from 'react';
import IconAddressBookRaw from '@assets/icons/address-book.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconAddressBookProps = Pick<
IconComponentProps,
@@ -10,7 +8,7 @@ type IconAddressBookProps = Pick<
>;
export const IconAddressBook = (props: IconAddressBookProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? 24;
const stroke = props.stroke ?? theme.icon.stroke.md;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconAnthropicRaw from '@assets/icons/anthropic.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconBrandAnthropicProps = Pick<IconComponentProps, 'size'>;
export const IconBrandAnthropic = (props: IconBrandAnthropicProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconAnthropicRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconBrandGeminiRaw from '@assets/icons/gemini.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconBrandGeminiProps = Pick<IconComponentProps, 'size'>;
export const IconBrandGemini = (props: IconBrandGeminiProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconBrandGeminiRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconBrandGroqRaw from '@assets/icons/groq.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconBrandGroqProps = Pick<IconComponentProps, 'size'>;
export const IconBrandGroq = (props: IconBrandGroqProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconBrandGroqRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconBrandMistralRaw from '@assets/icons/mistral.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconBrandMistralProps = Pick<IconComponentProps, 'size'>;
export const IconBrandMistral = (props: IconBrandMistralProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconBrandMistralRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconBrandXaiRaw from '@assets/icons/xai.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconBrandXaiProps = Pick<IconComponentProps, 'size' | 'color'>;
export const IconBrandXai = (props: IconBrandXaiProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
@@ -1,8 +1,6 @@
import { useContext } from 'react';
import { IconChartBar } from '@ui/icon/components/TablerIcons';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './IconChartBarHorizontal.module.scss';
@@ -12,7 +10,7 @@ type IconChartBarHorizontalProps = Pick<
>;
export const IconChartBarHorizontal = (props: IconChartBarHorizontalProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.md;
const stroke = props.stroke ?? theme.icon.stroke.sm;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconGmailRaw from '@assets/icons/gmail.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconGmailProps = Pick<IconComponentProps, 'size'>;
export const IconGmail = (props: IconGmailProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconGmailRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconGoogleRaw from '@assets/icons/google.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconGoogleProps = Pick<IconComponentProps, 'size'>;
export const IconGoogle = (props: IconGoogleProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconGoogleRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconGoogleCalendarRaw from '@assets/icons/google-calendar.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconGoogleCalendarProps = Pick<IconComponentProps, 'size'>;
export const IconGoogleCalendar = (props: IconGoogleCalendarProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconGoogleCalendarRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconLockRaw from '@assets/icons/lock.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconLockCustomProps = Pick<IconComponentProps, 'size'>;
export const IconLockCustom = (props: IconLockCustomProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconLockRaw height={size} width={size} />;
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IconMicrosoftRaw from '@assets/icons/microsoft.svg?react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
interface IconMicrosoftProps {
size?: number | string;
}
export const IconMicrosoft = (props: IconMicrosoftProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconMicrosoftRaw height={size} width={size} />;
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IconMicrosoftCalendarRaw from '@assets/icons/microsoft-calendar.svg?react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
interface IconMicrosoftCalendarProps {
size?: number | string;
}
export const IconMicrosoftCalendar = (props: IconMicrosoftCalendarProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconMicrosoftCalendarRaw height={size} width={size} />;
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IconMicrosoftOutlookRaw from '@assets/icons/microsoft-outlook.svg?react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
interface IconMicrosoftOutlookProps {
size?: number | string;
}
export const IconMicrosoftOutlook = (props: IconMicrosoftOutlookProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconMicrosoftOutlookRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconModelClaudeRaw from '@assets/icons/claude.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconModelClaudeProps = Pick<IconComponentProps, 'size'>;
export const IconModelClaude = (props: IconModelClaudeProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return <IconModelClaudeRaw height={size} width={size} />;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconOpenaiRaw from '@assets/icons/openai.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconProviderOpenaiProps = Pick<IconComponentProps, 'size' | 'color'>;
export const IconProviderOpenai = (props: IconProviderOpenaiProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconRelationManyToOneRaw from '@assets/icons/many-to-one.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconRelationManyToOneProps = Pick<IconComponentProps, 'size' | 'stroke'>;
export const IconRelationManyToOne = (props: IconRelationManyToOneProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? 24;
const stroke = props.stroke ?? theme.icon.stroke.md;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconTrashXOffRaw from '@assets/icons/trash-x-off.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconTrashXOffProps = Pick<IconComponentProps, 'size' | 'stroke'>;
export const IconTrashXOff = (props: IconTrashXOffProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
const stroke = props.stroke ?? theme.icon.stroke.md;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconTwentyStarRaw from '@assets/icons/twenty-star.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconTwentyStarProps = Pick<IconComponentProps, 'size' | 'stroke'>;
export const IconTwentyStar = (props: IconTwentyStarProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? 24;
const stroke = props.stroke ?? theme.icon.stroke.md;
@@ -1,13 +1,11 @@
import { useContext } from 'react';
import IconTwentyStarFilledRaw from '@assets/icons/twenty-star-filled.svg?react';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IconTwentyStarFilledProps = Pick<IconComponentProps, 'size' | 'stroke'>;
export const IconTwentyStarFilled = (props: IconTwentyStarFilledProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? 24;
const stroke = props.stroke ?? theme.icon.stroke.md;
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconArrayRaw from '@assets/icons/illustration-array.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconArrayProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconArray = (props: IllustrationIconArrayProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconCalendarEventRaw from '@assets/icons/illustration-calendar-event.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconCalendarEventProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconCalendarEvent = (
props: IllustrationIconCalendarEventProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconCalendarTimeRaw from '@assets/icons/illustration-calendar-time.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconCalendarTimeProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconCalendarTime = (
props: IllustrationIconCalendarTimeProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconCurrencyRaw from '@assets/icons/illustration-currency.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconCurrencyProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconCurrency = (
props: IllustrationIconCurrencyProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconFileRaw from '@assets/icons/illustration-file.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconFileProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconFile = (props: IllustrationIconFileProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconJsonRaw from '@assets/icons/illustration-json.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconJsonProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconJson = (props: IllustrationIconJsonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconLinkRaw from '@assets/icons/illustration-link.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconLinkProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconLink = (props: IllustrationIconLinkProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconMailRaw from '@assets/icons/illustration-mail.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconMailProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconMail = (props: IllustrationIconMailProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconManyToManyRaw from '@assets/icons/illustration-many-to-many.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconManyToManyProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconManyToMany = (
props: IllustrationIconManyToManyProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconMapRaw from '@assets/icons/illustration-map.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconMapProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconMap = (props: IllustrationIconMapProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconNumbersRaw from '@assets/icons/illustration-numbers.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconNumbersProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconNumbers = (
props: IllustrationIconNumbersProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconOneToManyRaw from '@assets/icons/illustration-one-to-many.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconOneToManyProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconOneToMany = (
props: IllustrationIconOneToManyProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconOneToOneRaw from '@assets/icons/illustration-one-to-one.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconOneToOneProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconOneToOne = (
props: IllustrationIconOneToOneProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconPhoneRaw from '@assets/icons/illustration-phone.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconPhoneProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconPhone = (props: IllustrationIconPhoneProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
@@ -1,16 +1,14 @@
import { useContext } from 'react';
import IllustrationIconSettingRaw from '@assets/icons/illustration-setting.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconSettingProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconSetting = (
props: IllustrationIconSettingProps,
) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconStarRaw from '@assets/icons/illustration-star.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconStarProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconStar = (props: IllustrationIconStarProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconTagRaw from '@assets/icons/illustration-tag.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconTagProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconTag = (props: IllustrationIconTagProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconTagsRaw from '@assets/icons/illustration-tags.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconTagsProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconTags = (props: IllustrationIconTagsProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconTextRaw from '@assets/icons/illustration-text.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconTextProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconText = (props: IllustrationIconTextProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconToggleRaw from '@assets/icons/illustration-toggle.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconToggleProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconToggle = (props: IllustrationIconToggleProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconUidRaw from '@assets/icons/illustration-uid.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconUidProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconUid = (props: IllustrationIconUidProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,14 +1,12 @@
import { useContext } from 'react';
import IllustrationIconUserRaw from '@assets/icons/illustration-user.svg?react';
import { IllustrationIconWrapper } from '@ui/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type IllustrationIconUserProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconUser = (props: IllustrationIconUserProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const size = props.size ?? theme.icon.size.lg;
return (
<IllustrationIconWrapper>
@@ -1,7 +1,7 @@
import { Toggle } from '@ui/input';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import { useContext, useId } from 'react';
import { useId } from 'react';
import styles from './AdvancedSettingsToggle.module.scss';
@@ -18,7 +18,7 @@ export const AdvancedSettingsToggle = ({
label = 'Advanced',
className,
}: AdvancedSettingsToggleProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const onChange = (newValue: boolean) => {
setIsAdvancedModeEnabled(newValue);
@@ -1,10 +1,10 @@
import { clsx } from 'clsx';
import { type MotionProps, motion } from 'framer-motion';
import React, { useContext, useMemo } from 'react';
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';
import { Pill } from '@ui/data-display/Pill/Pill';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { themeCssVariables, useTheme } from '@ui/theme-constants';
import { GRAY_SCALE_LIGHT } from '@ui/theme/constants/GrayScaleLight';
import { useIsMobile } from '@ui/utilities';
import { getOsShortcutSeparator } from '@ui/utilities/device/getOsShortcutSeparator';
@@ -335,7 +335,7 @@ export const AnimatedButton = ({
dataGloballyPreventClickOutside,
soonLabel = 'Soon',
}: AnimatedButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const isMobile = useIsMobile();
const isDisabled = soon || disabled;
@@ -1,13 +1,13 @@
import { clsx } from 'clsx';
import { motion, type MotionProps } from 'framer-motion';
import { type ComponentProps, type MouseEvent, useContext } from 'react';
import { type ComponentProps, type MouseEvent } from 'react';
import { type IconComponent } from '@ui/icon';
import {
type LightIconButtonAccent,
type LightIconButtonSize,
} from '@ui/input/LightIconButton/LightIconButton';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './AnimatedLightIconButton.module.scss';
@@ -40,7 +40,7 @@ export const AnimatedLightIconButton = ({
onClick,
title,
}: AnimatedLightIconButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,7 +1,6 @@
import { type IconComponent } from '@ui/icon';
import { Loader } from '@ui/feedback';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import styles from './ButtonIcon.module.scss';
@@ -12,7 +11,7 @@ export const ButtonIcon = ({
Icon?: IconComponent;
isLoading?: boolean;
}) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<div className={styles.iconWrapper}>
@@ -4,9 +4,13 @@ import { BASE_CODE_EDITOR_THEME_ID } from '@ui/input/CodeEditor/constants/BaseCo
import { getBaseCodeEditorTheme } from '@ui/input/CodeEditor/utils/getBaseCodeEditorTheme';
import { ResizeHandle } from '@ui/layout/ResizeHandle/ResizeHandle';
import { useResizeHandle } from '@ui/layout/ResizeHandle/hooks/useResizeHandle';
import { ThemeContext, type ThemeType } from '@ui/theme-constants';
import {
useTheme,
useThemeColorScheme,
type ThemeType,
} from '@ui/theme-constants';
import { type editor } from 'monaco-editor';
import { type KeyboardEvent, useContext, useEffect, useState } from 'react';
import { type KeyboardEvent, useEffect, useState } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import styles from './CodeEditor.module.scss';
@@ -56,7 +60,8 @@ export const CodeEditor = ({
contentPadding = 'default',
autoHeight = false,
}: CodeEditorProps) => {
const { theme, colorScheme } = useContext(ThemeContext);
const theme = useTheme();
const colorScheme = useThemeColorScheme();
const [monaco, setMonaco] = useState<Monaco | undefined>(undefined);
const [editor, setEditor] = useState<
editor.IStandaloneCodeEditor | undefined
@@ -1,9 +1,8 @@
import { clsx } from 'clsx';
import { useContext } from 'react';
import { Link } from 'react-router-dom';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './FloatingButton.module.scss';
@@ -37,7 +36,7 @@ export const FloatingButton = ({
focus = false,
to,
}: FloatingButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
// Replaces the legacy Linaria `as` polymorphism: react-router Link when a
// `to` is provided, a native button otherwise. Typed as any to forward all
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import React, { useContext } from 'react';
import React from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './FloatingIconButton.module.scss';
@@ -40,7 +40,7 @@ export const FloatingIconButton = ({
onClick,
isActive,
}: FloatingIconButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import React, { useContext } from 'react';
import React from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './IconButton.module.scss';
@@ -40,7 +40,7 @@ export const IconButton = ({
onClick,
to,
}: IconButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import React, { useContext } from 'react';
import React from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './InsideButton.module.scss';
@@ -21,7 +21,7 @@ export const InsideButton = ({
onClick,
disabled = false,
}: InsideButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import { type MouseEvent, useContext } from 'react';
import { type MouseEvent } from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './LightButton.module.scss';
@@ -31,7 +31,7 @@ export const LightButton = ({
type = 'button',
onClick,
}: LightButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import { type ComponentProps, type MouseEvent, useContext } from 'react';
import { type ComponentProps, type MouseEvent } from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './LightIconButton.module.scss';
@@ -35,7 +35,7 @@ export const LightIconButton = ({
onClick,
title,
}: LightIconButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import React, { type FunctionComponent, useContext } from 'react';
import React, { type FunctionComponent } from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './MainButton.module.scss';
@@ -31,7 +31,7 @@ export const MainButton = ({
disabled,
className,
}: MainButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
// Replicates the legacy ternary exactly: fullWidth wins over width, and a
// falsy width (0) falls back to the 'auto' default.
@@ -1,8 +1,8 @@
import { clsx } from 'clsx';
import React, { useContext } from 'react';
import React from 'react';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './RoundedIconButton.module.scss';
@@ -21,7 +21,7 @@ export const RoundedIconButton = ({
size = 'small',
'aria-label': ariaLabel,
}: RoundedIconButtonProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -1,10 +1,10 @@
import { Input } from '@base-ui/react/input';
import { clsx } from 'clsx';
import { type ReactNode, useContext, useId, useState } from 'react';
import { type ReactNode, useId, useState } from 'react';
import { IconFilter, IconSearch } from '@ui/icon';
import { IconButton } from '@ui/input/IconButton/IconButton';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './SearchInput.module.scss';
@@ -35,7 +35,7 @@ export const SearchInput = ({
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
}: SearchInputProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const [isFocused, setIsFocused] = useState(false);
const generatedId = useId();
const inputId = id ?? generatedId;
@@ -1,8 +1,8 @@
import { Pill } from '@ui/data-display/Pill/Pill';
import { Avatar } from '@ui/data-display';
import { type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { type ReactElement, useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import { type ReactElement } from 'react';
import { StyledTabHover } from '@ui/input/TabButton/parts/StyledTabBase';
export type TabContentProps = {
@@ -29,7 +29,7 @@ export const TabContent = ({
contentSize = 'sm',
className,
}: TabContentProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const iconColor = active
? theme.font.color.primary
@@ -1,9 +1,8 @@
import { VisibilityHidden } from '@ui/accessibility';
import { IconChevronDown } from '@ui/icon';
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { themeCssVariables, useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import { useContext } from 'react';
import styles from './JsonArrow.module.scss';
@@ -16,7 +15,7 @@ export const JsonArrow = ({
onClick: () => void;
variant?: 'blue' | 'red';
}) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const { arrowButtonCollapsedLabel, arrowButtonExpandedLabel } =
useJsonTreeContextOrThrow();
@@ -1,8 +1,7 @@
import { type IconComponent } from '@ui/icon';
import { type JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import { useContext } from 'react';
import styles from './JsonNodeLabel.module.scss';
@@ -15,7 +14,7 @@ export const JsonNodeLabel = ({
Icon: IconComponent;
highlighting?: JsonNodeHighlighting | undefined;
}) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<span
@@ -1,7 +1,7 @@
import { motion } from 'framer-motion';
import React, { useContext } from 'react';
import React from 'react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './AnimatedCircleLoading.module.scss';
@@ -10,7 +10,7 @@ export const AnimatedCircleLoading = ({
}: {
children: React.ReactNode;
}) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<motion.div
@@ -1,7 +1,6 @@
import { motion } from 'framer-motion';
import { useContext } from 'react';
import { type AnimationDuration } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type AnimatedEaseInProps = Omit<
React.ComponentProps<typeof motion.div>,
@@ -14,7 +13,7 @@ export const AnimatedEaseIn = ({
children,
duration = 'normal',
}: AnimatedEaseInProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const initial = { opacity: 0 };
const animate = { opacity: 1 };
@@ -1,7 +1,6 @@
import { AnimatePresence, motion } from 'framer-motion';
import { useContext } from 'react';
import { type AnimationDuration } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type AnimatedEaseInOutProps = {
isOpen: boolean;
@@ -20,7 +19,7 @@ export const AnimatedEaseInOut = ({
duration = 'normal',
initial = true,
}: AnimatedEaseInOutProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<AnimatePresence initial={initial}>
@@ -4,9 +4,9 @@ import { type AnimationDurations } from '@ui/layout/AnimatedExpandableContainer/
import { type AnimationMode } from '@ui/layout/AnimatedExpandableContainer/types/AnimationMode';
import { type AnimationSize } from '@ui/layout/AnimatedExpandableContainer/types/AnimationSize';
import { getExpandableAnimationConfig } from '@ui/layout/AnimatedExpandableContainer/utils/getExpandableAnimationConfig';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { AnimatePresence, motion } from 'framer-motion';
import { type ReactNode, useContext, useRef, useState } from 'react';
import { type ReactNode, useRef, useState } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
type AnimatedExpandableContainerProps = {
@@ -28,7 +28,7 @@ export const AnimatedExpandableContainer = ({
containAnimation = true,
initial = true,
}: AnimatedExpandableContainerProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const contentRef = useRef<HTMLDivElement>(null);
const [size, setSize] = useState<AnimationSize>(0);
@@ -1,7 +1,6 @@
import { AnimatePresence, motion } from 'framer-motion';
import { useContext } from 'react';
import { type AnimationDuration } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type AnimatedFadeOutProps = {
isOpen: boolean;
@@ -18,7 +17,7 @@ export const AnimatedFadeOut = ({
marginBottom,
marginTop,
}: AnimatedFadeOutProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<AnimatePresence>
@@ -1,8 +1,8 @@
import { motion } from 'framer-motion';
import React, { useContext } from 'react';
import React from 'react';
import { type IconComponent } from '@ui/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './AnimatedIconCrossfade.module.scss';
@@ -19,7 +19,7 @@ export const AnimatedIconCrossfade = ({
InactiveIcon,
size,
}: AnimatedIconCrossfadeProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const iconSize = size ?? theme.icon.size.sm;
@@ -1,7 +1,7 @@
import { type HTMLMotionProps, motion } from 'framer-motion';
import { type AnimationDuration } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { type JSX, useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import { type JSX } from 'react';
import styles from './AnimatedRotate.module.scss';
@@ -18,7 +18,7 @@ export const AnimatedRotate = ({
duration = 'fast',
animateOnHover,
}: AnimatedRotateProps): JSX.Element => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const initial = { opacity: 0, rotate: -90 };
const animate = { opacity: 1, rotate: 0 };
@@ -1,13 +1,12 @@
import { motion } from 'framer-motion';
import { useContext } from 'react';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
type AnimatedTranslationProps = {
children: React.ReactNode;
};
export const AnimatedTranslation = ({ children }: AnimatedTranslationProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<motion.div
@@ -1,15 +1,14 @@
import { IconBrandGithub } from '@ui/icon';
import { ClickToActionLink } from '@ui/navigation/ClickToActionLink/ClickToActionLink';
import { GITHUB_LINK } from '@ui/navigation/Link/constants/GithubLink';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
interface GithubVersionLinkProps {
version: string;
}
export const GithubVersionLink = ({ version }: GithubVersionLinkProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<ClickToActionLink href={GITHUB_LINK} target="_blank" rel="noreferrer">
@@ -6,12 +6,11 @@ import {
type MouseEvent,
type ReactElement,
type ReactNode,
useContext,
} from 'react';
import { MenuItemHotKeys } from '@ui/navigation/MenuItemHotKeys/MenuItemHotKeys';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import { type Nullable } from '@ui/utilities/types/Nullable';
import { MenuItemLeftContent } from '@ui/navigation/MenuItem/parts/MenuItemLeftContent';
@@ -98,7 +97,7 @@ export const MenuItem = ({
hotKeys,
isSubMenuOpened = false,
}: MenuItemProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0;
const handleMenuItemClick = (event: MouseEvent<HTMLDivElement>) => {
@@ -1,8 +1,7 @@
import { TintedIconTile } from '@ui/data-display';
import { type IconComponent } from '@ui/icon';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import { isDefined } from '@ui/utilities/utils/isDefined';
import { MenuItemIconBoxContainer } from '@ui/navigation/MenuItem/parts/MenuItemIconBoxContainer';
@@ -19,7 +18,7 @@ export const MenuItemIcon = ({
withContainer = false,
withContainerBackground = true,
}: MenuItemIconProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
if (!Icon) {
return null;
@@ -1,9 +1,8 @@
import { TintedIconTile } from '@ui/data-display';
import { type IconComponent, IconGripVertical } from '@ui/icon';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import { useContext } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import { MenuItemIconBoxContainer } from '@ui/navigation/MenuItem/parts/MenuItemIconBoxContainer';
@@ -22,7 +21,7 @@ export const MenuItemIconWithGripSwap = ({
withIconContainer = false,
gripIconColor,
}: MenuItemIconWithGripSwapProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
if (!LeftIcon) {
return null;
@@ -1,10 +1,10 @@
import { isNonEmptyString, isString } from '@sniptt/guards';
import { type ReactNode, useContext } from 'react';
import { type ReactNode } from 'react';
import { type IconComponent, IconGripVertical } from '@ui/icon';
import { OverflowingTextWithTooltip } from '@ui/surfaces';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { themeCssVariables, useTheme } from '@ui/theme-constants';
import { type MenuItemDraggableGripMode } from '@ui/navigation/MenuItem/types/MenuItemDraggableGripMode';
import { MenuItemIcon } from '@ui/navigation/MenuItem/parts/MenuItemIcon';
import { MenuItemIconBoxContainer } from '@ui/navigation/MenuItem/parts/MenuItemIconBoxContainer';
@@ -46,7 +46,7 @@ export const MenuItemLeftContent = ({
gripMode = 'never',
disabled = false,
}: MenuItemLeftContentProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
const gripIconColor = withIconContainer
? themeCssVariables.font.color.tertiary
@@ -1,6 +1,5 @@
import { IconChevronRight, type IconComponent } from '@ui/icon';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import { MenuItemLeftContent } from '@ui/navigation/MenuItem/parts/MenuItemLeftContent';
import {
StyledMenuItemBase,
@@ -22,7 +21,7 @@ export const MenuItemNavigate = ({
className,
onClick,
}: MenuItemNavigateProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<StyledMenuItemBase onClick={onClick} className={className}>
@@ -2,12 +2,11 @@ import { isString } from '@sniptt/guards';
import { IconCheck, IconChevronRight, type IconComponent } from '@ui/icon';
import { OverflowingTextWithTooltip } from '@ui/surfaces';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { clsx } from 'clsx';
import {
type ComponentPropsWithoutRef,
forwardRef,
useContext,
type ReactNode,
} from 'react';
import { MenuItemLeftContent } from '@ui/navigation/MenuItem/parts/MenuItemLeftContent';
@@ -77,7 +76,7 @@ export const MenuItemSelect = ({
contextualText,
contextualTextPosition = 'left',
}: MenuItemSelectProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<StyledMenuItemSelect
@@ -1,4 +1,4 @@
import { type ReactNode, useContext } from 'react';
import { type ReactNode } from 'react';
import {
StyledMenuItemIconCheck,
@@ -8,7 +8,7 @@ import {
} from '@ui/navigation/MenuItem/parts/StyledMenuItemBase';
import { OverflowingTextWithTooltip } from '@ui/surfaces';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import { StyledMenuItemSelect } from '@ui/navigation/MenuItemSelect/MenuItemSelect';
import styles from './MenuItemSelectAvatar.module.scss';
@@ -36,7 +36,7 @@ export const MenuItemSelectAvatar = ({
focused,
testId,
}: MenuItemSelectAvatarProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<StyledMenuItemSelect
@@ -6,8 +6,7 @@ import {
import { ColorSample, type ColorSampleVariant } from '@ui/data-display';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import {
DEFAULT_COLOR_LABELS,
type ColorLabels,
@@ -35,7 +34,7 @@ export const MenuItemSelectColor = ({
variant = 'default',
colorLabels = DEFAULT_COLOR_LABELS,
}: MenuItemSelectColorProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<StyledMenuItemSelect
@@ -6,8 +6,7 @@ import {
import { Tag } from '@ui/data-display';
import { type IconComponent } from '@ui/icon';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { useTheme } from '@ui/theme-constants';
import { StyledMenuItemSelect } from '@ui/navigation/MenuItemSelect/MenuItemSelect';
type MenuItemSelectTagProps = {
@@ -33,7 +32,7 @@ export const MenuItemSelectTag = ({
variant = 'solid',
LeftIcon,
}: MenuItemSelectTagProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<StyledMenuItemSelect
@@ -1,11 +1,10 @@
import { isNonEmptyString } from '@sniptt/guards';
import { clsx } from 'clsx';
import { useContext } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import { type IconComponent } from '@ui/icon';
import { AppTooltip, TooltipDelay, TooltipPosition } from '@ui/surfaces';
import { ThemeContext } from '@ui/theme-constants';
import { useTheme } from '@ui/theme-constants';
import styles from './MenuPicker.module.scss';
@@ -38,7 +37,7 @@ export const MenuPicker = ({
tooltipDelay = TooltipDelay.noDelay,
tooltipOffset = 5,
}: MenuPickerProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<>
@@ -1,7 +1,5 @@
import { useContext } from 'react';
import { type IconComponent } from '@ui/icon/types/IconComponent';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { themeCssVariables, useTheme } from '@ui/theme-constants';
import styles from './NavigationBarItem.module.scss';
@@ -18,7 +16,7 @@ export const NavigationBarItem = ({
onClick,
ariaLabel,
}: NavigationBarItemProps) => {
const { theme } = useContext(ThemeContext);
const theme = useTheme();
return (
<button
@@ -2,6 +2,7 @@ import { Tooltip } from '@base-ui/react/tooltip';
import { isNonEmptyString } from '@sniptt/guards';
import { clsx } from 'clsx';
import { useEffect, useRef, useState } from 'react';
import { useThemeContainer } from '@ui/theme-constants';
import { isDefined } from '@ui/utilities/utils/isDefined';
import styles from './AppTooltip.module.scss';
@@ -88,6 +89,8 @@ export const AppTooltip = ({
}
};
const themeContainer = useThemeContainer();
const [show, setShow] = useState(false);
const [anchorElements, setAnchorElements] = useState<Element[]>([]);
const [activeAnchor, setActiveAnchor] = useState<Element | null>(null);
@@ -283,7 +286,7 @@ export const AppTooltip = ({
}
}}
>
<Tooltip.Portal>
<Tooltip.Portal container={themeContainer ?? undefined}>
<Tooltip.Positioner
anchor={activeAnchor}
side={side}
@@ -1,6 +1,7 @@
import { Dialog } from '@base-ui/react/dialog';
import { clsx } from 'clsx';
import { useRef } from 'react';
import { useThemeContainer } from '@ui/theme-constants';
import { isDefined } from '@ui/utilities/utils/isDefined';
import { type ModalOverlay } from '@ui/surfaces/Modal/types/ModalOverlay';
@@ -63,6 +64,11 @@ export const Modal = ({
const internalRef = useRef<HTMLDivElement>(null);
const resolvedRef = externalRef ?? internalRef;
const themeContainer = useThemeContainer();
const resolvedContainer = isDefined(container)
? container
: (themeContainer ?? undefined);
const handleBackdropMouseDown = (e: React.MouseEvent) => {
e.stopPropagation();
onBackdropMouseDown?.(e);
@@ -73,7 +79,7 @@ export const Modal = ({
// Modal, open/close is fully controlled by the isOpen prop
return (
<Dialog.Root open={isOpen} modal={false} disablePointerDismissal>
<Dialog.Portal container={isDefined(container) ? container : undefined}>
<Dialog.Portal container={resolvedContainer}>
<ModalBackdrop
data-testid={backdropTestId}
data-click-outside-id={backdropClickOutsideId}
@@ -1,5 +1,9 @@
import { createContext, useLayoutEffect, useState } from 'react';
import { clsx } from 'clsx';
import { createContext, useLayoutEffect, useRef, useState } from 'react';
import { isDefined } from '@ui/utilities/utils/isDefined';
import { ThemeScopeContext } from './ThemeScopeContext';
import { themeCssVariables } from './themeCssVariables';
type StringLeaves<T> = {
@@ -47,7 +51,9 @@ export type ThemeContextType = {
colorScheme: 'light' | 'dark';
};
const computeThemeFromCss = (): ThemeType => {
export type ThemeOverrides = Record<string, string | number>;
const computeThemeFromCss = (sourceElement?: HTMLElement): ThemeType => {
if (
typeof document === 'undefined' ||
typeof getComputedStyle !== 'function'
@@ -55,7 +61,9 @@ const computeThemeFromCss = (): ThemeType => {
return themeCssVariables as unknown as ThemeType;
}
const computedStyle = getComputedStyle(document.documentElement);
const computedStyle = getComputedStyle(
sourceElement ?? document.documentElement,
);
const resolve = (obj: Record<string, unknown>): Record<string, unknown> => {
const result: Record<string, unknown> = {};
@@ -99,23 +107,68 @@ export const ThemeContext = createContext<ThemeContextType>({
export const ThemeProvider = ({
children,
colorScheme,
applyToRoot = true,
overrides,
className,
}: {
children: React.ReactNode;
colorScheme: 'light' | 'dark';
applyToRoot?: boolean;
overrides?: ThemeOverrides;
className?: string;
}) => {
const isScoped = isDefined(overrides) || !applyToRoot;
const wrapperRef = useRef<HTMLDivElement>(null);
const [theme, setTheme] = useState<ThemeType>(() => {
applyColorSchemeClass(colorScheme);
if (applyToRoot) {
applyColorSchemeClass(colorScheme);
}
return computeThemeFromCss();
});
const [scopeContainer, setScopeContainer] = useState<HTMLElement | null>(
null,
);
const overridesKey = isDefined(overrides) ? JSON.stringify(overrides) : '';
useLayoutEffect(() => {
applyColorSchemeClass(colorScheme);
setTheme(computeThemeFromCss());
}, [colorScheme]);
if (applyToRoot) {
applyColorSchemeClass(colorScheme);
}
setTheme(
computeThemeFromCss(
isScoped ? (wrapperRef.current ?? undefined) : undefined,
),
);
setScopeContainer(isScoped ? wrapperRef.current : null);
}, [colorScheme, applyToRoot, isScoped, overridesKey]);
const contextValue = { theme, colorScheme };
if (!isScoped) {
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
}
const overridesStyle = (overrides ?? {}) as React.CSSProperties;
return (
<ThemeContext.Provider value={{ theme, colorScheme }}>
{children}
<ThemeContext.Provider value={contextValue}>
<ThemeScopeContext.Provider value={scopeContainer}>
<div
ref={wrapperRef}
className={clsx(applyToRoot ? undefined : colorScheme, className)}
style={{ display: 'contents', ...overridesStyle }}
>
{children}
</div>
</ThemeScopeContext.Provider>
</ThemeContext.Provider>
);
};
@@ -0,0 +1,3 @@
import { createContext } from 'react';
export const ThemeScopeContext = createContext<HTMLElement | null>(null);
@@ -10,5 +10,13 @@
export { MOBILE_VIEWPORT } from './constants';
export { getNextThemeColor } from './getNextThemeColor';
export { themeCssVariables } from './themeCssVariables';
export type { ThemeType, ThemeContextType } from './ThemeProvider';
export type {
ThemeType,
ThemeContextType,
ThemeOverrides,
} from './ThemeProvider';
export { ThemeContext, ThemeProvider } from './ThemeProvider';
export { ThemeScopeContext } from './ThemeScopeContext';
export { useTheme } from './useTheme';
export { useThemeColorScheme } from './useThemeColorScheme';
export { useThemeContainer } from './useThemeContainer';
@@ -0,0 +1,5 @@
import { useContext } from 'react';
import { ThemeContext, type ThemeType } from './ThemeProvider';
export const useTheme = (): ThemeType => useContext(ThemeContext).theme;
@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ThemeContext } from './ThemeProvider';
export const useThemeColorScheme = (): 'light' | 'dark' =>
useContext(ThemeContext).colorScheme;
@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ThemeScopeContext } from './ThemeScopeContext';
export const useThemeContainer = (): HTMLElement | null =>
useContext(ThemeScopeContext);