Add pin message item to message context menu

This commit is contained in:
Jamie
2025-11-17 14:21:59 -08:00
committed by GitHub
parent 024d467745
commit 6b16d75036
10 changed files with 467 additions and 54 deletions
+1 -1
View File
@@ -188,7 +188,7 @@ export namespace AxoDialog {
export const Close: FC<CloseProps> = memo(props => {
return (
<div className={tw('col-[close-slot] text-end')}>
<div className={tw('col-[close-slot] text-end leading-none')}>
<Dialog.Close asChild>
<AxoIconButton.Root
size="sm"
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import type { Meta } from '@storybook/react';
import { AxoRadioGroup } from './AxoRadioGroup.dom.js';
export default {
title: 'Axo/AxoRadioGroup',
} satisfies Meta;
export function Default(): JSX.Element {
const [value, setValue] = useState('foo');
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
deserunt iste voluptatem asperiores voluptates rem odio necessitatibus
delectus, optio officia?
</AxoRadioGroup.Label>
</AxoRadioGroup.Item>
</AxoRadioGroup.Root>
);
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { RadioGroup } from 'radix-ui';
import type { FC, ReactNode } from 'react';
import React, { memo, useId, useMemo } from 'react';
import { tw } from './tw.dom.js';
import {
createStrictContext,
useStrictContext,
} from './_internal/StrictContext.dom.js';
export const Namespace = 'AxoRadioGroup';
/**
* @example Anatomy
* ```tsx
* <AxoRadioGroup.Root>
* <AxoRadioGroup.Item>
* <AxoRadioGroup.Indicator/>
* <AxoRadioGroup.Label>...</AxoRadioGroup.Label>
* </AxoRadioGroup.Item>
* </AxoAlertDialog.Root>
* ```
*/
export namespace AxoRadioGroup {
/**
* Component: <AxoRadioGroup.Root>
* -------------------------------
*/
export type RootProps = Readonly<{
value: string | null;
onValueChange: (value: string) => void;
disabled?: boolean;
children: ReactNode;
}>;
export const Root: FC<RootProps> = memo(props => {
return (
<RadioGroup.Root
value={props.value}
onValueChange={props.onValueChange}
disabled={props.disabled}
className={tw('flex flex-col')}
>
{props.children}
</RadioGroup.Root>
);
});
Root.displayName = `${Namespace}.Root`;
/**
* Component: <AxoRadioGroup.Item>
* -------------------------------
*/
type ItemContextType = Readonly<{
id: string;
value: string;
disabled: boolean;
}>;
const ItemContext = createStrictContext<ItemContextType>(`${Namespace}.Item`);
export type ItemProps = Readonly<{
value: string;
disabled?: boolean;
children: ReactNode;
}>;
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')}>
{props.children}
</label>
</ItemContext.Provider>
);
});
Item.displayName = `${Namespace}.Item`;
/**
* Component: <AxoRadioGroup.Indicator>
* ------------------------------------
*/
export type IndicatorProps = Readonly<{
// ...
}>;
export const Indicator: FC<IndicatorProps> = 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-border-primary inset-shadow-on-color',
'data-[state=unchecked]:bg-fill-primary',
'data-[state=unchecked]:pressed:bg-fill-primary-pressed',
'data-[state=checked]:bg-color-fill-primary',
'data-[state=checked]:pressed:bg-color-fill-primary-pressed',
'data-[disabled]:border-border-secondary',
'outline-0 outline-border-focused focused:outline-[2.5px]',
'overflow-hidden',
'forced-colors:data-[state=checked]:bg-[SelectedItem]'
)}
>
<RadioGroup.Indicator asChild>
<span
className={tw(
'size-[9px] rounded-full',
'data-[state=checked]:bg-label-primary-on-color',
'data-[state=checked]:data-[disabled]:bg-label-disabled-on-color',
'forced-colors:data-[state=checked]:bg-[SelectedItemText]'
)}
/>
</RadioGroup.Indicator>
</RadioGroup.Item>
);
});
Indicator.displayName = `${Namespace}.Indicator`;
/**
* Component: <AxoRadioGroup.Indicator>
* ------------------------------------
*/
export type LabelProps = Readonly<{
children: ReactNode;
}>;
export const Label: FC<LabelProps> = memo(props => {
return (
<span className={tw('truncate type-body-large text-label-primary')}>
{props.children}
</span>
);
});
Label.displayName = `${Namespace}.Label`;
}