mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-08-28 00:46:33 +01:00
Init AxoRadioGroupList
This commit is contained in:
@@ -1,25 +1,12 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useId,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { DropdownMenu } from 'radix-ui';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { computeAccessibleName } from 'dom-accessibility-api';
|
||||
import { AxoSymbol } from './AxoSymbol.dom.tsx';
|
||||
import { AxoBaseMenu } from './_internal/AxoBaseMenu.dom.tsx';
|
||||
import { tw } from './tw.dom.tsx';
|
||||
import {
|
||||
AriaLabellingProvider,
|
||||
useAriaLabellingContext,
|
||||
useCreateAriaLabellingContext,
|
||||
} from './_internal/AriaLabellingContext.dom.tsx';
|
||||
import { assert } from './_internal/assert.std.tsx';
|
||||
import {
|
||||
getElementAriaRole,
|
||||
@@ -32,6 +19,7 @@ import {
|
||||
import { isTestOrMockEnvironment } from '../environment.std.ts';
|
||||
import { AxoDragRegion } from './AxoDragRegion.dom.tsx';
|
||||
import { AxoTheme } from './AxoTheme.dom.tsx';
|
||||
import { AriaLabelled } from './aria/AriaLabelled.dom.tsx';
|
||||
|
||||
const { useDisableDragRegions } = AxoDragRegion;
|
||||
|
||||
@@ -218,28 +206,25 @@ export namespace AxoDropdownMenu {
|
||||
* Uses a portal to render the content part into the `body`.
|
||||
*/
|
||||
export const Content: FC<ContentProps> = memo(props => {
|
||||
const { context, labelId, descriptionId } = useCreateAriaLabellingContext();
|
||||
const { open } = useStrictContext(RootContext);
|
||||
return (
|
||||
<AriaLabellingProvider value={context}>
|
||||
<DropdownMenu.Portal>
|
||||
<AxoTheme.Inherit>
|
||||
<DropdownMenu.Portal>
|
||||
<AxoTheme.Inherit>
|
||||
<AriaLabelled.Root asChild>
|
||||
<DropdownMenu.Content
|
||||
sideOffset={4}
|
||||
align={props.align}
|
||||
side={props.side}
|
||||
collisionPadding={6}
|
||||
className={AxoBaseMenu.menuContentStyles}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descriptionId}
|
||||
onCloseAutoFocus={props.onCloseAutoFocus}
|
||||
inert={!open}
|
||||
>
|
||||
{props.children}
|
||||
</DropdownMenu.Content>
|
||||
</AxoTheme.Inherit>
|
||||
</DropdownMenu.Portal>
|
||||
</AriaLabellingProvider>
|
||||
</AriaLabelled.Root>
|
||||
</AxoTheme.Inherit>
|
||||
</DropdownMenu.Portal>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -396,30 +381,19 @@ export namespace AxoDropdownMenu {
|
||||
* for the containing menu.
|
||||
*/
|
||||
export const Header: FC<HeaderProps> = memo(props => {
|
||||
const labelId = useId();
|
||||
const descriptionId = useId();
|
||||
|
||||
const { labelRef, descriptionRef } = useAriaLabellingContext(
|
||||
'AxoDropdownMenu.Content/SubContent'
|
||||
);
|
||||
|
||||
return (
|
||||
<span aria-hidden="true" className={AxoBaseMenu.menuHeaderStyles}>
|
||||
<span
|
||||
ref={labelRef}
|
||||
id={labelId}
|
||||
className={AxoBaseMenu.menuHeaderLabelStyles}
|
||||
>
|
||||
{props.label}
|
||||
</span>
|
||||
{props.description && (
|
||||
<span
|
||||
ref={descriptionRef}
|
||||
id={descriptionId}
|
||||
className={AxoBaseMenu.menuHeaderDescriptionStyles}
|
||||
>
|
||||
{props.description}
|
||||
<AriaLabelled.Label asChild>
|
||||
<span className={AxoBaseMenu.menuHeaderLabelStyles}>
|
||||
{props.label}
|
||||
</span>
|
||||
</AriaLabelled.Label>
|
||||
{props.description && (
|
||||
<AriaLabelled.Description asChild>
|
||||
<span className={AxoBaseMenu.menuHeaderDescriptionStyles}>
|
||||
{props.description}
|
||||
</span>
|
||||
</AriaLabelled.Description>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
@@ -643,20 +617,17 @@ export namespace AxoDropdownMenu {
|
||||
* inside {@link AxoDropdownMenu.Sub}.
|
||||
*/
|
||||
export const SubContent: FC<SubContentProps> = memo(props => {
|
||||
const { context, labelId, descriptionId } = useCreateAriaLabellingContext();
|
||||
return (
|
||||
<DropdownMenu.Portal>
|
||||
<AriaLabellingProvider value={context}>
|
||||
<AriaLabelled.Root asChild>
|
||||
<DropdownMenu.SubContent
|
||||
alignOffset={-6}
|
||||
collisionPadding={6}
|
||||
className={AxoBaseMenu.menuSubContentStyles}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descriptionId}
|
||||
>
|
||||
{props.children}
|
||||
</DropdownMenu.SubContent>
|
||||
</AriaLabellingProvider>
|
||||
</AriaLabelled.Root>
|
||||
</DropdownMenu.Portal>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RefCallback } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { createStrictContext, useStrictContext } from './StrictContext.dom.tsx';
|
||||
|
||||
/** @internal */
|
||||
type AriaLabellingContextType = Readonly<{
|
||||
labelRef: RefCallback<HTMLElement>;
|
||||
descriptionRef: RefCallback<HTMLElement>;
|
||||
}>;
|
||||
|
||||
/** @internal */
|
||||
const AriaLabellingContext = createStrictContext<AriaLabellingContextType>(
|
||||
'AriaLabellingContext.Provider'
|
||||
);
|
||||
|
||||
export type CreateAriaLabellingContextResult = Readonly<{
|
||||
/** The labelling context to pass to `AriaLabellingProvider`. */
|
||||
context: AriaLabellingContextType;
|
||||
/** The `id` of the label element, once mounted. `undefined` if no label has been registered. */
|
||||
labelId: string | undefined;
|
||||
/** The `id` of the description element, once mounted. `undefined` if none has been registered. */
|
||||
descriptionId: string | undefined;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Creates a labelling context that captures element IDs from nested label and
|
||||
* description refs. Use the returned `labelId`/`descriptionId` to wire up
|
||||
* `aria-labelledby`/`aria-describedby` on the containing widget.
|
||||
*/
|
||||
export function useCreateAriaLabellingContext(): CreateAriaLabellingContextResult {
|
||||
const [labelId, setLabelId] = useState<string | undefined>();
|
||||
const [descriptionId, setDescriptionId] = useState<string | undefined>();
|
||||
|
||||
const context = useMemo((): AriaLabellingContextType => {
|
||||
function labelRef(element: HTMLElement | null) {
|
||||
setLabelId(element?.id);
|
||||
}
|
||||
|
||||
function descriptionRef(element: HTMLElement | null) {
|
||||
setDescriptionId(element?.id);
|
||||
}
|
||||
|
||||
return { labelRef, descriptionRef };
|
||||
}, []);
|
||||
|
||||
return { context, labelId, descriptionId };
|
||||
}
|
||||
|
||||
/** Context provider. Pass the `context` returned by `useCreateAriaLabellingContext`. */
|
||||
export const AriaLabellingProvider = AriaLabellingContext.Provider;
|
||||
|
||||
/**
|
||||
* Reads the labelling context and returns `labelRef`/`descriptionRef` callbacks
|
||||
* for registering label and description elements. Must be called inside an
|
||||
* `AriaLabellingProvider`.
|
||||
*/
|
||||
export function useAriaLabellingContext(
|
||||
providerName: string
|
||||
): AriaLabellingContextType {
|
||||
return useStrictContext(
|
||||
AriaLabellingContext,
|
||||
`Must be wrapped with a <${providerName}>`
|
||||
);
|
||||
}
|
||||
@@ -8,12 +8,8 @@ import { tw } from '../tw.dom.tsx';
|
||||
import { AxoBadge } from '../AxoBadge.dom.tsx';
|
||||
import { createStrictContext, useStrictContext } from './StrictContext.dom.tsx';
|
||||
import { variants } from './variants.dom.tsx';
|
||||
import {
|
||||
AriaLabellingProvider,
|
||||
useAriaLabellingContext,
|
||||
useCreateAriaLabellingContext,
|
||||
} from './AriaLabellingContext.dom.tsx';
|
||||
import { forwardExtraPropsForRadix } from './props.dom.tsx';
|
||||
import { AriaLabelled } from '../aria/AriaLabelled.dom.tsx';
|
||||
|
||||
/**
|
||||
* Used to share styles/animations for SegmentedControls, Toolbar ToggleGroups,
|
||||
@@ -173,11 +169,6 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
export const Item: FC<ItemProps> = memo(props => {
|
||||
const { ref, value, children, ...rest } = props;
|
||||
const context = useStrictContext(RootContext);
|
||||
const {
|
||||
context: labellingContext,
|
||||
labelId,
|
||||
descriptionId,
|
||||
} = useCreateAriaLabellingContext();
|
||||
|
||||
const isSelected = useMemo(() => {
|
||||
if (context.value == null) {
|
||||
@@ -192,7 +183,7 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
}, [value, context.value]);
|
||||
|
||||
return (
|
||||
<AriaLabellingProvider value={labellingContext}>
|
||||
<AriaLabelled.Root asChild>
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
@@ -210,8 +201,6 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
'forced-colors:data-[axo-contextmenu-state=open]:bg-[Highlight]'
|
||||
)
|
||||
)}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descriptionId}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
{children}
|
||||
@@ -224,7 +213,7 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</AriaLabellingProvider>
|
||||
</AriaLabelled.Root>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -246,17 +235,17 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
|
||||
/** Truncated label text inside a segmented control item. */
|
||||
export const ItemText: FC<ItemTextProps> = memo(props => {
|
||||
const id = useId();
|
||||
const { labelRef } = useAriaLabellingContext('AxoBaseSegmentControl.Item');
|
||||
return (
|
||||
<span
|
||||
ref={labelRef}
|
||||
id={id}
|
||||
className={tw('relative z-20 block truncate forced-color-adjust-none')}
|
||||
style={{ maxWidth: props.maxWidth }}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
<AriaLabelled.Label asChild>
|
||||
<span
|
||||
className={tw(
|
||||
'relative z-20 block truncate forced-color-adjust-none'
|
||||
)}
|
||||
style={{ maxWidth: props.maxWidth }}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
</AriaLabelled.Label>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -271,24 +260,18 @@ export namespace ExperimentalAxoBaseSegmentedControl {
|
||||
|
||||
/** A badge rendered to the right of the item label. */
|
||||
export const ItemBadge: FC<ItemBadgeProps> = memo((props: ItemBadgeProps) => {
|
||||
const id = useId();
|
||||
const { descriptionRef } = useAriaLabellingContext(
|
||||
'AxoBaseSegmentControl.Item'
|
||||
);
|
||||
return (
|
||||
<span
|
||||
ref={descriptionRef}
|
||||
id={id}
|
||||
className={tw('relative z-20 ms-[5px]')}
|
||||
>
|
||||
<AxoBadge.Root
|
||||
variant={props.variant}
|
||||
size="md"
|
||||
value={props.value}
|
||||
max={props.max}
|
||||
label={props.label}
|
||||
/>
|
||||
</span>
|
||||
<AriaLabelled.Description asChild>
|
||||
<span className={tw('relative z-20 ms-[5px]')}>
|
||||
<AxoBadge.Root
|
||||
variant={props.variant}
|
||||
size="md"
|
||||
value={props.value}
|
||||
max={props.max}
|
||||
label={props.label}
|
||||
/>
|
||||
</span>
|
||||
</AriaLabelled.Description>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AriaLabelled } from './AriaLabelled.dom.tsx';
|
||||
import { tw } from '../tw.dom.tsx';
|
||||
|
||||
export default {
|
||||
title: 'Axo/Aria/AriaLabelled',
|
||||
} satisfies Meta;
|
||||
|
||||
export function Basic(): ReactNode {
|
||||
return (
|
||||
<AriaLabelled.Root asChild>
|
||||
<div role="figure">
|
||||
<AriaLabelled.Label>Label</AriaLabelled.Label>
|
||||
<AriaLabelled.Description>Description</AriaLabelled.Description>
|
||||
</div>
|
||||
</AriaLabelled.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export function AsChild(): ReactNode {
|
||||
return (
|
||||
<AriaLabelled.Root asChild>
|
||||
<div role="figure">
|
||||
<AriaLabelled.Label asChild>
|
||||
<div className={tw('type-body-medium font-semibold')}>Label</div>
|
||||
</AriaLabelled.Label>
|
||||
<AriaLabelled.Description asChild>
|
||||
<div className={tw('type-caption text-secondary')}>Description</div>
|
||||
</AriaLabelled.Description>
|
||||
</div>
|
||||
</AriaLabelled.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { memo, useCallback, useId, useMemo, useState } from 'react';
|
||||
import { Slot } from 'radix-ui';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from '../_internal/StrictContext.dom.tsx';
|
||||
import { assert } from '../_internal/assert.std.tsx';
|
||||
|
||||
export namespace AriaLabelled {
|
||||
type Ids = ReadonlySet<string>;
|
||||
|
||||
type Unregister = () => void;
|
||||
type Register = (element: Element | null) => Unregister;
|
||||
|
||||
function useIds(): [Ids, Register] {
|
||||
const [ids, setIds] = useState<Ids>(() => new Set());
|
||||
|
||||
const register: Register = useCallback(element => {
|
||||
assert(element != null, 'must be rendered in react 19 or higher');
|
||||
const id = element.id;
|
||||
assert(id !== '', 'element is missing id');
|
||||
setIds(prev => {
|
||||
const copy = new Set(prev);
|
||||
copy.add(id);
|
||||
return copy;
|
||||
});
|
||||
return () => {
|
||||
setIds(prev => {
|
||||
const copy = new Set(prev);
|
||||
copy.delete(id);
|
||||
return copy;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
return [ids, register];
|
||||
}
|
||||
|
||||
function concatIds(input: Ids): string | undefined {
|
||||
if (input.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Array.from(input).join(' ');
|
||||
}
|
||||
|
||||
type RootContextType = Readonly<{
|
||||
registerLabel: Register;
|
||||
registerDescription: Register;
|
||||
}>;
|
||||
|
||||
const RootContext = createStrictContext<RootContextType>('AxoLabelled.Root');
|
||||
|
||||
/**
|
||||
* <AriaLabelled.Root>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type RootProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
const { label, description } = props;
|
||||
const Comp = props.asChild ? Slot.Root : 'div';
|
||||
const fallbackId = useId();
|
||||
const id = props.id ?? fallbackId;
|
||||
|
||||
const [labels, registerLabel] = useIds();
|
||||
const [descriptions, registerDescription] = useIds();
|
||||
|
||||
const context = useMemo((): RootContextType => {
|
||||
return { registerLabel, registerDescription };
|
||||
}, [registerLabel, registerDescription]);
|
||||
|
||||
const labelProps = useMemo(() => {
|
||||
let labelledBy = concatIds(labels);
|
||||
let describbedBy = concatIds(descriptions);
|
||||
|
||||
if (label != null && labelledBy != null) {
|
||||
labelledBy = `${id} ${labelledBy}`;
|
||||
}
|
||||
|
||||
if (description != null && describbedBy != null) {
|
||||
describbedBy = `${id} ${describbedBy}`;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
'aria-label': label,
|
||||
'aria-description': description,
|
||||
'aria-labelledby': labelledBy,
|
||||
'aria-describedby': describbedBy,
|
||||
};
|
||||
}, [id, label, description, labels, descriptions]);
|
||||
|
||||
return (
|
||||
<RootContext value={context}>
|
||||
<Comp {...labelProps}>{props.children}</Comp>
|
||||
</RootContext>
|
||||
);
|
||||
});
|
||||
|
||||
Root.displayName = 'AriaLabelled.Root';
|
||||
|
||||
/**
|
||||
* <AriaLabelled.Label>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type LabelProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Label: FC<LabelProps> = memo(props => {
|
||||
const Comp = props.asChild ? Slot.Root : 'div';
|
||||
const fallbackId = useId();
|
||||
const { registerLabel } = useStrictContext(RootContext);
|
||||
return (
|
||||
<Comp ref={registerLabel} id={props.id ?? fallbackId}>
|
||||
{props.children}
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
|
||||
Label.displayName = 'AriaLabelled.Label';
|
||||
|
||||
/**
|
||||
* <AriaLabelled.Description>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type DescriptionProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
const Comp = props.asChild ? Slot.Root : 'div';
|
||||
const fallbackId = useId();
|
||||
const { registerDescription } = useStrictContext(RootContext);
|
||||
return (
|
||||
<Comp ref={registerDescription} id={props.id ?? fallbackId}>
|
||||
{props.children}
|
||||
</Comp>
|
||||
);
|
||||
});
|
||||
|
||||
Description.displayName = 'AriaLabelled.Description';
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { Slot } from 'radix-ui';
|
||||
import { AriaLabelled } from './AriaLabelled.dom.tsx';
|
||||
|
||||
export namespace AriaList {
|
||||
/**
|
||||
* <AriaList.Root>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type RootProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
const Comp = props.asChild ? Slot.Root : 'ul';
|
||||
return <Comp role="list">{props.children}</Comp>;
|
||||
});
|
||||
|
||||
Root.displayName = 'AriaList.Root';
|
||||
|
||||
/**
|
||||
* <AriaList.Item>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type ItemProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Item: FC<ItemProps> = memo(props => {
|
||||
const Comp = props.asChild ? Slot.Root : 'li';
|
||||
return (
|
||||
<AriaLabelled.Root asChild id={props.id}>
|
||||
<Comp role="listitem">{props.children}</Comp>
|
||||
</AriaLabelled.Root>
|
||||
);
|
||||
});
|
||||
|
||||
Item.displayName = 'AriaList.Item';
|
||||
|
||||
/**
|
||||
* <AriaList.Label>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type LabelProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Label: FC<LabelProps> = memo(props => {
|
||||
return <AriaLabelled.Label {...props} />;
|
||||
});
|
||||
|
||||
Label.displayName = 'AriaList.Label';
|
||||
|
||||
/**
|
||||
* <AriaList.Description>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type DescriptionProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
return <AriaLabelled.Description {...props} />;
|
||||
});
|
||||
|
||||
Description.displayName = 'AriaList.Description';
|
||||
}
|
||||
+1
-4
@@ -5,7 +5,7 @@ import type { Meta } from '@storybook/react';
|
||||
import { AxoRadioGroup } from './AxoRadioGroup.dom.tsx';
|
||||
|
||||
export default {
|
||||
title: 'Axo/AxoRadioGroup',
|
||||
title: 'Axo/Controls/AxoRadioGroup',
|
||||
} satisfies Meta;
|
||||
|
||||
export function Default(): JSX.Element {
|
||||
@@ -13,15 +13,12 @@ export function Default(): JSX.Element {
|
||||
return (
|
||||
<AxoRadioGroup.Root value={value} onValueChange={setValue}>
|
||||
<AxoRadioGroup.Item value="foo">
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>Foo</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
<AxoRadioGroup.Item value="bar">
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>Bar</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
<AxoRadioGroup.Item value="baz">
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Veniam
|
||||
accusantium a aperiam quas perferendis error velit ipsam animi natus
|
||||
@@ -3,12 +3,9 @@
|
||||
|
||||
import { RadioGroup } from 'radix-ui';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { memo, useId, useMemo } from 'react';
|
||||
import { tw } from './tw.dom.tsx';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from './_internal/StrictContext.dom.tsx';
|
||||
import { memo } from 'react';
|
||||
import { tw } from '../tw.dom.tsx';
|
||||
import { AxoBaseRadioGroup } from './_AxoBaseRadioGroup.dom.tsx';
|
||||
|
||||
/**
|
||||
* A set of checkable buttons—known as radio buttons—where no more than one of
|
||||
@@ -18,7 +15,6 @@ import {
|
||||
* ```tsx
|
||||
* <AxoRadioGroup.Root value={value} onValueChange={setValue}>
|
||||
* <AxoRadioGroup.Item value="option-a">
|
||||
* <AxoRadioGroup.Indicator />
|
||||
* <AxoRadioGroup.Label>Option A</AxoRadioGroup.Label>
|
||||
* </AxoRadioGroup.Item>
|
||||
* </AxoRadioGroup.Root>
|
||||
@@ -62,15 +58,12 @@ export namespace AxoRadioGroup {
|
||||
* ```tsx
|
||||
* <AxoRadioGroup.Root value={notify} onValueChange={setNotify}>
|
||||
* <AxoRadioGroup.Item value="all">
|
||||
* <AxoRadioGroup.Indicator />
|
||||
* <AxoRadioGroup.Label>All messages</AxoRadioGroup.Label>
|
||||
* </AxoRadioGroup.Item>
|
||||
* <AxoRadioGroup.Item value="mentions">
|
||||
* <AxoRadioGroup.Indicator />
|
||||
* <AxoRadioGroup.Label>Mentions only</AxoRadioGroup.Label>
|
||||
* </AxoRadioGroup.Item>
|
||||
* <AxoRadioGroup.Item value="off">
|
||||
* <AxoRadioGroup.Indicator />
|
||||
* <AxoRadioGroup.Label>Off</AxoRadioGroup.Label>
|
||||
* </AxoRadioGroup.Item>
|
||||
* </AxoRadioGroup.Root>
|
||||
@@ -96,17 +89,6 @@ export namespace AxoRadioGroup {
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** @internal */
|
||||
type ItemContextType = Readonly<{
|
||||
id: string;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
}>;
|
||||
|
||||
/** @internal */
|
||||
const ItemContext =
|
||||
createStrictContext<ItemContextType>('AxoRadioGroup.Item');
|
||||
|
||||
export type ItemProps = Readonly<{
|
||||
/**
|
||||
* The value given as data when submitted with a name.
|
||||
@@ -117,7 +99,7 @@ export namespace AxoRadioGroup {
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Should be an `Indicator` and a `Label`.
|
||||
* Should contain an accessible label
|
||||
*/
|
||||
children: ReactNode;
|
||||
}>;
|
||||
@@ -126,68 +108,22 @@ export namespace AxoRadioGroup {
|
||||
* A single radio option.
|
||||
*/
|
||||
export const Item: FC<ItemProps> = memo(props => {
|
||||
const { value, disabled = false } = props;
|
||||
const id = useId();
|
||||
|
||||
const context = useMemo((): ItemContextType => {
|
||||
return { id, value, disabled };
|
||||
}, [id, value, disabled]);
|
||||
|
||||
return (
|
||||
<ItemContext.Provider value={context}>
|
||||
<label htmlFor={id} className={tw('flex gap-3 py-2.5')}>
|
||||
<AxoBaseRadioGroup.Item
|
||||
asChild
|
||||
value={props.value}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<div className={tw('flex gap-3 py-2.5')}>
|
||||
<AxoBaseRadioGroup.Indicator />
|
||||
{props.children}
|
||||
</label>
|
||||
</ItemContext.Provider>
|
||||
</div>
|
||||
</AxoBaseRadioGroup.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Item.displayName = 'AxoRadioGroup.Item';
|
||||
|
||||
/**
|
||||
* <AxoRadioGroup.Indicator>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Renders when the radio item is in a checked state.
|
||||
*/
|
||||
export const Indicator: FC = memo(() => {
|
||||
const context = useStrictContext(ItemContext);
|
||||
return (
|
||||
<RadioGroup.Item
|
||||
id={context.id}
|
||||
value={context.value}
|
||||
disabled={context.disabled}
|
||||
className={tw(
|
||||
'flex size-5 shrink-0 items-center justify-center rounded-full',
|
||||
'border border-primary inset-shadow-on-color',
|
||||
'data-[state=unchecked]:bg-control',
|
||||
'data-[state=unchecked]:enabled:active:bg-control-pressed',
|
||||
'data-[state=checked]:bg-accent',
|
||||
'data-[state=checked]:active:bg-accent-pressed',
|
||||
'data-disabled:border-secondary',
|
||||
'outline-none keyboard-mode:focus:axo-focus-ring',
|
||||
'overflow-hidden',
|
||||
'forced-colors:data-[state=checked]:bg-[SelectedItem]'
|
||||
)}
|
||||
>
|
||||
<RadioGroup.Indicator asChild>
|
||||
<span
|
||||
className={tw(
|
||||
'size-2.25 rounded-full',
|
||||
'data-[state=checked]:bg-(--axo-color-label-primary-oncolor)',
|
||||
'data-[state=checked]:data-disabled:bg-(--axo-color-label-disabled-oncolor)',
|
||||
'forced-colors:data-[state=checked]:bg-[SelectedItemText]'
|
||||
)}
|
||||
/>
|
||||
</RadioGroup.Indicator>
|
||||
</RadioGroup.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Indicator.displayName = 'AxoRadioGroup.Indicator';
|
||||
|
||||
/**
|
||||
* <AxoRadioGroup.Label>
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -205,9 +141,11 @@ export namespace AxoRadioGroup {
|
||||
*/
|
||||
export const Label: FC<LabelProps> = memo(props => {
|
||||
return (
|
||||
<span className={tw('truncate type-body-large text-primary')}>
|
||||
{props.children}
|
||||
</span>
|
||||
<AxoBaseRadioGroup.Label asChild>
|
||||
<span className={tw('truncate type-body-large text-primary')}>
|
||||
{props.children}
|
||||
</span>
|
||||
</AxoBaseRadioGroup.Label>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { RadioGroup } from 'radix-ui';
|
||||
import { memo } from 'react';
|
||||
import { tw } from '../tw.dom.tsx';
|
||||
import { AriaLabelled } from '../aria/AriaLabelled.dom.tsx';
|
||||
|
||||
export namespace AxoBaseRadioGroup {
|
||||
/**
|
||||
* <AxoBaseRadioGroup.Item>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type ItemProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
/**
|
||||
* The value given as data when submitted with a name.
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* When true, prevents the user from interacting with the radio item.
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Should be an `Indicator` and a `Label`.
|
||||
*/
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* A single radio option.
|
||||
*/
|
||||
export const Item: FC<ItemProps> = memo(props => {
|
||||
return (
|
||||
<AriaLabelled.Root asChild>
|
||||
<RadioGroup.Item {...props} />
|
||||
</AriaLabelled.Root>
|
||||
);
|
||||
});
|
||||
|
||||
Item.displayName = 'AxoRadioGroup.Item';
|
||||
|
||||
/**
|
||||
* <AxoBaseRadioGroup.Indicator>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export const Indicator: FC = memo(() => {
|
||||
return (
|
||||
<RadioGroup.Indicator asChild forceMount>
|
||||
<span
|
||||
className={tw(
|
||||
'group',
|
||||
'flex size-5 shrink-0 items-center justify-center rounded-full',
|
||||
'border border-primary inset-shadow-on-color',
|
||||
'data-[state=unchecked]:bg-control',
|
||||
'data-[state=unchecked]:enabled:active:bg-control-pressed',
|
||||
'data-[state=checked]:bg-accent',
|
||||
'data-[state=checked]:active:bg-accent-pressed',
|
||||
'data-disabled:border-secondary',
|
||||
'outline-none keyboard-mode:focus:axo-focus-ring',
|
||||
'overflow-hidden',
|
||||
'forced-colors:data-[state=checked]:bg-[SelectedItem]'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={tw(
|
||||
'size-2.25 rounded-full',
|
||||
'group-data-[state=checked]:bg-(--axo-color-label-primary-oncolor)',
|
||||
'group-data-[state=checked]:data-disabled:bg-(--axo-color-label-disabled-oncolor)',
|
||||
'forced-colors:group-data-[state=checked]:bg-[SelectedItemText]'
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
</RadioGroup.Indicator>
|
||||
);
|
||||
});
|
||||
|
||||
Indicator.displayName = 'AxoBaseRadioGroup.Indicator';
|
||||
|
||||
/**
|
||||
* <AxoBaseRadioGroup.Label>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type LabelProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Label: FC<LabelProps> = memo(props => {
|
||||
return <AriaLabelled.Label {...props} />;
|
||||
});
|
||||
|
||||
Label.displayName = 'AxoBaseRadioGroup.Label';
|
||||
|
||||
/**
|
||||
* <AxoBaseRadioGroup.Description>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type DescriptionProps = Readonly<{
|
||||
asChild?: boolean;
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
return <AriaLabelled.Description {...props} />;
|
||||
});
|
||||
|
||||
Description.displayName = 'AxoBaseRadioGroup.Description';
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { AxoList } from './AxoList.dom.tsx';
|
||||
import type { AxoIconButton } from '../AxoIconButton.dom.tsx';
|
||||
import { forwardExtraPropsForRadix } from '../_internal/props.dom.tsx';
|
||||
import { tw } from '../tw.dom.tsx';
|
||||
import { AriaList } from '../aria/AriaList.dom.tsx';
|
||||
|
||||
export namespace AxoContactList {
|
||||
/**
|
||||
@@ -23,11 +24,15 @@ export namespace AxoContactList {
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
return (
|
||||
<AxoList.Root>
|
||||
<AxoList.Header>
|
||||
<AxoList.Title>{props.title}</AxoList.Title>
|
||||
</AxoList.Header>
|
||||
{props.title != null && (
|
||||
<AxoList.Header>
|
||||
<AxoList.Title>{props.title}</AxoList.Title>
|
||||
</AxoList.Header>
|
||||
)}
|
||||
<AxoList.Body>
|
||||
<AxoBaseItem.Group spacing="sm">{props.children}</AxoBaseItem.Group>
|
||||
<AriaList.Root asChild>
|
||||
<AxoBaseItem.Group spacing="sm">{props.children}</AxoBaseItem.Group>
|
||||
</AriaList.Root>
|
||||
</AxoList.Body>
|
||||
</AxoList.Root>
|
||||
);
|
||||
@@ -51,33 +56,39 @@ export namespace AxoContactList {
|
||||
export const Item: FC<ItemProps> = memo(props => {
|
||||
const id = useId();
|
||||
return (
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.LegacyAvatarSlot>
|
||||
{props.avatar}
|
||||
</AxoBaseItem.LegacyAvatarSlot>
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>
|
||||
<AxoBaseItem.Title id={id}>{props.title}</AxoBaseItem.Title>
|
||||
{props.value != null && (
|
||||
<AxoBaseItem.Value>{props.value}</AxoBaseItem.Value>
|
||||
<AriaList.Item asChild>
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.LeadingSlot>{props.avatar}</AxoBaseItem.LeadingSlot>
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>
|
||||
<AriaList.Label asChild id={id}>
|
||||
<AxoBaseItem.Title>{props.title}</AxoBaseItem.Title>
|
||||
</AriaList.Label>
|
||||
{props.value != null && (
|
||||
<AriaList.Label asChild>
|
||||
<AxoBaseItem.Value>{props.value}</AxoBaseItem.Value>
|
||||
</AriaList.Label>
|
||||
)}
|
||||
{props.description != null && (
|
||||
<AriaList.Description asChild>
|
||||
<AxoBaseItem.Description truncate>
|
||||
{props.description}
|
||||
</AxoBaseItem.Description>
|
||||
</AriaList.Description>
|
||||
)}
|
||||
{props.onClick != null && (
|
||||
<AxoBaseItem.HiddenTrigger
|
||||
labelledby={id}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</AxoBaseItem.Body>
|
||||
{props.accessory != null && (
|
||||
<AxoBaseItem.Accessory>{props.accessory}</AxoBaseItem.Accessory>
|
||||
)}
|
||||
{props.description != null && (
|
||||
<AxoBaseItem.Description truncate>
|
||||
{props.description}
|
||||
</AxoBaseItem.Description>
|
||||
)}
|
||||
{props.onClick != null && (
|
||||
<AxoBaseItem.HiddenTrigger
|
||||
labelledby={id}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</AxoBaseItem.Body>
|
||||
{props.accessory != null && (
|
||||
<AxoBaseItem.Accessory>{props.accessory}</AxoBaseItem.Accessory>
|
||||
)}
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
</AriaList.Item>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -91,7 +102,7 @@ export namespace AxoContactList {
|
||||
export type ItemActionVariant = 'subtle-secondary';
|
||||
|
||||
export type ItemActionProps = Readonly<{
|
||||
ref?: Ref<HTMLButtonElement | null>;
|
||||
ref?: Ref<HTMLButtonElement>;
|
||||
variant: ItemActionVariant;
|
||||
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||
children: ReactNode;
|
||||
@@ -121,7 +132,7 @@ export namespace AxoContactList {
|
||||
export type ItemIconActionVariant = 'implied-secondary';
|
||||
|
||||
export type ItemIconActionProps = Readonly<{
|
||||
ref?: Ref<HTMLButtonElement | null>;
|
||||
ref?: Ref<HTMLButtonElement>;
|
||||
variant: ItemIconActionVariant;
|
||||
label: string;
|
||||
symbol: AxoSymbol.Name;
|
||||
@@ -160,20 +171,24 @@ export namespace AxoContactList {
|
||||
export const ActionItem: FC<ActionItemProps> = memo(props => {
|
||||
const id = useId();
|
||||
return (
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.IconAvatar size={32} symbol={props.symbol} />
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>
|
||||
<AxoBaseItem.Title id={id}>{props.title}</AxoBaseItem.Title>
|
||||
{props.onClick != null && (
|
||||
<AxoBaseItem.HiddenTrigger
|
||||
labelledby={id}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</AxoBaseItem.Body>
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
<AriaList.Item asChild>
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.IconAvatar size={32} symbol={props.symbol} />
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>
|
||||
<AriaList.Label asChild id={id}>
|
||||
<AxoBaseItem.Title>{props.title}</AxoBaseItem.Title>
|
||||
</AriaList.Label>
|
||||
{props.onClick != null && (
|
||||
<AxoBaseItem.HiddenTrigger
|
||||
labelledby={id}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</AxoBaseItem.Body>
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
</AriaList.Item>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -190,15 +205,19 @@ export namespace AxoContactList {
|
||||
|
||||
export const EmptyStateItem: FC<EmptyStateItemProps> = memo(props => {
|
||||
return (
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>
|
||||
<AxoBaseItem.Title truncate>
|
||||
<span className={tw('text-secondary')}>{props.title}</span>
|
||||
</AxoBaseItem.Title>
|
||||
</AxoBaseItem.Body>
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
<AriaList.Item asChild>
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>
|
||||
<AriaList.Label asChild>
|
||||
<AxoBaseItem.Title truncate>
|
||||
<span className={tw('text-secondary')}>{props.title}</span>
|
||||
</AxoBaseItem.Title>
|
||||
</AriaList.Label>
|
||||
</AxoBaseItem.Body>
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
</AriaList.Item>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { AxoSymbol } from '../AxoSymbol.dom.tsx';
|
||||
import type { AxoIconButton } from '../AxoIconButton.dom.tsx';
|
||||
import { forwardExtraPropsForRadix } from '../_internal/props.dom.tsx';
|
||||
import { AxoBaseItem } from './_AxoBaseItem.dom.tsx';
|
||||
import { AriaList } from '../aria/AriaList.dom.tsx';
|
||||
|
||||
/**
|
||||
* @example Anatomy
|
||||
@@ -43,7 +44,11 @@ export namespace AxoItem {
|
||||
}>;
|
||||
|
||||
export const Group: FC<GroupProps> = memo(props => {
|
||||
return <AxoBaseItem.Group spacing="md">{props.children}</AxoBaseItem.Group>;
|
||||
return (
|
||||
<AriaList.Root asChild>
|
||||
<AxoBaseItem.Group spacing="md">{props.children}</AxoBaseItem.Group>
|
||||
</AriaList.Root>
|
||||
);
|
||||
});
|
||||
|
||||
Group.displayName = 'AxoItem.Group';
|
||||
@@ -54,11 +59,16 @@ export namespace AxoItem {
|
||||
*/
|
||||
|
||||
export type RootProps = Readonly<{
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
return <AxoBaseItem.Root>{props.children}</AxoBaseItem.Root>;
|
||||
return (
|
||||
<AriaList.Item asChild id={props.id}>
|
||||
<AxoBaseItem.Root>{props.children}</AxoBaseItem.Root>
|
||||
</AriaList.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Root.displayName = 'AxoItem.Root';
|
||||
@@ -120,7 +130,9 @@ export namespace AxoItem {
|
||||
|
||||
export const Title: FC<TitleProps> = memo(props => {
|
||||
return (
|
||||
<AxoBaseItem.Title id={props.id}>{props.children}</AxoBaseItem.Title>
|
||||
<AriaList.Label asChild id={props.id}>
|
||||
<AxoBaseItem.Title>{props.children}</AxoBaseItem.Title>
|
||||
</AriaList.Label>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -132,11 +144,16 @@ export namespace AxoItem {
|
||||
*/
|
||||
|
||||
export type ValueProps = Readonly<{
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Value: FC<ValueProps> = memo(props => {
|
||||
return <AxoBaseItem.Value>{props.children}</AxoBaseItem.Value>;
|
||||
return (
|
||||
<AriaList.Label asChild id={props.id}>
|
||||
<AxoBaseItem.Value>{props.children}</AxoBaseItem.Value>
|
||||
</AriaList.Label>
|
||||
);
|
||||
});
|
||||
|
||||
Value.displayName = 'AxoItem.Value';
|
||||
@@ -147,11 +164,16 @@ export namespace AxoItem {
|
||||
*/
|
||||
|
||||
export type DescriptionProps = Readonly<{
|
||||
id?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
return <AxoBaseItem.Description>{props.children}</AxoBaseItem.Description>;
|
||||
return (
|
||||
<AriaList.Description asChild id={props.id}>
|
||||
<AxoBaseItem.Description>{props.children}</AxoBaseItem.Description>
|
||||
</AriaList.Description>
|
||||
);
|
||||
});
|
||||
|
||||
Description.displayName = 'AxoItem.Description';
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { memo, useId } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { tw } from '../tw.dom.tsx';
|
||||
import {
|
||||
AriaLabellingProvider,
|
||||
useAriaLabellingContext,
|
||||
useCreateAriaLabellingContext,
|
||||
} from '../_internal/AriaLabellingContext.dom.tsx';
|
||||
import { AriaLabelled } from '../aria/AriaLabelled.dom.tsx';
|
||||
|
||||
export namespace AxoList {
|
||||
/**
|
||||
@@ -21,16 +17,10 @@ export namespace AxoList {
|
||||
}>;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
const { context, labelId } = useCreateAriaLabellingContext();
|
||||
return (
|
||||
<AriaLabellingProvider value={context}>
|
||||
<section
|
||||
aria-label={props.accessibilityLabel}
|
||||
aria-labelledby={labelId}
|
||||
>
|
||||
{props.children}
|
||||
</section>
|
||||
</AriaLabellingProvider>
|
||||
<AriaLabelled.Root asChild label={props.accessibilityLabel}>
|
||||
<section>{props.children}</section>
|
||||
</AriaLabelled.Root>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -61,16 +51,12 @@ export namespace AxoList {
|
||||
}>;
|
||||
|
||||
export const Title: FC<TitleProps> = memo(props => {
|
||||
const id = useId();
|
||||
const { labelRef } = useAriaLabellingContext('AxoList.Root');
|
||||
return (
|
||||
<h2
|
||||
ref={labelRef}
|
||||
id={id}
|
||||
className={tw('type-body-medium font-semibold')}
|
||||
>
|
||||
{props.children}
|
||||
</h2>
|
||||
<AriaLabelled.Label asChild>
|
||||
<h2 className={tw('type-body-medium font-semibold')}>
|
||||
{props.children}
|
||||
</h2>
|
||||
</AriaLabelled.Label>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -86,16 +72,10 @@ export namespace AxoList {
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
const id = useId();
|
||||
const { descriptionRef } = useAriaLabellingContext('AxoList.Root');
|
||||
return (
|
||||
<p
|
||||
ref={descriptionRef}
|
||||
id={id}
|
||||
className={tw('type-body-small text-secondary')}
|
||||
>
|
||||
{props.children}
|
||||
</p>
|
||||
<AriaLabelled.Description asChild>
|
||||
<p className={tw('type-body-small text-secondary')}>{props.children}</p>
|
||||
</AriaLabelled.Description>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -153,14 +133,16 @@ export namespace AxoList {
|
||||
|
||||
export const Help: FC<HelpProps> = memo(props => {
|
||||
return (
|
||||
<p
|
||||
className={tw(
|
||||
'type-body-small text-secondary',
|
||||
'forced-colors:text-[GrayText]'
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</p>
|
||||
<AriaLabelled.Description asChild>
|
||||
<p
|
||||
className={tw(
|
||||
'type-body-small text-secondary',
|
||||
'forced-colors:text-[GrayText]'
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</p>
|
||||
</AriaLabelled.Description>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { AxoRadioGroupList } from './AxoRadioGroupList.dom.tsx';
|
||||
|
||||
export default {
|
||||
title: 'Axo/Items/AxoRadioGroupList',
|
||||
} satisfies Meta;
|
||||
|
||||
export function Basic(): ReactNode {
|
||||
const [value, setValue] = useState('1');
|
||||
return (
|
||||
<AxoRadioGroupList.Root value={value} onValueChange={setValue}>
|
||||
<AxoRadioGroupList.Item value="1">
|
||||
<AxoRadioGroupList.Label>One</AxoRadioGroupList.Label>
|
||||
</AxoRadioGroupList.Item>
|
||||
<AxoRadioGroupList.Item value="2">
|
||||
<AxoRadioGroupList.Label>Two</AxoRadioGroupList.Label>
|
||||
</AxoRadioGroupList.Item>
|
||||
<AxoRadioGroupList.Item value="3">
|
||||
<AxoRadioGroupList.Label>Three</AxoRadioGroupList.Label>
|
||||
</AxoRadioGroupList.Item>
|
||||
</AxoRadioGroupList.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export function Descriptions(): ReactNode {
|
||||
const [value, setValue] = useState('1');
|
||||
return (
|
||||
<AxoRadioGroupList.Root value={value} onValueChange={setValue}>
|
||||
<AxoRadioGroupList.Item value="1">
|
||||
<AxoRadioGroupList.Label>One</AxoRadioGroupList.Label>
|
||||
<AxoRadioGroupList.Description>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</AxoRadioGroupList.Description>
|
||||
</AxoRadioGroupList.Item>
|
||||
<AxoRadioGroupList.Item value="2">
|
||||
<AxoRadioGroupList.Label>Two</AxoRadioGroupList.Label>
|
||||
<AxoRadioGroupList.Description>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</AxoRadioGroupList.Description>
|
||||
</AxoRadioGroupList.Item>
|
||||
<AxoRadioGroupList.Item value="3">
|
||||
<AxoRadioGroupList.Label>Three</AxoRadioGroupList.Label>
|
||||
<AxoRadioGroupList.Description>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</AxoRadioGroupList.Description>
|
||||
</AxoRadioGroupList.Item>
|
||||
</AxoRadioGroupList.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { RadioGroup } from 'radix-ui';
|
||||
import { AxoList } from './AxoList.dom.tsx';
|
||||
import { AxoBaseItem } from './_AxoBaseItem.dom.tsx';
|
||||
import { AxoBaseRadioGroup } from '../controls/_AxoBaseRadioGroup.dom.tsx';
|
||||
|
||||
export namespace AxoRadioGroupList {
|
||||
/**
|
||||
* <AxoRadioGroupList.Root>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type RootProps = Readonly<{
|
||||
title?: ReactNode;
|
||||
description?: ReactNode;
|
||||
help?: ReactNode;
|
||||
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
return (
|
||||
<AxoList.Root>
|
||||
{props.title != null && (
|
||||
<AxoList.Header>
|
||||
<AxoList.Title>{props.title}</AxoList.Title>
|
||||
{/* We probably don't ever want to show a description without a title */}
|
||||
{props.description != null && (
|
||||
<AxoList.Description>{props.description}</AxoList.Description>
|
||||
)}
|
||||
</AxoList.Header>
|
||||
)}
|
||||
<AxoList.Body>
|
||||
<RadioGroup.Root
|
||||
asChild
|
||||
value={props.value}
|
||||
onValueChange={props.onValueChange}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<AxoBaseItem.Group spacing="md">{props.children}</AxoBaseItem.Group>
|
||||
</RadioGroup.Root>
|
||||
</AxoList.Body>
|
||||
{props.help != null && (
|
||||
<AxoList.Footer>
|
||||
<AxoList.Help>{props.help}</AxoList.Help>
|
||||
</AxoList.Footer>
|
||||
)}
|
||||
</AxoList.Root>
|
||||
);
|
||||
});
|
||||
|
||||
Root.displayName = 'AxoRadioGroupList.Root';
|
||||
|
||||
/**
|
||||
* <AxoRadioGroupList.Item>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type ItemProps = Readonly<{
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Item: FC<ItemProps> = memo(props => {
|
||||
return (
|
||||
<AxoBaseRadioGroup.Item
|
||||
value={props.value}
|
||||
disabled={props.disabled}
|
||||
asChild
|
||||
>
|
||||
<AxoBaseItem.Root>
|
||||
<AxoBaseItem.LeadingSlot>
|
||||
<AxoBaseRadioGroup.Indicator />
|
||||
</AxoBaseItem.LeadingSlot>
|
||||
<AxoBaseItem.Content>
|
||||
<AxoBaseItem.Body>{props.children}</AxoBaseItem.Body>
|
||||
</AxoBaseItem.Content>
|
||||
</AxoBaseItem.Root>
|
||||
</AxoBaseRadioGroup.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Item.displayName = 'AxoRadioGroupList.Item';
|
||||
|
||||
/**
|
||||
* <AxoRadioGroupList.Label>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type LabelProps = Readonly<{
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Label: FC<LabelProps> = memo(props => {
|
||||
return (
|
||||
<AxoBaseRadioGroup.Label asChild>
|
||||
<AxoBaseItem.Title>{props.children}</AxoBaseItem.Title>
|
||||
</AxoBaseRadioGroup.Label>
|
||||
);
|
||||
});
|
||||
|
||||
Label.displayName = 'AxoRadioGroupList.Label';
|
||||
|
||||
/**
|
||||
* <AxoRadioGroupList.Description>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type DescriptionProps = Readonly<{
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
return (
|
||||
<AxoBaseRadioGroup.Description asChild>
|
||||
<AxoBaseItem.Description>{props.children}</AxoBaseItem.Description>
|
||||
</AxoBaseRadioGroup.Description>
|
||||
);
|
||||
});
|
||||
|
||||
Description.displayName = 'AxoRadioGroupList.Description';
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import type { FC, MouseEvent, ReactNode, Ref } from 'react';
|
||||
import { memo, useId, useMemo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import {
|
||||
createStrictContext,
|
||||
useStrictContext,
|
||||
} from '../_internal/StrictContext.dom.tsx';
|
||||
import { tw } from '../tw.dom.tsx';
|
||||
import {
|
||||
AriaLabellingProvider,
|
||||
useAriaLabellingContext,
|
||||
useCreateAriaLabellingContext,
|
||||
} from '../_internal/AriaLabellingContext.dom.tsx';
|
||||
import { AriaClickable } from '../AriaClickable.dom.tsx';
|
||||
import { forwardExtraPropsForRadix } from '../_internal/props.dom.tsx';
|
||||
import { AxoIconButton } from '../AxoIconButton.dom.tsx';
|
||||
import { AxoSymbol } from '../AxoSymbol.dom.tsx';
|
||||
import { AxoButton } from '../AxoButton.dom.tsx';
|
||||
import { AxoCheckbox } from '../AxoCheckbox.dom.tsx';
|
||||
import { AxoRadioGroup } from '../AxoRadioGroup.dom.tsx';
|
||||
import { AxoAvatar } from '../AxoAvatar.dom.tsx';
|
||||
import { variants } from '../_internal/variants.dom.tsx';
|
||||
|
||||
@@ -72,12 +66,13 @@ export namespace AxoBaseItem {
|
||||
createStrictContext<GroupContextType>('AxoBaseItem.Group');
|
||||
|
||||
export type GroupProps = Readonly<{
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
spacing: Spacing;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Group: FC<GroupProps> = memo(props => {
|
||||
const { spacing } = props;
|
||||
const { ref, spacing, children, ...rest } = props;
|
||||
|
||||
const context = useMemo((): GroupContextType => {
|
||||
return { spacing };
|
||||
@@ -86,13 +81,16 @@ export namespace AxoBaseItem {
|
||||
return (
|
||||
<GroupContext value={context}>
|
||||
<div
|
||||
role="list"
|
||||
ref={ref}
|
||||
className={tw('grid min-w-90')}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
// Style needs to come after forwarded props. Long-term we should
|
||||
// figure out how to automatically merge props like these
|
||||
style={{
|
||||
gridTemplateColumns: GRID_TEMPLATE_COLUMNS,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
</GroupContext>
|
||||
);
|
||||
@@ -115,42 +113,40 @@ export namespace AxoBaseItem {
|
||||
}>;
|
||||
|
||||
export const Root: FC<RootProps> = memo(props => {
|
||||
const { children, ...rest } = props;
|
||||
const groupContext = useStrictContext(GroupContext);
|
||||
const { context, labelId, descriptionId } = useCreateAriaLabellingContext();
|
||||
|
||||
return (
|
||||
<AriaLabellingProvider value={context}>
|
||||
<AriaClickable.Root asChild>
|
||||
<AriaClickable.Root asChild>
|
||||
<div
|
||||
className={tw(
|
||||
'group',
|
||||
// forward grid
|
||||
'col-span-full grid grid-cols-subgrid',
|
||||
'p-0.5',
|
||||
'outline-none'
|
||||
)}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
<div
|
||||
role="listitem"
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descriptionId}
|
||||
className={tw(
|
||||
'group',
|
||||
// forward grid
|
||||
'col-span-full grid grid-cols-subgrid',
|
||||
'p-0.5'
|
||||
'gap-x-3 px-3',
|
||||
RootSpacing.get(groupContext.spacing),
|
||||
'items-baseline',
|
||||
'text-primary',
|
||||
'curved-14',
|
||||
'group-data-hovered:bg-secondary',
|
||||
'group-data-pressed:bg-secondary-pressed',
|
||||
'outline-none keyboard-mode:group-data-focused:axo-focus-ring',
|
||||
'keyboard-mode:group-focus:axo-focus-ring'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={tw(
|
||||
// forward grid
|
||||
'col-span-full grid grid-cols-subgrid',
|
||||
'gap-x-3 px-3',
|
||||
RootSpacing.get(groupContext.spacing),
|
||||
'items-baseline',
|
||||
'text-primary',
|
||||
'curved-14',
|
||||
'group-data-hovered:bg-secondary',
|
||||
'group-data-pressed:bg-secondary-pressed',
|
||||
'outline-none keyboard-mode:group-data-focused:axo-focus-ring'
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</AriaClickable.Root>
|
||||
</AriaLabellingProvider>
|
||||
</div>
|
||||
</AriaClickable.Root>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -161,14 +157,12 @@ export namespace AxoBaseItem {
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** @internal */
|
||||
type LeadingSlotProps = Readonly<{
|
||||
export type LeadingSlotProps = Readonly<{
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
/** @internal */
|
||||
const LeadingSlot: FC<LeadingSlotProps> = memo(props => {
|
||||
export const LeadingSlot: FC<LeadingSlotProps> = memo(props => {
|
||||
return (
|
||||
<div style={{ gridColumn: LEADING_SLOT }} className={props.className}>
|
||||
{props.children}
|
||||
@@ -247,36 +241,6 @@ export namespace AxoBaseItem {
|
||||
|
||||
Checkbox.displayName = 'AxoBaseItem.Checkbox';
|
||||
|
||||
/**
|
||||
* <AxoBaseItem.RadioGroupIndicator>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export const RadioGroupIndicator: FC = memo(() => {
|
||||
return (
|
||||
<LeadingSlot>
|
||||
<AxoRadioGroup.Indicator />
|
||||
</LeadingSlot>
|
||||
);
|
||||
});
|
||||
|
||||
RadioGroupIndicator.displayName = 'AxoBaseItem.RadioGroupIndicator';
|
||||
|
||||
/**
|
||||
* <AxoBaseItem.LegacyAvatarSlot>
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type LegacyAvatarSlotProps = Readonly<{
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const LegacyAvatarSlot: FC<LegacyAvatarSlotProps> = memo(props => {
|
||||
return <LeadingSlot>{props.children}</LeadingSlot>;
|
||||
});
|
||||
|
||||
LegacyAvatarSlot.displayName = 'AxoBaseItem.LegacyAvatarSlot';
|
||||
|
||||
/**
|
||||
* <AxoBaseItem.IconAvatar>
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -362,27 +326,26 @@ export namespace AxoBaseItem {
|
||||
*/
|
||||
|
||||
export type TitleProps = Readonly<{
|
||||
id?: string;
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
truncate?: boolean;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Title: FC<TitleProps> = memo(props => {
|
||||
const fallbackId = useId();
|
||||
const { labelRef } = useAriaLabellingContext('AxoBaseItem.Root');
|
||||
const { ref, truncate, children, ...rest } = props;
|
||||
return (
|
||||
<div
|
||||
ref={labelRef}
|
||||
id={props.id ?? fallbackId}
|
||||
ref={ref}
|
||||
className={tw(
|
||||
'min-w-50', // force value to next line if there's not much space
|
||||
'grow-[calc(infinity)]',
|
||||
'type-body-medium text-primary',
|
||||
props.truncate ? 'truncate' : 'line-clamp-2',
|
||||
truncate ? 'truncate' : 'line-clamp-2',
|
||||
'-my-0.75 py-0.75' // extra space for focus rings
|
||||
)}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -395,13 +358,19 @@ export namespace AxoBaseItem {
|
||||
*/
|
||||
|
||||
export type ValueProps = Readonly<{
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Value: FC<ValueProps> = memo(props => {
|
||||
const { ref, children, ...rest } = props;
|
||||
return (
|
||||
<div className={tw('w-fit grow type-body-medium text-secondary')}>
|
||||
{props.children}
|
||||
<div
|
||||
ref={ref}
|
||||
className={tw('w-fit grow type-body-medium text-secondary')}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -414,25 +383,29 @@ export namespace AxoBaseItem {
|
||||
*/
|
||||
|
||||
export type DescriptionProps = Readonly<{
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
truncate?: boolean;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
export const Description: FC<DescriptionProps> = memo(props => {
|
||||
const { ref, truncate, children, ...rest } = props;
|
||||
return (
|
||||
<>
|
||||
{/* Force description to its own line */}
|
||||
<div className={tw('basis-full')} />
|
||||
<div
|
||||
ref={ref}
|
||||
className={tw(
|
||||
'min-w-0',
|
||||
'type-body-small text-secondary',
|
||||
'forced-colors:text-[GrayText]',
|
||||
props.truncate && 'truncate',
|
||||
truncate && 'truncate',
|
||||
'-my-0.5 py-0.5' // extra space for focus rings
|
||||
)}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
+1
-2
@@ -5,7 +5,7 @@ import { useCallback, useMemo, useState, type JSX } from 'react';
|
||||
import type { LocalizerType } from '../../../types/Util.std.ts';
|
||||
import { getMuteOptions } from '../../../util/getMuteOptions.std.ts';
|
||||
import { AxoDialog } from '../../../axo/AxoDialog.dom.tsx';
|
||||
import { AxoRadioGroup } from '../../../axo/AxoRadioGroup.dom.tsx';
|
||||
import { AxoRadioGroup } from '../../../axo/controls/AxoRadioGroup.dom.tsx';
|
||||
import { safeParseInteger } from '../../../util/numbers.std.ts';
|
||||
import { strictAssert } from '../../../util/assert.std.ts';
|
||||
|
||||
@@ -62,7 +62,6 @@ export function ConversationNotificationsModal({
|
||||
value={`${option.value}`}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>{option.name}</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { MouseEvent } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { AxoDialog } from '../../../axo/AxoDialog.dom.tsx';
|
||||
import type { LocalizerType } from '../../../types/I18N.std.ts';
|
||||
import { AxoRadioGroup } from '../../../axo/AxoRadioGroup.dom.tsx';
|
||||
import { AxoRadioGroup } from '../../../axo/controls/AxoRadioGroup.dom.tsx';
|
||||
import { strictAssert } from '../../../util/assert.std.ts';
|
||||
import { AxoAlertDialog } from '../../../axo/AxoAlertDialog.dom.tsx';
|
||||
import { isInternalFeaturesEnabled } from '../../../util/isInternalFeaturesEnabled.dom.ts';
|
||||
@@ -239,32 +239,27 @@ function PinMessageSelectDurationDialog(props: {
|
||||
onValueChange={handleDurationChange}
|
||||
>
|
||||
<AxoRadioGroup.Item value={DurationOption.TIME_24_HOURS}>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
{i18n('icu:PinMessageDialog__Option--TIME_24_HOURS')}
|
||||
</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
<AxoRadioGroup.Item value={DurationOption.TIME_7_DAYS}>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
{i18n('icu:PinMessageDialog__Option--TIME_7_DAYS')}
|
||||
</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
<AxoRadioGroup.Item value={DurationOption.TIME_30_DAYS}>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
{i18n('icu:PinMessageDialog__Option--TIME_30_DAYS')}
|
||||
</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
<AxoRadioGroup.Item value={DurationOption.FOREVER}>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
{i18n('icu:PinMessageDialog__Option--FOREVER')}
|
||||
</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
{isInternalFeaturesEnabled() && (
|
||||
<AxoRadioGroup.Item value={DurationOption.DEBUG_10_SECONDS}>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>10 seconds (Internal)</AxoRadioGroup.Label>
|
||||
</AxoRadioGroup.Item>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { tw } from '../../../axo/tw.dom.tsx';
|
||||
import { AxoSymbol } from '../../../axo/AxoSymbol.dom.tsx';
|
||||
import { AxoButton } from '../../../axo/AxoButton.dom.tsx';
|
||||
import { AxoDialog } from '../../../axo/AxoDialog.dom.tsx';
|
||||
import { AxoRadioGroup } from '../../../axo/AxoRadioGroup.dom.tsx';
|
||||
import { AxoRadioGroup } from '../../../axo/controls/AxoRadioGroup.dom.tsx';
|
||||
import { AxoTextField } from '../../../axo/fields/AxoTextField.dom.tsx';
|
||||
|
||||
import type { LocalizerType } from '../../../types/I18N.std.ts';
|
||||
@@ -327,7 +327,6 @@ function PhoneNumberDiscoverabilityDialog({
|
||||
}}
|
||||
>
|
||||
<AxoRadioGroup.Item value={PhoneNumberDiscoverability.Discoverable}>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
<div className={tw('type-body-medium')}>
|
||||
{i18n('icu:Preferences__pnp__discoverability__everyone')}
|
||||
@@ -337,7 +336,6 @@ function PhoneNumberDiscoverabilityDialog({
|
||||
<AxoRadioGroup.Item
|
||||
value={PhoneNumberDiscoverability.NotDiscoverable}
|
||||
>
|
||||
<AxoRadioGroup.Indicator />
|
||||
<AxoRadioGroup.Label>
|
||||
<div className={tw('type-body-medium')}>
|
||||
{i18n('icu:Preferences__pnp__discoverability__nobody')}
|
||||
|
||||
Reference in New Issue
Block a user