mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 10:19:08 +00:00
Highlight chat folder with open context menu
This commit is contained in:
@@ -161,7 +161,7 @@
|
||||
}
|
||||
|
||||
.module-message:hover .module-message__buttons,
|
||||
.module-message__buttons:has([data-state='open']) {
|
||||
.module-message__buttons:has([data-axo-contextmenu-state='open']) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -5046,7 +5046,7 @@ button.module-calling-participants-list__contact {
|
||||
}
|
||||
|
||||
&:hover:not(:disabled, &--disabled, &--is-selected),
|
||||
&[data-state='open'] {
|
||||
&[data-axo-contextmenu-state='open'] {
|
||||
background-color: light-dark(
|
||||
variables.$color-gray-05,
|
||||
variables.$color-gray-75
|
||||
|
||||
@@ -1099,7 +1099,7 @@ $secondary-text-color: light-dark(
|
||||
.Preferences__ChatFolders__ChatSelection__Item--Clickable {
|
||||
cursor: pointer;
|
||||
&:hover .Preferences__ChatFolders__ChatSelection__ItemContent,
|
||||
.Preferences__ChatFolders__ChatSelection__ItemContent[data-state='open'] {
|
||||
.Preferences__ChatFolders__ChatSelection__ItemContent[data-axo-contextmenu-state='open'] {
|
||||
background: light-dark(variables.$color-gray-02, variables.$color-gray-80);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import React, {
|
||||
createContext,
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { memo, useCallback, useRef, useState } from 'react';
|
||||
import type { ReactNode, MouseEvent, FC } from 'react';
|
||||
import { useLayoutEffect } from '@react-aria/utils';
|
||||
import { tw } from './tw.dom.js';
|
||||
import { assert } from './_internal/assert.dom.js';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from './_internal/StrictContext.dom.js';
|
||||
|
||||
const Namespace = 'AriaClickable';
|
||||
|
||||
@@ -51,8 +48,8 @@ export namespace AriaClickable {
|
||||
|
||||
type TriggerStateUpdate = (state: TriggerState) => void;
|
||||
|
||||
const TriggerStateUpdateContext = createContext<TriggerStateUpdate | null>(
|
||||
null
|
||||
const TriggerStateUpdateContext = createStrictContext<TriggerStateUpdate>(
|
||||
`${Namespace}.Root`
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -174,13 +171,7 @@ export namespace AriaClickable {
|
||||
*/
|
||||
export const HiddenTrigger: FC<HiddenTriggerProps> = memo(props => {
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
const onTriggerStateUpdate = useContext(TriggerStateUpdateContext);
|
||||
|
||||
if (onTriggerStateUpdate == null) {
|
||||
throw new Error(
|
||||
`<${Namespace}.HiddenTrigger> must be wrapped with <${Namespace}.Root>`
|
||||
);
|
||||
}
|
||||
const onTriggerStateUpdate = useStrictContext(TriggerStateUpdateContext);
|
||||
|
||||
const onTriggerStateUpdateRef = useRef(onTriggerStateUpdate);
|
||||
useLayoutEffect(() => {
|
||||
|
||||
@@ -31,6 +31,7 @@ export namespace ExperimentalAxoBadge {
|
||||
'flex size-fit items-center justify-center-safe overflow-clip',
|
||||
'rounded-full font-semibold',
|
||||
'bg-color-fill-primary text-label-primary-on-color',
|
||||
'forced-color-adjust-none forced-colors:bg-[Mark] forced-colors:text-[MarkText]',
|
||||
'select-none'
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ContextMenu } from 'radix-ui';
|
||||
import type {
|
||||
FC,
|
||||
@@ -12,6 +19,10 @@ import { AxoSymbol } from './AxoSymbol.dom.js';
|
||||
import { AxoBaseMenu } from './_internal/AxoBaseMenu.dom.js';
|
||||
import { tw } from './tw.dom.js';
|
||||
import { assert } from './_internal/assert.dom.js';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from './_internal/StrictContext.dom.js';
|
||||
|
||||
const Namespace = 'AxoContextMenu';
|
||||
|
||||
@@ -55,6 +66,14 @@ const Namespace = 'AxoContextMenu';
|
||||
* ```
|
||||
*/
|
||||
export namespace AxoContextMenu {
|
||||
export type RootContextType = Readonly<{
|
||||
open: boolean;
|
||||
}>;
|
||||
|
||||
export const RootContext = createStrictContext<RootContextType>(
|
||||
`${Namespace}.RootContext`
|
||||
);
|
||||
|
||||
/**
|
||||
* Component: <AxoContextMenu.Root>
|
||||
* --------------------------------
|
||||
@@ -63,10 +82,27 @@ export namespace AxoContextMenu {
|
||||
export type RootProps = AxoBaseMenu.MenuRootProps;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
const { onOpenChange } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(nextOpen: boolean) => {
|
||||
setOpen(nextOpen);
|
||||
onOpenChange?.(nextOpen);
|
||||
},
|
||||
[onOpenChange]
|
||||
);
|
||||
|
||||
const context = useMemo(() => {
|
||||
return { open };
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Root onOpenChange={props.onOpenChange}>
|
||||
<RootContext.Provider value={context}>
|
||||
<ContextMenu.Root onOpenChange={handleOpenChange}>
|
||||
{props.children}
|
||||
</ContextMenu.Root>
|
||||
</RootContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -125,6 +161,7 @@ export namespace AxoContextMenu {
|
||||
export type TriggerProps = AxoBaseMenu.MenuTriggerProps;
|
||||
|
||||
export const Trigger: FC<TriggerProps> = memo(props => {
|
||||
const context = useStrictContext(RootContext);
|
||||
const [disableCurrentEvent, setDisableCurrentEvent] = useState(false);
|
||||
|
||||
const handleContextMenuCapture = useCallback(
|
||||
@@ -164,7 +201,8 @@ export namespace AxoContextMenu {
|
||||
onContextMenu={handleContextMenu}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={disableCurrentEvent || props.disabled}
|
||||
data-axo-context-menu-trigger
|
||||
data-axo-contextmenu-trigger
|
||||
data-axo-contextmenu-state={context.open ? 'open' : 'closed'}
|
||||
>
|
||||
{props.children}
|
||||
</ContextMenu.Trigger>
|
||||
@@ -176,7 +214,7 @@ export namespace AxoContextMenu {
|
||||
export function useAxoContextMenuOutsideKeyboardTrigger(): KeyboardEventHandler {
|
||||
return useContextMenuTriggerKeyboardEventHandler(event => {
|
||||
return assert(
|
||||
event.currentTarget.querySelector('[data-axo-context-menu-trigger]'),
|
||||
event.currentTarget.querySelector('[data-axo-contextmenu-trigger]'),
|
||||
`Couldn't find <${Namespace}.Trigger> element, did you forget to pass all html props through?`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -303,8 +303,7 @@ export namespace AxoDropdownMenu {
|
||||
const descriptionId = useId();
|
||||
|
||||
const { labelRef, descriptionRef } = useAriaLabellingContext(
|
||||
`<${Namespace}.Header>`,
|
||||
`<${Namespace}.Content/SubContent>`
|
||||
`${Namespace}.Content/SubContent`
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,24 +2,22 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FC } from 'react';
|
||||
import React, { createContext, memo, useContext } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import type { AxoBaseMenu } from './_internal/AxoBaseMenu.dom.js';
|
||||
import { assert, unreachable } from './_internal/assert.dom.js';
|
||||
import { unreachable } from './_internal/assert.dom.js';
|
||||
import { AxoDropdownMenu } from './AxoDropdownMenu.dom.js';
|
||||
import { AxoContextMenu } from './AxoContextMenu.dom.js';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from './_internal/StrictContext.dom.js';
|
||||
|
||||
const Namespace = 'AxoMenuBuilder';
|
||||
|
||||
export namespace AxoMenuBuilder {
|
||||
export type Renderer = 'AxoDropdownMenu' | 'AxoContextMenu';
|
||||
|
||||
const MenuBuilderContext = createContext<Renderer | null>(null);
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function useMenuBuilderContext(): Renderer {
|
||||
const context = useContext(MenuBuilderContext);
|
||||
return assert(context, `Must be wrapped with <${Namespace}.Root>`);
|
||||
}
|
||||
const MenuBuilderContext = createStrictContext<Renderer>(`${Namespace}.Root`);
|
||||
|
||||
export type RootProps = AxoBaseMenu.MenuRootProps &
|
||||
Readonly<{
|
||||
@@ -48,7 +46,7 @@ export namespace AxoMenuBuilder {
|
||||
Root.displayName = `${Namespace}.Root`;
|
||||
|
||||
export const Trigger: FC<AxoBaseMenu.MenuTriggerProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Trigger {...props} />;
|
||||
}
|
||||
@@ -61,7 +59,7 @@ export namespace AxoMenuBuilder {
|
||||
Trigger.displayName = `${Namespace}.Trigger`;
|
||||
|
||||
export const Content: FC<AxoBaseMenu.MenuContentProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Content {...props} />;
|
||||
}
|
||||
@@ -74,7 +72,7 @@ export namespace AxoMenuBuilder {
|
||||
Content.displayName = `${Namespace}.Content`;
|
||||
|
||||
export const Item: FC<AxoBaseMenu.MenuItemProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Item {...props} />;
|
||||
}
|
||||
@@ -87,7 +85,7 @@ export namespace AxoMenuBuilder {
|
||||
Item.displayName = `${Namespace}.Item`;
|
||||
|
||||
export const Group: FC<AxoBaseMenu.MenuGroupProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Group {...props} />;
|
||||
}
|
||||
@@ -100,7 +98,7 @@ export namespace AxoMenuBuilder {
|
||||
Group.displayName = `${Namespace}.Group`;
|
||||
|
||||
export const Label: FC<AxoBaseMenu.MenuLabelProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Label {...props} />;
|
||||
}
|
||||
@@ -113,7 +111,7 @@ export namespace AxoMenuBuilder {
|
||||
Label.displayName = `${Namespace}.Label`;
|
||||
|
||||
export const Separator: FC<AxoBaseMenu.MenuSeparatorProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Separator {...props} />;
|
||||
}
|
||||
@@ -127,7 +125,7 @@ export namespace AxoMenuBuilder {
|
||||
|
||||
export const CheckboxItem: FC<AxoBaseMenu.MenuCheckboxItemProps> = memo(
|
||||
props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.CheckboxItem {...props} />;
|
||||
}
|
||||
@@ -141,7 +139,7 @@ export namespace AxoMenuBuilder {
|
||||
CheckboxItem.displayName = `${Namespace}.CheckboxItem`;
|
||||
|
||||
export const RadioGroup: FC<AxoBaseMenu.MenuRadioGroupProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.RadioGroup {...props} />;
|
||||
}
|
||||
@@ -154,7 +152,7 @@ export namespace AxoMenuBuilder {
|
||||
RadioGroup.displayName = `${Namespace}.RadioGroup`;
|
||||
|
||||
export const RadioItem: FC<AxoBaseMenu.MenuRadioItemProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.RadioItem {...props} />;
|
||||
}
|
||||
@@ -167,7 +165,7 @@ export namespace AxoMenuBuilder {
|
||||
RadioItem.displayName = `${Namespace}.RadioItem`;
|
||||
|
||||
export const Sub: FC<AxoBaseMenu.MenuSubProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.Sub {...props} />;
|
||||
}
|
||||
@@ -180,7 +178,7 @@ export namespace AxoMenuBuilder {
|
||||
Sub.displayName = `${Namespace}.Sub`;
|
||||
|
||||
export const SubTrigger: FC<AxoBaseMenu.MenuSubTriggerProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.SubTrigger {...props} />;
|
||||
}
|
||||
@@ -193,7 +191,7 @@ export namespace AxoMenuBuilder {
|
||||
SubTrigger.displayName = `${Namespace}.SubTrigger`;
|
||||
|
||||
export const SubContent: FC<AxoBaseMenu.MenuSubContentProps> = memo(props => {
|
||||
const renderer = useMenuBuilderContext();
|
||||
const renderer = useStrictContext(MenuBuilderContext);
|
||||
if (renderer === 'AxoDropdownMenu') {
|
||||
return <AxoDropdownMenu.SubContent {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import React, { createContext, memo, useContext, useMemo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import type { CSSProperties, FC, ReactNode } from 'react';
|
||||
import type { TailwindStyles } from './tw.dom.js';
|
||||
import { tw } from './tw.dom.js';
|
||||
import { assert } from './_internal/assert.dom.js';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from './_internal/StrictContext.dom.js';
|
||||
|
||||
const Namespace = 'AxoScrollArea';
|
||||
|
||||
@@ -15,13 +18,10 @@ const AXO_SCROLL_AREA_TIMELINE_HORIZONTAL =
|
||||
type AxoScrollAreaOrientation = 'vertical' | 'horizontal' | 'both';
|
||||
|
||||
const AxoScrollAreaOrientationContext =
|
||||
createContext<AxoScrollAreaOrientation | null>(null);
|
||||
createStrictContext<AxoScrollAreaOrientation>(`${Namespace}.Root`);
|
||||
|
||||
export function useAxoScrollAreaOrientation(): AxoScrollArea.Orientation {
|
||||
return assert(
|
||||
useContext(AxoScrollAreaOrientationContext),
|
||||
`Must be wrapped with <${Namespace}.Root>`
|
||||
);
|
||||
return useStrictContext(AxoScrollAreaOrientationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,15 +74,9 @@ export namespace AxoScrollArea {
|
||||
scrollBehavior: ScrollBehavior;
|
||||
}>;
|
||||
|
||||
const ScrollAreaConfigContext = createContext<ScrollAreaConfig | null>(null);
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function useAxoScrollAreaConfig(): ScrollAreaConfig {
|
||||
return assert(
|
||||
useContext(ScrollAreaConfigContext),
|
||||
`Must be wrapped with <${Namespace}.Root>`
|
||||
const ScrollAreaConfigContext = createStrictContext<ScrollAreaConfig>(
|
||||
`${Namespace}.Root`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: <AxoScrollArea.Root>
|
||||
@@ -221,7 +215,7 @@ export namespace AxoScrollArea {
|
||||
scrollbarGutter,
|
||||
scrollbarVisibility,
|
||||
scrollBehavior,
|
||||
} = useAxoScrollAreaConfig();
|
||||
} = useStrictContext(ScrollAreaConfigContext);
|
||||
|
||||
const style = useMemo((): CSSProperties => {
|
||||
const hasVerticalScrollbar = orientation !== 'horizontal';
|
||||
@@ -376,7 +370,7 @@ export namespace AxoScrollArea {
|
||||
export const Hint: FC<HintProps> = memo(props => {
|
||||
const { edge, animationStartOffset = 1, animationEndOffset = 20 } = props;
|
||||
const orientation = useAxoScrollAreaOrientation();
|
||||
const { scrollbarWidth } = useAxoScrollAreaConfig();
|
||||
const { scrollbarWidth } = useStrictContext(ScrollAreaConfigContext);
|
||||
|
||||
const style = useMemo((): CSSProperties => {
|
||||
const isVerticalEdge = edge === 'top' || edge === 'bottom';
|
||||
@@ -445,7 +439,7 @@ export namespace AxoScrollArea {
|
||||
} = props;
|
||||
|
||||
const orientation = useAxoScrollAreaOrientation();
|
||||
const { scrollbarWidth } = useAxoScrollAreaConfig();
|
||||
const { scrollbarWidth } = useStrictContext(ScrollAreaConfigContext);
|
||||
|
||||
const style = useMemo(() => {
|
||||
const hasVerticalScrollbar = orientation !== 'horizontal';
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RefCallback } from 'react';
|
||||
import { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { assert } from './assert.dom.js';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { createStrictContext, useStrictContext } from './StrictContext.dom.js';
|
||||
|
||||
type AriaLabellingContextType = Readonly<{
|
||||
labelRef: RefCallback<HTMLElement>;
|
||||
descriptionRef: RefCallback<HTMLElement>;
|
||||
}>;
|
||||
|
||||
const AriaLabellingContext = createContext<AriaLabellingContextType | null>(
|
||||
null
|
||||
const AriaLabellingContext = createStrictContext<AriaLabellingContextType>(
|
||||
'AriaLabellingContext.Provider'
|
||||
);
|
||||
|
||||
export type CreateAriaLabellingContextResult = Readonly<{
|
||||
@@ -42,11 +42,10 @@ export function useCreateAriaLabellingContext(): CreateAriaLabellingContextResul
|
||||
export const AriaLabellingProvider = AriaLabellingContext.Provider;
|
||||
|
||||
export function useAriaLabellingContext(
|
||||
componentName: string,
|
||||
providerName: string
|
||||
): AriaLabellingContextType {
|
||||
return assert(
|
||||
useContext(AriaLabellingContext),
|
||||
`${componentName} must be wrapped with a ${providerName}`
|
||||
return useStrictContext(
|
||||
AriaLabellingContext,
|
||||
`Must be wrapped with a <${providerName}>`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,19 +8,13 @@ import type {
|
||||
HTMLAttributes,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import React, {
|
||||
createContext,
|
||||
forwardRef,
|
||||
memo,
|
||||
useContext,
|
||||
useId,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import React, { forwardRef, memo, useId, useMemo } from 'react';
|
||||
import type { Transition } from 'framer-motion';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { TailwindStyles } from '../tw.dom.js';
|
||||
import { tw } from '../tw.dom.js';
|
||||
import { ExperimentalAxoBadge } from '../AxoBadge.dom.js';
|
||||
import { createStrictContext, useStrictContext } from './StrictContext.dom.js';
|
||||
|
||||
const Namespace = 'AxoBaseSegmentedControl';
|
||||
|
||||
@@ -54,18 +48,7 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
itemWidth: ItemWidth;
|
||||
}>;
|
||||
|
||||
const RootContext = createContext<RootContextType | null>(null);
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function useRootContext(componentName: string): RootContextType {
|
||||
const context = useContext(RootContext);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
`<${Namespace}.${componentName}> must be wrapped with <${Namespace}.Root>`
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
const RootContext = createStrictContext<RootContextType>(`${Namespace}.Root`);
|
||||
|
||||
type VariantConfig = {
|
||||
rootStyles: TailwindStyles;
|
||||
@@ -81,7 +64,7 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
),
|
||||
indicatorStyles: tw(
|
||||
'pointer-events-none absolute inset-0 z-10 rounded-full',
|
||||
'forced-colors:bg-[Highlight]'
|
||||
'forced-colors:bg-[SelectedItem]'
|
||||
),
|
||||
};
|
||||
|
||||
@@ -167,7 +150,7 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
forwardRef((props, ref: ForwardedRef<HTMLButtonElement>) => {
|
||||
const { value, ...rest } = props;
|
||||
|
||||
const context = useRootContext('Item');
|
||||
const context = useStrictContext(RootContext);
|
||||
const config = Variants[context.variant];
|
||||
const itemWidthStyles = ItemWidths[context.itemWidth];
|
||||
|
||||
@@ -189,12 +172,18 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
type="button"
|
||||
{...rest}
|
||||
className={tw(
|
||||
'group relative flex min-w-0 items-center justify-center px-3 py-[5px]',
|
||||
'relative flex min-w-0 items-center justify-center px-3 py-[5px]',
|
||||
'cursor-pointer rounded-full type-body-medium font-medium text-label-primary',
|
||||
'outline-border-focused not-forced-colors:outline-0 not-forced-colors:focused:outline-[2.5px]',
|
||||
'forced-colors:bg-[ButtonFace] forced-colors:text-[ButtonText]',
|
||||
'forced-colors:data-[axo-contextmenu-state=open]:text-[HighlightText]',
|
||||
itemWidthStyles,
|
||||
isSelected && tw('forced-colors:text-[HighlightText]')
|
||||
isSelected && tw('forced-colors:text-[SelectedItemText]'),
|
||||
!isSelected &&
|
||||
tw(
|
||||
'data-[axo-contextmenu-state=open]:bg-fill-secondary',
|
||||
'forced-colors:data-[axo-contextmenu-state=open]:bg-[Highlight]'
|
||||
)
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
29
ts/axo/_internal/StrictContext.dom.tsx
Normal file
29
ts/axo/_internal/StrictContext.dom.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Context } from 'react';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
const EMPTY: unique symbol = Symbol('STRICT_CONTEXT_EMPTY');
|
||||
const WRAPPER: unique symbol = Symbol('STRICT_CONTEXT_MESSAGE');
|
||||
|
||||
export type StrictContext<T> = Context<T | typeof EMPTY> & {
|
||||
[WRAPPER]: string;
|
||||
};
|
||||
|
||||
export function createStrictContext<T>(wrapper: string): StrictContext<T> {
|
||||
return Object.assign(createContext<T | typeof EMPTY>(EMPTY), {
|
||||
[WRAPPER]: wrapper,
|
||||
});
|
||||
}
|
||||
|
||||
export function useStrictContext<T>(
|
||||
context: StrictContext<T>,
|
||||
message?: string
|
||||
): T {
|
||||
const value = useContext(context);
|
||||
if (value === EMPTY) {
|
||||
throw new Error(message ?? `Must be wrapped with <${context[WRAPPER]}>`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
Reference in New Issue
Block a user