Init AxoIconButton

This commit is contained in:
Jamie
2025-11-11 08:56:03 -08:00
committed by GitHub
parent 5260600efa
commit 44d412bc06
8 changed files with 531 additions and 68 deletions

View File

@@ -11,6 +11,8 @@ import { SpinnerV2 } from '../components/SpinnerV2.dom.js';
const Namespace = 'AxoButton'; const Namespace = 'AxoButton';
type GenericButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
const baseAxoButtonStyles = tw( const baseAxoButtonStyles = tw(
'relative inline-flex max-w-full items-center-safe justify-center-safe rounded-full select-none', 'relative inline-flex max-w-full items-center-safe justify-center-safe rounded-full select-none',
'outline-0 outline-border-focused focused:outline-[2.5px]', 'outline-0 outline-border-focused focused:outline-[2.5px]',
@@ -133,11 +135,6 @@ const AxoButtonSizes = {
sm: tw('min-w-12 px-2 py-1 type-body-small font-medium'), sm: tw('min-w-12 px-2 py-1 type-body-small font-medium'),
} as const satisfies Record<string, TailwindStyles>; } as const satisfies Record<string, TailwindStyles>;
type BaseButtonAttrs = Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
'className' | 'style' | 'children'
>;
type AxoButtonVariant = keyof typeof AxoButtonVariants; type AxoButtonVariant = keyof typeof AxoButtonVariants;
type AxoButtonSize = keyof typeof AxoButtonSizes; type AxoButtonSize = keyof typeof AxoButtonSizes;
@@ -215,16 +212,19 @@ export namespace AxoButton {
full: tw('w-full'), full: tw('w-full'),
}; };
export type RootProps = BaseButtonAttrs & export type RootProps = Readonly<{
Readonly<{ variant: AxoButtonVariant;
variant: AxoButtonVariant; size: AxoButtonSize;
size: AxoButtonSize; width?: Width;
width?: Width; symbol?: AxoSymbol.InlineGlyphName;
symbol?: AxoSymbol.InlineGlyphName; arrow?: boolean;
arrow?: boolean; experimentalSpinner?: { 'aria-label': string } | null;
experimentalSpinner?: { 'aria-label': string } | null; disabled?: GenericButtonProps['disabled'];
children: ReactNode; onClick?: GenericButtonProps['onClick'];
}>; children: ReactNode;
// Note: Technically we forward all props for Radix, but we restrict the
// props that the type accepts
}>;
export const Root: FC<RootProps> = memo( export const Root: FC<RootProps> = memo(
forwardRef((props, ref: ForwardedRef<HTMLButtonElement>) => { forwardRef((props, ref: ForwardedRef<HTMLButtonElement>) => {
@@ -251,9 +251,9 @@ export namespace AxoButton {
return ( return (
<button <button
ref={ref} ref={ref}
{...rest}
type="button" type="button"
className={tw(variantStyles, sizeStyles, widthStyles)} className={tw(variantStyles, sizeStyles, widthStyles)}
{...rest}
> >
<span <span
className={tw( className={tw(

View File

@@ -41,6 +41,7 @@ function Template(props: {
back?: boolean; back?: boolean;
contentSize: AxoDialog.ContentSize; contentSize: AxoDialog.ContentSize;
bodyPadding?: AxoDialog.BodyPadding; bodyPadding?: AxoDialog.BodyPadding;
iconAction?: boolean;
footerContent?: ReactNode; footerContent?: ReactNode;
children: ReactNode; children: ReactNode;
}): JSX.Element { }): JSX.Element {
@@ -68,12 +69,26 @@ function Template(props: {
</AxoDialog.FooterContent> </AxoDialog.FooterContent>
)} )}
<AxoDialog.Actions> <AxoDialog.Actions>
<AxoDialog.Action variant="secondary" onClick={action('onCancel')}> {props.iconAction ? (
Cancel <AxoDialog.IconAction
</AxoDialog.Action> aria-label="Send"
<AxoDialog.Action variant="primary" onClick={action('onSave')}> variant="primary"
Save symbol="send-fill"
</AxoDialog.Action> onClick={action('onSend')}
/>
) : (
<>
<AxoDialog.Action
variant="secondary"
onClick={action('onCancel')}
>
Cancel
</AxoDialog.Action>
<AxoDialog.Action variant="primary" onClick={action('onSave')}>
Save
</AxoDialog.Action>
</>
)}
</AxoDialog.Actions> </AxoDialog.Actions>
</AxoDialog.Footer> </AxoDialog.Footer>
</AxoDialog.Content> </AxoDialog.Content>
@@ -102,6 +117,14 @@ export function Large(): JSX.Element {
return <Template contentSize="lg">{TEXT_LONG}</Template>; return <Template contentSize="lg">{TEXT_LONG}</Template>;
} }
export function IconAction(): JSX.Element {
return (
<Template contentSize="sm" iconAction>
{TEXT_SHORT}
</Template>
);
}
export function LongContent(): JSX.Element { export function LongContent(): JSX.Element {
return ( return (
<Template contentSize="md"> <Template contentSize="md">
@@ -166,7 +189,8 @@ function TextInputField(props: { placeholder: string }) {
'w-full px-3 py-1.5', 'w-full px-3 py-1.5',
'border-[0.5px] border-border-primary shadow-elevation-0', 'border-[0.5px] border-border-primary shadow-elevation-0',
'rounded-lg bg-fill-primary', 'rounded-lg bg-fill-primary',
'placeholder:text-label-placeholder' 'placeholder:text-label-placeholder',
'forced-colors:border forced-colors:border-[ButtonBorder] forced-colors:bg-[ButtonFace] forced-colors:text-[ButtonText]'
)} )}
/> />
</div> </div>
@@ -193,7 +217,10 @@ export function ExampleNicknameAndNoteDialog(): JSX.Element {
encrypted. They are only visible to you. encrypted. They are only visible to you.
</p> </p>
<div <div
className={tw('mx-auto size-20 rounded-full bg-color-fill-primary')} className={tw(
'mx-auto size-20 rounded-full bg-color-fill-primary',
'forced-colors:border'
)}
/> />
<Spacer height={12} /> <Spacer height={12} />
<TextInputField placeholder="First name" /> <TextInputField placeholder="First name" />
@@ -331,7 +358,10 @@ export function ExampleLanguageDialog(): JSX.Element {
// eslint-disable-next-line jsx-a11y/no-autofocus // eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus autoFocus
placeholder="Search languages" placeholder="Search languages"
className={tw('w-full rounded-lg bg-fill-secondary px-3 py-[5px]')} className={tw(
'w-full rounded-lg bg-fill-secondary px-3 py-[5px]',
'forced-colors:border forced-colors:border-[ButtonBorder] forced-colors:bg-[ButtonFace] forced-colors:text-[ButtonText]'
)}
/> />
</AxoDialog.ExperimentalSearch> </AxoDialog.ExperimentalSearch>
<AxoDialog.Body padding="only-scrollbar-gutter"> <AxoDialog.Body padding="only-scrollbar-gutter">

View File

@@ -16,6 +16,7 @@ import { tw } from './tw.dom.js';
import { AxoScrollArea } from './AxoScrollArea.dom.js'; import { AxoScrollArea } from './AxoScrollArea.dom.js';
import { getScrollbarGutters } from './_internal/scrollbars.dom.js'; import { getScrollbarGutters } from './_internal/scrollbars.dom.js';
import { AxoButton } from './AxoButton.dom.js'; import { AxoButton } from './AxoButton.dom.js';
import { AxoIconButton } from './AxoIconButton.dom.js';
const Namespace = 'AxoDialog'; const Namespace = 'AxoDialog';
@@ -237,9 +238,11 @@ export namespace AxoDialog {
export const Back: FC<BackProps> = memo(props => { export const Back: FC<BackProps> = memo(props => {
return ( return (
<div className={tw('col-[back-slot] text-start')}> <div className={tw('col-[back-slot] text-start')}>
<HeaderIconButton <AxoIconButton.Root
label={props['aria-label']} size="sm"
variant="borderless-secondary"
symbol="chevron-[start]" symbol="chevron-[start]"
aria-label={props['aria-label']}
/> />
</div> </div>
); );
@@ -260,7 +263,12 @@ export namespace AxoDialog {
return ( return (
<div className={tw('col-[close-slot] text-end')}> <div className={tw('col-[close-slot] text-end')}>
<Dialog.Close asChild> <Dialog.Close asChild>
<HeaderIconButton label={props['aria-label']} symbol="x" /> <AxoIconButton.Root
size="sm"
variant="borderless-secondary"
symbol="x"
aria-label={props['aria-label']}
/>
</Dialog.Close> </Dialog.Close>
</div> </div>
); );
@@ -462,4 +470,31 @@ export namespace AxoDialog {
}); });
Action.displayName = `${Namespace}.Action`; Action.displayName = `${Namespace}.Action`;
/**
* Component: <AxoDialog.Actions>
* ------------------------------
*/
export type IconActionVariant = 'primary' | 'destructive' | 'secondary';
export type IconActionProps = Readonly<{
'aria-label': string;
variant: ActionVariant;
symbol: AxoSymbol.IconName;
onClick: () => void;
}>;
export const IconAction: FC<IconActionProps> = memo(props => {
return (
<AxoIconButton.Root
aria-label={props['aria-label']}
variant={props.variant}
size="md"
symbol={props.symbol}
/>
);
});
IconAction.displayName = `${Namespace}.IconAction`;
} }

View File

@@ -0,0 +1,224 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { Fragment } from 'react';
import type { Meta } from '@storybook/react';
import { AxoIconButton } from './AxoIconButton.dom.js';
import type { TailwindStyles } from './tw.dom.js';
import { tw } from './tw.dom.js';
export default {
title: 'Axo/AxoIconButton',
} satisfies Meta;
export function Basic(): JSX.Element {
return (
<AxoIconButton.Root
variant="secondary"
size="lg"
symbol="more"
aria-label="More"
/>
);
}
const Backgrounds: Record<string, TailwindStyles> = {
'background-primary': tw('bg-background-primary'),
'background-secondary': tw('bg-background-secondary'),
'background-overlay': tw('bg-background-overlay'),
'elevated-background-primary': tw('bg-elevated-background-primary'),
'elevated-background-secondary': tw('bg-elevated-background-secondary'),
'elevated-background-tertiary': tw('bg-elevated-background-tertiary'),
'elevated-background-quaternary': tw('bg-elevated-background-quaternary'),
};
const Themes: Record<string, TailwindStyles> = {
light: tw('scheme-only-light'),
dark: tw('scheme-only-dark'),
};
function getRows() {
return Object.keys(Themes).flatMap(theme => {
return Object.keys(Backgrounds).map(background => {
return { theme, background };
});
});
}
export function Variants(): JSX.Element {
const variants = AxoIconButton._getAllVariants();
return (
<div className={tw('grid min-w-full')}>
{getRows().map((row, rowIndex) => {
return (
<Fragment key={rowIndex}>
<div
className={tw('flex items-center p-2 text-end')}
style={{
gridRow: rowIndex + 1,
gridColumn: 1,
}}
>
<code>
{row.background} ({row.theme})
</code>
</div>
{variants.map((variant, variantIndex) => {
return (
<div
key={variant}
className={tw(
'p-2 text-center',
Themes[row.theme],
Backgrounds[row.background]
)}
style={{
gridRow: rowIndex + 1,
gridColumn: variantIndex + 2,
}}
>
<AxoIconButton.Root
variant={variant}
size="lg"
symbol="more"
aria-label="More"
/>
</div>
);
})}
</Fragment>
);
})}
</div>
);
}
export function Sizes(): JSX.Element {
return (
<div className={tw('grid min-w-full')}>
{AxoIconButton._getAllSizes().map((size, sizeIndex) => {
return (
<Fragment key={size}>
<div
className={tw('flex items-center p-2 text-end')}
style={{
gridRow: sizeIndex + 1,
gridColumn: 1,
}}
>
<code>{size}</code>
</div>
{AxoIconButton._getAllVariants().map((variant, variantIndex) => {
return (
<div
key={variant}
className={tw('p-2 text-center')}
style={{
gridRow: sizeIndex + 1,
gridColumn: variantIndex + 2,
}}
>
<AxoIconButton.Root
variant={variant}
size={size}
symbol="more"
aria-label="More"
/>
</div>
);
})}
</Fragment>
);
})}
</div>
);
}
const AllStates: Record<string, Partial<AxoIconButton.RootProps>> = {
'disabled=true': { disabled: true },
'aria-pressed=false': { 'aria-pressed': false },
'aria-pressed=true': { 'aria-pressed': true },
};
export function States(): JSX.Element {
return (
<div className={tw('grid min-w-full')}>
{Object.keys(AllStates).map((state, stateIndex) => {
return (
<Fragment key={stateIndex}>
<div
className={tw('flex items-center p-2 text-end')}
style={{
gridRow: stateIndex + 1,
gridColumn: 1,
}}
>
<code>{state}</code>
</div>
{AxoIconButton._getAllVariants().map((variant, variantIndex) => {
return (
<div
key={variant}
className={tw('p-2 text-center')}
style={{
gridRow: stateIndex + 1,
gridColumn: variantIndex + 2,
}}
>
<AxoIconButton.Root
variant={variant}
size="lg"
symbol="more"
aria-label="More"
{...AllStates[state]}
/>
</div>
);
})}
</Fragment>
);
})}
</div>
);
}
export function Spinners(): JSX.Element {
return (
<div className={tw('grid min-w-full')}>
{AxoIconButton._getAllSizes().map((size, sizeIndex) => {
return (
<Fragment key={size}>
<div
className={tw('flex items-center p-2 text-end')}
style={{
gridRow: sizeIndex + 1,
gridColumn: 1,
}}
>
<code>{size}</code>
</div>
{AxoIconButton._getAllVariants().map((variant, variantIndex) => {
return (
<div
key={variant}
className={tw('p-2 text-center')}
style={{
gridRow: sizeIndex + 1,
gridColumn: variantIndex + 2,
}}
>
<AxoIconButton.Root
variant={variant}
size={size}
symbol="more"
aria-label="More"
experimentalSpinner={{ 'aria-label': 'Loading' }}
/>
</div>
);
})}
</Fragment>
);
})}
</div>
);
}

View File

@@ -0,0 +1,196 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ButtonHTMLAttributes, FC, ForwardedRef } from 'react';
import React, { forwardRef, memo } from 'react';
import { AxoSymbol } from './AxoSymbol.dom.js';
import type { TailwindStyles } from './tw.dom.js';
import { tw } from './tw.dom.js';
import type { SpinnerVariant } from '../components/SpinnerV2.dom.js';
import { SpinnerV2 } from '../components/SpinnerV2.dom.js';
const Namespace = 'AxoIconButton';
type GenericButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
export namespace AxoIconButton {
const baseStyles = tw(
'relative rounded-full align-top leading-none select-none',
'outline-border-focused not-forced-colors:outline-0 not-forced-colors:focused:outline-[2.5px]',
'forced-colors:border forced-colors:border-[ButtonBorder] forced-colors:bg-[ButtonFace] forced-colors:text-[ButtonText]',
'forced-colors:disabled:text-[GrayText]',
'forced-colors:aria-pressed:bg-[SelectedItem] forced-colors:aria-pressed:text-[SelectedItemText]'
);
const pressedStyles = {
fillInverted: tw(
'aria-pressed:bg-fill-inverted aria-pressed:pressed:bg-fill-inverted-pressed',
'aria-pressed:text-label-primary-inverted aria-pressed:disabled:text-label-disabled-inverted'
),
colorFillPrimary: tw(
'aria-pressed:bg-color-fill-primary aria-pressed:pressed:bg-color-fill-primary-pressed',
'aria-pressed:text-label-primary-on-color aria-pressed:disabled:text-label-disabled-on-color'
),
};
const Variants: Record<Variant, TailwindStyles> = {
secondary: tw(
'bg-fill-secondary pressed:bg-fill-secondary-pressed',
'text-label-primary disabled:text-label-disabled',
pressedStyles.fillInverted
),
primary: tw(
'bg-color-fill-primary pressed:bg-color-fill-primary-pressed',
'text-label-primary-on-color disabled:text-label-disabled-on-color',
pressedStyles.fillInverted
),
affirmative: tw(
'bg-color-fill-affirmative pressed:bg-color-fill-affirmative-pressed',
'text-label-primary-on-color disabled:text-label-disabled-on-color',
pressedStyles.fillInverted
),
destructive: tw(
'bg-color-fill-destructive pressed:bg-color-fill-destructive-pressed',
'text-label-primary-on-color disabled:text-label-disabled-on-color',
pressedStyles.fillInverted
),
'borderless-secondary': tw(
'hovered:bg-fill-secondary pressed:bg-fill-secondary-pressed',
'text-label-primary disabled:text-label-disabled',
pressedStyles.colorFillPrimary
),
'floating-secondary': tw(
'bg-fill-floating pressed:bg-fill-floating-pressed',
'text-label-primary disabled:text-label-disabled',
'shadow-elevation-1',
pressedStyles.fillInverted
),
};
export function _getAllVariants(): ReadonlyArray<Variant> {
return Object.keys(Variants) as ReadonlyArray<Variant>;
}
type SizeConfig = Readonly<{
buttonStyles: TailwindStyles;
iconSize: AxoSymbol.IconSize;
}>;
const Sizes: Record<Size, SizeConfig> = {
sm: { buttonStyles: tw('p-[5px]'), iconSize: 18 },
md: { buttonStyles: tw('p-1.5'), iconSize: 20 },
lg: { buttonStyles: tw('p-2'), iconSize: 20 },
};
export function _getAllSizes(): ReadonlyArray<Size> {
return Object.keys(Sizes) as ReadonlyArray<Size>;
}
export type Variant =
| 'secondary'
| 'primary'
| 'affirmative'
| 'destructive'
| 'borderless-secondary'
| 'floating-secondary';
export type Size = 'sm' | 'md' | 'lg';
export type RootProps = Readonly<{
// required: Should describe the purpose of the button, not the icon.
'aria-label': string;
variant: Variant;
size: Size;
symbol: AxoSymbol.IconName;
experimentalSpinner?: { 'aria-label': string } | null;
disabled?: GenericButtonProps['disabled'];
onClick?: GenericButtonProps['onClick'];
'aria-pressed'?: GenericButtonProps['aria-pressed'];
// Note: Technically we forward all props for Radix, but we restrict the
// props that the type accepts
}>;
export const Root: FC<RootProps> = memo(
forwardRef((props, ref: ForwardedRef<HTMLButtonElement>) => {
const { variant, size, symbol, experimentalSpinner, ...rest } = props;
return (
<button
ref={ref}
{...rest}
type="button"
className={tw(
baseStyles,
Variants[variant],
Sizes[size].buttonStyles
)}
>
<span
className={tw(
'forced-color-adjust-none',
experimentalSpinner != null ? 'opacity-0' : null
)}
>
<AxoSymbol.Icon
size={Sizes[size].iconSize}
symbol={symbol}
label={null}
/>
</span>
{experimentalSpinner != null && (
<Spinner
buttonVariant={variant}
buttonSize={size}
aria-label={experimentalSpinner['aria-label']}
/>
)}
</button>
);
})
);
Root.displayName = `${Namespace}.Root`;
const SpinnerVariants: Record<Variant, SpinnerVariant> = {
primary: 'axo-button-spinner-on-color',
secondary: 'axo-button-spinner-secondary',
affirmative: 'axo-button-spinner-on-color',
destructive: 'axo-button-spinner-on-color',
'floating-secondary': 'axo-button-spinner-secondary',
'borderless-secondary': 'axo-button-spinner-secondary',
};
type SpinnerSizeConfig = { size: number; strokeWidth: number };
const SpinnerSizes: Record<Size, SpinnerSizeConfig> = {
lg: { size: 20, strokeWidth: 2 },
md: { size: 20, strokeWidth: 2 },
sm: { size: 16, strokeWidth: 1.5 },
};
type SpinnerProps = Readonly<{
buttonVariant: Variant;
buttonSize: Size;
'aria-label': string;
}>;
// eslint-disable-next-line no-inner-declarations
function Spinner(props: SpinnerProps): JSX.Element {
const variant = SpinnerVariants[props.buttonVariant];
const sizeConfig = SpinnerSizes[props.buttonSize];
return (
<span className={tw('absolute inset-0 flex items-center justify-center')}>
<SpinnerV2
size={sizeConfig.size}
strokeWidth={sizeConfig.strokeWidth}
variant={variant}
value="indeterminate"
ariaLabel={props['aria-label']}
/>
</span>
);
}
}

View File

@@ -141,8 +141,7 @@ export namespace AxoScrollArea {
'rounded-[2px] outline-border-focused', 'rounded-[2px] outline-border-focused',
// Move the outline from the viewport to the parent // Move the outline from the viewport to the parent
// so it doesn't get cut off by <Mask> // so it doesn't get cut off by <Mask>
'[:where(.keyboard-mode)_&:has([data-axo-scroll-area-viewport]:focus)]:outline-[2.5px]', '[:where(.keyboard-mode)_&:has([data-axo-scroll-area-viewport]:focus)]:outline-[2.5px]'
'forced-colors:border forced-colors:border-[ButtonBorder]'
)} )}
style={style} style={style}
> >
@@ -310,15 +309,16 @@ export namespace AxoScrollArea {
'absolute z-10', 'absolute z-10',
'opacity-0', 'opacity-0',
'from-shadow-outline to-transparent dark:from-shadow-elevation-1', 'from-shadow-outline to-transparent dark:from-shadow-elevation-1',
'animate-duration-1 [animation-name:axo-scroll-area-hint-reveal]' 'animate-duration-1 [animation-name:axo-scroll-area-hint-reveal]',
'forced-colors:bg-[ButtonBorder]'
); );
// Need `animation-fill-mode` so we can customize the `animation-range` // Need `animation-fill-mode` so we can customize the `animation-range`
const edgeStartStyles = tw('animate-forwards'); const edgeStartStyles = tw('animate-both');
const edgeEndStyles = tw('animate-backwards animate-reverse'); const edgeEndStyles = tw('animate-both animate-reverse');
const edgeYStyles = tw('inset-x-0 h-0.5'); const edgeYStyles = tw('inset-x-0 h-0.5 forced-colors:h-px');
const edgeXStyles = tw('inset-y-0 w-0.5'); const edgeXStyles = tw('inset-y-0 w-0.5 forced-colors:w-px');
const HintEdges: Record<Edge, TailwindStyles> = { const HintEdges: Record<Edge, TailwindStyles> = {
top: tw( top: tw(

View File

@@ -36,7 +36,8 @@ export namespace AxoBaseDialog {
// Allow the entire overlay to be scrolled in case the window is extremely small // Allow the entire overlay to be scrolled in case the window is extremely small
'overflow-auto scrollbar-width-none', 'overflow-auto scrollbar-width-none',
'data-[state=closed]:animate-exit data-[state=open]:animate-enter', 'data-[state=closed]:animate-exit data-[state=open]:animate-enter',
'animate-opacity-0' 'animate-opacity-0',
'forced-colors:bg-[Canvas]'
); );
/** /**
@@ -48,9 +49,10 @@ export namespace AxoBaseDialog {
'relative', 'relative',
'max-h-full min-h-fit max-w-full min-w-fit', 'max-h-full min-h-fit max-w-full min-w-fit',
'rounded-3xl bg-elevated-background-primary shadow-elevation-3 select-none', 'rounded-3xl bg-elevated-background-primary shadow-elevation-3 select-none',
'outline-0 outline-border-focused focused:outline-[2.5px]', 'outline-border-focused not-forced-colors:outline-0 not-forced-colors:focused:outline-[2.5px]',
'data-[state=closed]:animate-exit data-[state=open]:animate-enter', 'data-[state=closed]:animate-exit data-[state=open]:animate-enter',
'animate-scale-98 animate-translate-y-1' 'animate-scale-98 animate-translate-y-1',
'forced-colors:border forced-colors:border-[ButtonBorder] forced-colors:bg-[Canvas] forced-colors:text-[CanvasText]'
); );
export type ContentEscape = 'cancel-is-noop' | 'cancel-is-destructive'; export type ContentEscape = 'cancel-is-noop' | 'cancel-is-destructive';

View File

@@ -837,8 +837,6 @@ function NotificationProfilesNamePage({
<AxoButton.Root <AxoButton.Root
variant="primary" variant="primary"
size="lg" size="lg"
type="button"
form="notificationProfileName"
disabled={!isValid} disabled={!isValid}
onClick={onNext} onClick={onNext}
> >
@@ -907,16 +905,9 @@ function NotificationProfilesAllowedPage({
/> />
</Container> </Container>
<ButtonContainer> <ButtonContainer>
<form <AxoButton.Root variant="primary" size="lg" onClick={onNext}>
onSubmit={e => { {i18n('icu:next2')}
e.preventDefault(); </AxoButton.Root>
onNext();
}}
>
<AxoButton.Root variant="primary" size="lg" type="submit">
{i18n('icu:next2')}
</AxoButton.Root>
</form>
</ButtonContainer> </ButtonContainer>
</> </>
); );
@@ -1076,12 +1067,7 @@ function NotificationProfilesSchedulePage({
</FullWidthRow> </FullWidthRow>
</Container> </Container>
<ButtonContainer> <ButtonContainer>
<AxoButton.Root <AxoButton.Root variant="primary" size="lg" onClick={onNext}>
variant="primary"
size="lg"
type="button"
onClick={onNext}
>
{isEditing ? i18n('icu:done') : i18n('icu:next2')} {isEditing ? i18n('icu:done') : i18n('icu:next2')}
</AxoButton.Root> </AxoButton.Root>
</ButtonContainer> </ButtonContainer>
@@ -1111,12 +1097,7 @@ function NotificationProfilesDonePage({
<p className={tw('mt-4 mb-6 max-w-[350px] text-center leading-5')}> <p className={tw('mt-4 mb-6 max-w-[350px] text-center leading-5')}>
{i18n('icu:NotificationProfiles--done-description')} {i18n('icu:NotificationProfiles--done-description')}
</p> </p>
<AxoButton.Root <AxoButton.Root variant="primary" size="lg" onClick={onNext}>
variant="primary"
size="lg"
type="button"
onClick={onNext}
>
{i18n('icu:done')} {i18n('icu:done')}
</AxoButton.Root> </AxoButton.Root>
</MidFloatingContainer> </MidFloatingContainer>
@@ -1406,12 +1387,7 @@ function NotificationProfilesEditPage({
</FullWidthButton> </FullWidthButton>
</Container> </Container>
<ButtonContainer> <ButtonContainer>
<AxoButton.Root <AxoButton.Root variant="primary" size="lg" onClick={onBack}>
variant="primary"
size="lg"
type="button"
onClick={onBack}
>
{i18n('icu:done')} {i18n('icu:done')}
</AxoButton.Root> </AxoButton.Root>
</ButtonContainer> </ButtonContainer>