diff --git a/ts/axo/_storybook-helpers/Story.dom.tsx b/ts/axo/_storybook-helpers/Story.dom.tsx new file mode 100644 index 0000000000..396b54fef5 --- /dev/null +++ b/ts/axo/_storybook-helpers/Story.dom.tsx @@ -0,0 +1,77 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { ReactNode, FC } from 'react'; +import { memo } from 'react'; +import { tw } from '../tw.dom.tsx'; +import { AxoSymbol } from '../AxoSymbol.dom.tsx'; + +export namespace Story { + /** + * + * -------------------------------------------------------------------------- + */ + + export type HintProps = Readonly<{ + children?: ReactNode; + }>; + + export const Hint: FC = memo(props => { + return ( +

{props.children}

+ ); + }); + + Hint.displayName = 'Story.Hint'; + + /** + * + * -------------------------------------------------------------------------- + */ + + export type CalloutProps = Readonly<{ + children?: ReactNode; + }>; + + export const Callout: FC = memo(props => { + return ( +
+ +
{props.children}
+
+ ); + }); + + Callout.displayName = 'Story.Callout'; + + /** + * + * -------------------------------------------------------------------------- + */ + + export type LegendProps = Readonly<{ + label: string; + children?: ReactNode; + }>; + + export const Legend: FC = memo(props => { + return ( +
+ + {props.label} + + {props.children} +
+ ); + }); + + Legend.displayName = 'Story.Legend'; +} diff --git a/ts/axo/_tailwind.css b/ts/axo/_tailwind.css index 0c89839c1f..8398c3909e 100644 --- a/ts/axo/_tailwind.css +++ b/ts/axo/_tailwind.css @@ -25,3 +25,4 @@ @import './AxoDragRegions.css'; @import './AxoScrollArea.css'; @import './AxoSelect.css'; +@import './items/_AxoBaseItem.css'; diff --git a/ts/axo/items/AxoContactList.dom.stories.tsx b/ts/axo/items/AxoContactList.dom.stories.tsx index db67d16243..447abc8c4a 100644 --- a/ts/axo/items/AxoContactList.dom.stories.tsx +++ b/ts/axo/items/AxoContactList.dom.stories.tsx @@ -178,7 +178,6 @@ export function ItemActions(): ReactNode { } title="Adrian" - value="Admin" onClick={action('onClick')} accessory={ @@ -235,7 +234,6 @@ export function ItemIconActions(): ReactNode { } title="Adrian" - value="Admin" onClick={action('onClick')} accessory={ {props.title != null && ( - {props.title} + {props.title} )} @@ -58,11 +58,11 @@ export namespace AxoContactList { return ( - {props.avatar} + {props.avatar} - {props.title} + {props.title} {props.value != null && ( @@ -82,10 +82,10 @@ export namespace AxoContactList { onClick={props.onClick} /> )} + {props.accessory != null && ( + {props.accessory} + )} - {props.accessory != null && ( - {props.accessory} - )} @@ -173,11 +173,13 @@ export namespace AxoContactList { return ( - + + + - {props.title} + {props.title} {props.onClick != null && ( - + {props.title} - + diff --git a/ts/axo/items/AxoFieldList.dom.tsx b/ts/axo/items/AxoFieldList.dom.tsx index ae9b9253f8..b8b6948dca 100644 --- a/ts/axo/items/AxoFieldList.dom.tsx +++ b/ts/axo/items/AxoFieldList.dom.tsx @@ -27,7 +27,7 @@ export namespace AxoFieldList { {props.title != null && ( - {props.title} + {props.title} {/* We probably don't ever want to show a description without a title */} {props.description != null && ( {props.description} @@ -37,7 +37,7 @@ export namespace AxoFieldList { {props.children} {props.help != null && ( - {props.help} + {props.help} )} diff --git a/ts/axo/items/AxoItem.dom.stories.tsx b/ts/axo/items/AxoItem.dom.stories.tsx index 919a8a8c99..85520fce7c 100644 --- a/ts/axo/items/AxoItem.dom.stories.tsx +++ b/ts/axo/items/AxoItem.dom.stories.tsx @@ -9,19 +9,32 @@ import { tw } from '../tw.dom.tsx'; import { AxoSwitch } from '../AxoSwitch.dom.tsx'; import { AxoSelect } from '../AxoSelect.dom.tsx'; import { AxoDropdownMenu } from '../AxoDropdownMenu.dom.tsx'; +import { Story } from '../_storybook-helpers/Story.dom.tsx'; export default { title: 'Axo/Items/AxoItem', } satisfies Meta; -export function Title(): ReactNode { +const LONG_TEXT = ( + <> + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsa magni rerum + consequatur vero enim, laborum ullam voluptate expedita tempora amet natus + nisi praesentium alias earum accusantium ex doloribus et quidem. + +); + +const LONG_LABEL = <>Very long label: {LONG_TEXT}; +const LONG_VALUE = <>Very long value: {LONG_TEXT}; +const LONG_DESCRIPTION = <>Very long description: {LONG_TEXT}; + +export function Label(): ReactNode { return (
- Title of item + Label of item - Another title + Another label - Yet another title + {LONG_LABEL} - Title of item + Label of item Description of the item - Another title + Another label Description with more detail about the item @@ -93,10 +106,8 @@ export function Description(): ReactNode { - Yet another title - - Description that explains what this means - + Yet another label + {LONG_DESCRIPTION} - + + + - Title of item + Label of item - + + + - Another title - - Description with more detail about the item - + Another label + {LONG_DESCRIPTION} - Item without icon + Item without icon Notice it stays aligned with the other items @@ -161,6 +174,71 @@ export function Icon(): ReactNode { ); } +function IconAvatarTemplate(props: { size: AxoItem.IconAvatarSize }) { + return ( + + + + + + + + + Label of item + + + + + + + + + + + + Another label + {LONG_DESCRIPTION} + + + + + + + + + Item without icon avatar + + Notice it stays aligned with the other items + + + + + + + + ); +} + +export function IconAvatar(): ReactNode { + return ( +
+ + + + +
+ ); +} + export function Value(): ReactNode { return (
@@ -168,7 +246,7 @@ export function Value(): ReactNode { - Title of item + Label of item +1 555 555-5555 - Another title + Another label Jamie-MacBook-Pro.local - - Description with more detail about the item - + {LONG_DESCRIPTION} - Yet another title + Yet another label System Language Description that explains what this means @@ -210,6 +286,11 @@ export function Value(): ReactNode { + + + Note: If you have a value, you cannot have any other + type of accessory. +
); } @@ -219,54 +300,60 @@ export function Arrow(): ReactNode {
- - Title of item + Label of item + + + - - - - - - - - Another title - Jamie-MacBook-Pro.local - - Description with more detail about the item - - - - - - Yet another title - System Language - - Description that explains what this means - + Another label + {LONG_DESCRIPTION} + Item value + + + + + + + + + + + + Item without arrow + {LONG_DESCRIPTION} - + + + Note: Arrows should only be used when the item is + clickable and needs an extra hint. + + + + Note: Arrows should not be used with any accessories + except for values. +
); } @@ -278,42 +365,56 @@ export function Action(): ReactNode { - Title of item + Label of item + + + Action + + - - Action - - Title of item + Label of item Description of the item + + + Action + + - - Action - - Another title - - Description with more detail about the item - + Another label + {LONG_DESCRIPTION} + + + Join + + - - - Join - -
+ + + Note: There should only be one text action at a time, + if you need more than one action you can use an icon action or menu. + + + + Note: Items with actions should not be clickable +
); } @@ -359,40 +460,36 @@ export function IconActions(): ReactNode { - Title of item + Label of item - + - + - Another title - - Description with more detail about the item - + Another label + {LONG_DESCRIPTION} - + - + - Yet another title - - Description that explains what this means - + Yet another label + {LONG_DESCRIPTION} - + - + @@ -425,55 +522,53 @@ function Select() { ); } -export function Accessories(): ReactNode { +export function OtherAccessories(): ReactNode { return (
- Title of item + Label of item - + - + - Another title + Another label + + - - Title of item + Label of item Description of the item - + - + - Another title - - Description with more detail about the item - + Another label + {LONG_DESCRIPTION} + + - @@ -481,243 +576,117 @@ export function Accessories(): ReactNode { ); } -function Header(props: { children: ReactNode }): ReactNode { - return

{props.children}

; +function StressTest(props: { + label: ReactNode; + value?: ReactNode; + description?: ReactNode; +}): ReactNode { + return ( + + + + + + + + {props.label} + {props.value != null && ( + {props.value} + )} + {props.description != null && ( + {props.description} + )} + + + + + + + + + ); } export function StressTests(): ReactNode { return (
-
Kitchen Sink
- - - - - - Title - Value - Description - - - - - - - - - - - - - Title - Value - Description - - - - - - - - - - - - - - Title - Value - Description - - - - Action - - - - - - - - - Title - Value - Description - - - - Action - Action - - - - - - - - - Title - Value - Description - - - - - - - - - - - - - Title - Value - Description - - - -