@westpac/ui 1.15.2 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,2 @@
1
- import React from 'react';
2
- import { ButtonRef } from './button.types.js';
3
- export declare const Button: React.ForwardRefExoticComponent<{
4
- block?: import("../../types/responsive-variants.types.js").ResponsiveVariants<boolean | undefined>;
5
- children?: React.ReactNode;
6
- iconAfter?: (props: import("../icon/icon.types.js").IconProps) => JSX.Element;
7
- iconBefore?: (props: import("../icon/icon.types.js").IconProps) => JSX.Element;
8
- iconColor?: import("../icon/icon.types.js").IconProps["color"];
9
- iconLook?: import("../icon/icon.types.js").IconProps["look"];
10
- iconSize?: import("../icon/icon.types.js").IconProps["size"];
11
- justify?: import("../../types/responsive-variants.types.js").ResponsiveVariants<boolean | undefined>;
12
- look?: import("../../types/responsive-variants.types.js").ResponsiveVariants<"link" | "primary" | "hero" | "faint" | "unstyled" | undefined>;
13
- removeLinkPadding?: boolean;
14
- size?: import("../../types/responsive-variants.types.js").ResponsiveVariants<"small" | "xlarge" | "medium" | "large" | undefined>;
15
- soft?: import("../../types/responsive-variants.types.js").ResponsiveVariants<boolean | undefined>;
16
- tag?: keyof Pick<JSX.IntrinsicElements, "a" | "span" | "button" | "div">;
17
- } & React.ButtonHTMLAttributes<Element> & React.AnchorHTMLAttributes<Element> & React.RefAttributes<ButtonRef>>;
1
+ import { ButtonComponent } from './button.types.js';
2
+ export declare const Button: ButtonComponent;
@@ -5,7 +5,13 @@ import { useBreakpoint } from '../../hook/breakpoints.hook.js';
5
5
  import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
6
6
  import { styles as buttonStyles } from './button.styles.js';
7
7
  import { getIconSize } from './button.utils.js';
8
- function BaseButton({ className, size = 'medium', look = 'hero', soft, block = false, justify, tag: Tag = 'button', iconBefore: IconBefore, iconAfter: IconAfter, iconLook, iconColor, iconSize, children, removeLinkPadding, type = Tag === 'button' ? 'button' : undefined, ...props }, ref) {
8
+ function BaseButton({ className, size = 'medium', look = 'hero', soft, block = false, justify, tag: Tag, iconBefore: IconBefore, iconAfter: IconAfter, iconLook, iconColor, iconSize, children, removeLinkPadding, ...props }, ref) {
9
+ var _props_type;
10
+ const Component = Tag !== null && Tag !== void 0 ? Tag : 'button';
11
+ const renderProps = Component === 'button' ? {
12
+ ...props,
13
+ type: (_props_type = props.type) !== null && _props_type !== void 0 ? _props_type : 'button'
14
+ } : props;
9
15
  const { isFocusVisible, focusProps } = useFocusRing();
10
16
  const btnIconSize = useMemo(()=>iconSize || getIconSize(size), [
11
17
  iconSize,
@@ -52,13 +58,12 @@ function BaseButton({ className, size = 'medium', look = 'hero', soft, block = f
52
58
  look,
53
59
  soft
54
60
  ]);
55
- return React.createElement(Tag, {
61
+ return React.createElement(Component, {
56
62
  ref: ref,
57
63
  className: styles.base({
58
64
  className
59
65
  }),
60
- ...mergeProps(props, focusProps),
61
- type: type
66
+ ...mergeProps(renderProps, focusProps)
62
67
  }, IconBefore && React.createElement(IconBefore, {
63
68
  look: iconLook,
64
69
  size: btnIconSize,
@@ -1,11 +1,10 @@
1
- import { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
1
+ import { ReactNode } from 'react';
2
2
  import { type VariantProps } from 'tailwind-variants';
3
3
  import { ResponsiveVariants } from '../../types/responsive-variants.types.js';
4
4
  import { IconProps } from '../icon/index.js';
5
5
  import { styles } from './button.styles.js';
6
6
  type Variants = VariantProps<typeof styles>;
7
- export type ButtonRef = HTMLButtonElement & HTMLAnchorElement & HTMLSpanElement & HTMLDivElement;
8
- export type ButtonProps = {
7
+ export type BaseButtonProps = {
9
8
  /**
10
9
  * Fit button width to its parent width.
11
10
  * @default false
@@ -64,10 +63,21 @@ export type ButtonProps = {
64
63
  * Removes background colour and adjusts text colour.
65
64
  */
66
65
  soft?: ResponsiveVariants<Variants['soft']>;
66
+ };
67
+ export type ButtonRef<C extends React.ElementType = 'button'> = React.ComponentRef<C>;
68
+ export type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];
69
+ export type ButtonProps<C extends React.ElementType = 'button'> = BaseButtonProps & {
67
70
  /**
68
- * Tag to render
71
+ * Type to render
69
72
  * @default button
70
73
  */
71
- tag?: keyof Pick<JSX.IntrinsicElements, 'a' | 'span' | 'button' | 'div'>;
72
- } & ButtonHTMLAttributes<Element> & AnchorHTMLAttributes<Element>;
74
+ tag?: C;
75
+ } & Omit<React.ComponentPropsWithoutRef<C>, keyof BaseButtonProps | 'tag'>;
76
+ export type ButtonImplementationProps = BaseButtonProps & {
77
+ className?: string;
78
+ tag?: React.ElementType;
79
+ } & Record<string, unknown>;
80
+ export type ButtonComponent = <C extends React.ElementType = 'button'>(props: ButtonProps<C> & {
81
+ ref?: PolymorphicRef<C>;
82
+ }) => React.ReactElement | null;
73
83
  export {};
@@ -1,12 +1,4 @@
1
- import React, { ForwardedRef } from 'react';
2
- import { type LinkProps } from './link.types.js';
3
- export declare function BaseLink({ className, children, href, iconBefore: IconBefore, iconAfter: IconAfter, iconSize, target, type, underline, ...props }: LinkProps, ref: ForwardedRef<HTMLAnchorElement>): import("react/jsx-runtime").JSX.Element;
4
- export declare const Link: React.ForwardRefExoticComponent<{
5
- children?: React.ReactNode;
6
- className?: string;
7
- iconAfter?: (props: import("../icon/icon.types.js").IconProps) => JSX.Element;
8
- iconBefore?: (props: import("../icon/icon.types.js").IconProps) => JSX.Element;
9
- iconSize?: "xsmall" | "small" | "medium" | "large" | "xlarge";
10
- type?: "inline" | "standalone";
11
- underline?: boolean;
12
- } & Omit<import("react-aria").AriaLinkOptions, "isDisabled" | "elementType"> & React.AnchorHTMLAttributes<Element> & React.RefAttributes<HTMLAnchorElement>>;
1
+ import { ForwardedRef } from 'react';
2
+ import { LinkComponent, LinkImplementationProps } from './link.types.js';
3
+ export declare function BaseLink({ className, children, href, tag: Tag, iconBefore: IconBefore, iconAfter: IconAfter, iconSize, target, type, underline, autoFocus, elementType, onBlur, onClick, onFocus, onFocusChange, onKeyDown, onKeyUp, onPress, onPressChange, onPressEnd, onPressStart, onPressUp, download, ping, referrerPolicy, rel, routerOptions, ...componentProps }: LinkImplementationProps, ref: ForwardedRef<unknown>): import("react/jsx-runtime").JSX.Element;
4
+ export declare const Link: LinkComponent;
@@ -1,13 +1,35 @@
1
1
  'use client';
2
2
  import React, { forwardRef } from 'react';
3
- import { mergeProps, useFocusRing, useLink } from 'react-aria';
3
+ import { mergeProps, useFocusRing, useLink, useObjectRef } from 'react-aria';
4
4
  import { ArrowRightIcon } from '../icon/index.js';
5
5
  import { styles as linkStyles } from './link.styles.js';
6
- export function BaseLink({ className, children, href, iconBefore: IconBefore, iconAfter: IconAfter, iconSize = 'small', target, type = 'standalone', underline = true, ...props }, ref) {
6
+ export function BaseLink({ className, children, href, tag: Tag, iconBefore: IconBefore, iconAfter: IconAfter, iconSize = 'small', target, type = 'standalone', underline = true, autoFocus, elementType, onBlur, onClick, onFocus, onFocusChange, onKeyDown, onKeyUp, onPress, onPressChange, onPressEnd, onPressStart, onPressUp, download, ping, referrerPolicy, rel, routerOptions, ...componentProps }, ref) {
7
+ const Component = Tag !== null && Tag !== void 0 ? Tag : 'a';
8
+ const ariaElementType = typeof Component === 'string' ? Component : elementType !== null && elementType !== void 0 ? elementType : 'a';
9
+ const linkRef = useObjectRef(ref);
7
10
  const { linkProps } = useLink({
8
- ...props,
9
- elementType: 'a'
10
- }, ref);
11
+ ...componentProps,
12
+ autoFocus,
13
+ download,
14
+ elementType: ariaElementType,
15
+ href: typeof href === 'string' ? href : undefined,
16
+ onBlur,
17
+ onClick,
18
+ onFocus,
19
+ onFocusChange,
20
+ onKeyDown,
21
+ onKeyUp,
22
+ onPress,
23
+ onPressChange,
24
+ onPressEnd,
25
+ onPressStart,
26
+ onPressUp,
27
+ ping,
28
+ referrerPolicy,
29
+ rel,
30
+ routerOptions,
31
+ target
32
+ }, linkRef);
11
33
  const { isFocusVisible, focusProps } = useFocusRing();
12
34
  const styles = linkStyles({
13
35
  type,
@@ -17,9 +39,10 @@ export function BaseLink({ className, children, href, iconBefore: IconBefore, ic
17
39
  if (type === 'standalone' && !IconBefore && !IconAfter) {
18
40
  IconBefore = ArrowRightIcon;
19
41
  }
20
- return React.createElement("a", {
42
+ return React.createElement(Component, {
43
+ ...componentProps,
21
44
  ...mergeProps(linkProps, focusProps),
22
- ref: ref,
45
+ ref: linkRef,
23
46
  href: href,
24
47
  target: target,
25
48
  className: styles.base({
@@ -1,7 +1,7 @@
1
- import { AnchorHTMLAttributes, ReactNode } from 'react';
1
+ import { ReactNode } from 'react';
2
2
  import { AriaLinkOptions } from 'react-aria';
3
3
  import { IconProps } from '../icon/index.js';
4
- export type LinkProps = {
4
+ export type BaseLinkProps = {
5
5
  /**
6
6
  * Link text or component
7
7
  */
@@ -33,4 +33,27 @@ export type LinkProps = {
33
33
  * @default true
34
34
  */
35
35
  underline?: boolean;
36
- } & Omit<AriaLinkOptions, 'isDisabled' | 'elementType'> & AnchorHTMLAttributes<Element>;
36
+ /**
37
+ * Specifies DOM element rendered by a custom component
38
+ * Required when the custom component does not render an '<a>' element
39
+ * @default 'a'
40
+ */
41
+ elementType?: AriaLinkOptions['elementType'];
42
+ } & Omit<AriaLinkOptions, 'elementType' | 'href' | 'isDisabled'>;
43
+ export type LinkRef<C extends React.ElementType = 'a'> = React.ComponentRef<C>;
44
+ export type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];
45
+ export type LinkProps<C extends React.ElementType = 'a'> = BaseLinkProps & {
46
+ /**
47
+ * Type to render
48
+ * @default a
49
+ */
50
+ tag?: C;
51
+ } & Omit<React.ComponentPropsWithoutRef<C>, keyof BaseLinkProps | 'tag'>;
52
+ export type LinkImplementationProps = BaseLinkProps & {
53
+ className?: string;
54
+ href?: unknown;
55
+ tag?: React.ElementType;
56
+ } & Record<string, unknown>;
57
+ export type LinkComponent = <C extends React.ElementType = 'a'>(props: LinkProps<C> & {
58
+ ref?: PolymorphicRef<C>;
59
+ }) => React.ReactElement | null;
@@ -1,2 +1,2 @@
1
1
  import { type ModalDialogFooterProps } from './modal-dialog-footer.types.js';
2
- export declare function ModalDialogFooter({ className, primaryLabel, primaryOnClick, secondaryLabel, secondaryOnClick, ...props }: ModalDialogFooterProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ModalDialogFooter({ className, primaryButtonProps, primaryLabel, primaryOnClick, secondaryButtonProps, secondaryLabel, secondaryOnClick, ...props }: ModalDialogFooterProps): import("react/jsx-runtime").JSX.Element;
@@ -5,7 +5,8 @@ import { resolveResponsiveVariant } from '../../../../../../utils/breakpoint.uti
5
5
  import { Button } from '../../../../../button/index.js';
6
6
  import { useModalDialogContext } from '../../modal-dialog.component.js';
7
7
  import { styles as modalFooterStyles } from './modal-dialog-footer.styles.js';
8
- export function ModalDialogFooter({ className, primaryLabel, primaryOnClick, secondaryLabel, secondaryOnClick, ...props }) {
8
+ export function ModalDialogFooter({ className, primaryButtonProps, primaryLabel, primaryOnClick, secondaryButtonProps, secondaryLabel, secondaryOnClick, ...props }) {
9
+ var _ref, _ref1, _ref2, _ref3;
9
10
  const { size, compact, setFooterPresent } = useModalDialogContext();
10
11
  const breakpoint = useBreakpoint();
11
12
  const styles = modalFooterStyles({
@@ -23,14 +24,20 @@ export function ModalDialogFooter({ className, primaryLabel, primaryOnClick, sec
23
24
  }),
24
25
  ...props
25
26
  }, React.createElement(Button, {
26
- look: "primary",
27
- size: "large",
28
- className: styles.primaryBtn(),
27
+ ...primaryButtonProps,
28
+ look: (_ref = primaryButtonProps === null || primaryButtonProps === void 0 ? void 0 : primaryButtonProps.look) !== null && _ref !== void 0 ? _ref : 'primary',
29
+ size: (_ref1 = primaryButtonProps === null || primaryButtonProps === void 0 ? void 0 : primaryButtonProps.size) !== null && _ref1 !== void 0 ? _ref1 : 'large',
30
+ className: styles.primaryBtn({
31
+ className: primaryButtonProps === null || primaryButtonProps === void 0 ? void 0 : primaryButtonProps.className
32
+ }),
29
33
  onClick: primaryOnClick
30
34
  }, primaryLabel), secondaryLabel && React.createElement(Button, {
31
- look: "link",
32
- size: "large",
33
- className: styles.secondaryBtn(),
35
+ ...secondaryButtonProps,
36
+ look: (_ref2 = secondaryButtonProps === null || secondaryButtonProps === void 0 ? void 0 : secondaryButtonProps.look) !== null && _ref2 !== void 0 ? _ref2 : 'link',
37
+ size: (_ref3 = secondaryButtonProps === null || secondaryButtonProps === void 0 ? void 0 : secondaryButtonProps.size) !== null && _ref3 !== void 0 ? _ref3 : 'large',
38
+ className: styles.secondaryBtn({
39
+ className: secondaryButtonProps === null || secondaryButtonProps === void 0 ? void 0 : secondaryButtonProps.className
40
+ }),
34
41
  onClick: secondaryOnClick
35
42
  }, secondaryLabel));
36
43
  }
@@ -1,9 +1,15 @@
1
1
  import { HTMLAttributes, ReactNode } from 'react';
2
+ import { type ButtonProps } from '../../../../../button/index.js';
3
+ type ModalFooterButtonProps = Omit<ButtonProps, 'children' | 'onClick'>;
2
4
  export type ModalDialogFooterProps = {
3
5
  /**
4
6
  * Footer content
5
7
  */
6
8
  children?: ReactNode;
9
+ /**
10
+ * Props passed to the primary button
11
+ */
12
+ primaryButtonProps?: ModalFooterButtonProps;
7
13
  /**
8
14
  * Label for primary button
9
15
  */
@@ -12,6 +18,10 @@ export type ModalDialogFooterProps = {
12
18
  * onClick for primary button
13
19
  */
14
20
  primaryOnClick: () => void;
21
+ /**
22
+ * Props passed to the secondary button
23
+ */
24
+ secondaryButtonProps?: ModalFooterButtonProps;
15
25
  /**
16
26
  * Label for secondary button
17
27
  */
@@ -21,3 +31,4 @@ export type ModalDialogFooterProps = {
21
31
  */
22
32
  secondaryOnClick?: () => void;
23
33
  } & HTMLAttributes<HTMLElement>;
34
+ export {};
@@ -1,2 +1,2 @@
1
1
  export { PassCode } from './pass-code.component.js';
2
- export { type PassCodeProps } from './pass-code.types.js';
2
+ export { type PassCodeProps, type PassCodeRef } from './pass-code.types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westpac/ui",
3
- "version": "1.15.2",
3
+ "version": "1.18.0",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -259,10 +259,10 @@
259
259
  "typescript": "^5.5.4",
260
260
  "vite": "^7.3.5",
261
261
  "vitest": "^3.2.6",
262
- "@westpac/test-config": "~0.0.0",
263
- "@westpac/ts-config": "~0.0.0",
262
+ "@westpac/eslint-config": "~1.1.0",
264
263
  "@westpac/style-config": "~1.0.2",
265
- "@westpac/eslint-config": "~1.1.0"
264
+ "@westpac/test-config": "~0.0.0",
265
+ "@westpac/ts-config": "~0.0.0"
266
266
  },
267
267
  "dependencies": {
268
268
  "@internationalized/date": "^3.12.0",
@@ -280,6 +280,7 @@
280
280
  },
281
281
  "peerDependencies": {
282
282
  "react": ">=18.2.0",
283
+ "react-dom": ">=18.2.0",
283
284
  "tailwindcss": "~4.1.12"
284
285
  },
285
286
  "repository": {
@@ -1,13 +1,13 @@
1
1
  'use client';
2
2
 
3
- import React, { Ref, forwardRef, useMemo } from 'react';
3
+ import React, { ForwardedRef, forwardRef, useMemo } from 'react';
4
4
  import { mergeProps, useFocusRing } from 'react-aria';
5
5
 
6
6
  import { useBreakpoint } from '../../hook/breakpoints.hook.js';
7
7
  import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
8
8
 
9
9
  import { styles as buttonStyles } from './button.styles.js';
10
- import { type ButtonProps, ButtonRef } from './button.types.js';
10
+ import { ButtonImplementationProps, ButtonComponent } from './button.types.js';
11
11
  import { getIconSize } from './button.utils.js';
12
12
 
13
13
  function BaseButton(
@@ -18,7 +18,7 @@ function BaseButton(
18
18
  soft,
19
19
  block = false,
20
20
  justify,
21
- tag: Tag = 'button',
21
+ tag: Tag,
22
22
  iconBefore: IconBefore,
23
23
  iconAfter: IconAfter,
24
24
  iconLook,
@@ -26,11 +26,12 @@ function BaseButton(
26
26
  iconSize,
27
27
  children,
28
28
  removeLinkPadding,
29
- type = Tag === 'button' ? 'button' : undefined,
30
29
  ...props
31
- }: ButtonProps,
32
- ref: Ref<ButtonRef>,
30
+ }: ButtonImplementationProps,
31
+ ref: ForwardedRef<unknown>,
33
32
  ) {
33
+ const Component = Tag ?? 'button';
34
+ const renderProps = Component === 'button' ? { ...props, type: props.type ?? 'button' } : props;
34
35
  const { isFocusVisible, focusProps } = useFocusRing();
35
36
  const btnIconSize = useMemo(() => iconSize || getIconSize(size), [iconSize, size]);
36
37
  const breakpoint = useBreakpoint();
@@ -76,7 +77,7 @@ function BaseButton(
76
77
  }, [iconColor, look, soft]);
77
78
 
78
79
  return (
79
- <Tag ref={ref} className={styles.base({ className })} {...mergeProps(props, focusProps)} type={type}>
80
+ <Component ref={ref} className={styles.base({ className })} {...mergeProps(renderProps, focusProps)}>
80
81
  {IconBefore && (
81
82
  <IconBefore
82
83
  look={iconLook}
@@ -96,8 +97,8 @@ function BaseButton(
96
97
  aria-hidden
97
98
  />
98
99
  )}
99
- </Tag>
100
+ </Component>
100
101
  );
101
102
  }
102
103
 
103
- export const Button = forwardRef(BaseButton);
104
+ export const Button = forwardRef(BaseButton) as unknown as ButtonComponent;
@@ -1,4 +1,4 @@
1
- import { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
1
+ import { ReactNode } from 'react';
2
2
  import { type VariantProps } from 'tailwind-variants';
3
3
 
4
4
  import { ResponsiveVariants } from '../../types/responsive-variants.types.js';
@@ -8,9 +8,7 @@ import { styles } from './button.styles.js';
8
8
 
9
9
  type Variants = VariantProps<typeof styles>;
10
10
 
11
- export type ButtonRef = HTMLButtonElement & HTMLAnchorElement & HTMLSpanElement & HTMLDivElement;
12
-
13
- export type ButtonProps = {
11
+ export type BaseButtonProps = {
14
12
  /**
15
13
  * Fit button width to its parent width.
16
14
  * @default false
@@ -69,10 +67,25 @@ export type ButtonProps = {
69
67
  * Removes background colour and adjusts text colour.
70
68
  */
71
69
  soft?: ResponsiveVariants<Variants['soft']>;
70
+ };
71
+
72
+ export type ButtonRef<C extends React.ElementType = 'button'> = React.ComponentRef<C>;
73
+
74
+ export type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];
75
+
76
+ export type ButtonProps<C extends React.ElementType = 'button'> = BaseButtonProps & {
72
77
  /**
73
- * Tag to render
78
+ * Type to render
74
79
  * @default button
75
80
  */
76
- tag?: keyof Pick<JSX.IntrinsicElements, 'a' | 'span' | 'button' | 'div'>;
77
- } & ButtonHTMLAttributes<Element> &
78
- AnchorHTMLAttributes<Element>;
81
+ tag?: C;
82
+ } & Omit<React.ComponentPropsWithoutRef<C>, keyof BaseButtonProps | 'tag'>;
83
+
84
+ export type ButtonImplementationProps = BaseButtonProps & {
85
+ className?: string;
86
+ tag?: React.ElementType;
87
+ } & Record<string, unknown>;
88
+
89
+ export type ButtonComponent = <C extends React.ElementType = 'button'>(
90
+ props: ButtonProps<C> & { ref?: PolymorphicRef<C> },
91
+ ) => React.ReactElement | null;
@@ -1,30 +1,77 @@
1
- // TODO: This component will not work with the Next Link component in Next.js 16+ as legacyBehavior as has been deprecated in 15. Need to find a solution.
2
1
  'use client';
3
2
 
4
- import React, { ForwardedRef, RefObject, forwardRef } from 'react';
5
- import { mergeProps, useFocusRing, useLink } from 'react-aria';
3
+ import { type FocusableElement } from '@react-types/shared';
4
+ import React, { ForwardedRef, forwardRef } from 'react';
5
+ import { mergeProps, useFocusRing, useLink, useObjectRef } from 'react-aria';
6
6
 
7
7
  import { ArrowRightIcon } from '../icon/index.js';
8
8
 
9
9
  import { styles as linkStyles } from './link.styles.js';
10
- import { type LinkProps } from './link.types.js';
10
+ import { LinkComponent, LinkImplementationProps } from './link.types.js';
11
11
 
12
12
  export function BaseLink(
13
13
  {
14
14
  className,
15
15
  children,
16
16
  href,
17
+ tag: Tag,
17
18
  iconBefore: IconBefore,
18
19
  iconAfter: IconAfter,
19
20
  iconSize = 'small',
20
21
  target,
21
22
  type = 'standalone',
22
23
  underline = true,
23
- ...props
24
- }: LinkProps,
25
- ref: ForwardedRef<HTMLAnchorElement>,
24
+ autoFocus,
25
+ elementType,
26
+ onBlur,
27
+ onClick,
28
+ onFocus,
29
+ onFocusChange,
30
+ onKeyDown,
31
+ onKeyUp,
32
+ onPress,
33
+ onPressChange,
34
+ onPressEnd,
35
+ onPressStart,
36
+ onPressUp,
37
+ download,
38
+ ping,
39
+ referrerPolicy,
40
+ rel,
41
+ routerOptions,
42
+ ...componentProps
43
+ }: LinkImplementationProps,
44
+ ref: ForwardedRef<unknown>,
26
45
  ) {
27
- const { linkProps } = useLink({ ...props, elementType: 'a' }, ref as RefObject<HTMLAnchorElement>);
46
+ const Component = Tag ?? 'a';
47
+ const ariaElementType = typeof Component === 'string' ? Component : (elementType ?? 'a');
48
+ const linkRef = useObjectRef(ref as ForwardedRef<FocusableElement>);
49
+ const { linkProps } = useLink(
50
+ {
51
+ ...componentProps,
52
+ autoFocus,
53
+ download,
54
+ elementType: ariaElementType,
55
+ href: typeof href === 'string' ? href : undefined,
56
+ onBlur,
57
+ onClick,
58
+ onFocus,
59
+ onFocusChange,
60
+ onKeyDown,
61
+ onKeyUp,
62
+ onPress,
63
+ onPressChange,
64
+ onPressEnd,
65
+ onPressStart,
66
+ onPressUp,
67
+ ping,
68
+ referrerPolicy,
69
+ rel,
70
+ routerOptions,
71
+ target,
72
+ },
73
+ linkRef,
74
+ );
28
75
  const { isFocusVisible, focusProps } = useFocusRing();
29
76
  const styles = linkStyles({ type, underline, isFocusVisible });
30
77
 
@@ -33,9 +80,10 @@ export function BaseLink(
33
80
  }
34
81
 
35
82
  return (
36
- <a
83
+ <Component
84
+ {...componentProps}
37
85
  {...mergeProps(linkProps, focusProps)}
38
- ref={ref}
86
+ ref={linkRef}
39
87
  href={href}
40
88
  target={target}
41
89
  className={styles.base({ className })}
@@ -43,8 +91,8 @@ export function BaseLink(
43
91
  {IconBefore && <IconBefore size={iconSize} color="primary" className={styles.iconBefore()} />}
44
92
  <span>{children}</span>
45
93
  {IconAfter && <IconAfter size={iconSize} color="primary" className={styles.iconAfter()} />}
46
- </a>
94
+ </Component>
47
95
  );
48
96
  }
49
97
 
50
- export const Link = forwardRef(BaseLink);
98
+ export const Link = forwardRef(BaseLink) as unknown as LinkComponent;
@@ -1,9 +1,9 @@
1
- import { AnchorHTMLAttributes, ReactNode } from 'react';
1
+ import { ReactNode } from 'react';
2
2
  import { AriaLinkOptions } from 'react-aria';
3
3
 
4
4
  import { IconProps } from '../icon/index.js';
5
5
 
6
- export type LinkProps = {
6
+ export type BaseLinkProps = {
7
7
  /**
8
8
  * Link text or component
9
9
  */
@@ -35,5 +35,32 @@ export type LinkProps = {
35
35
  * @default true
36
36
  */
37
37
  underline?: boolean;
38
- } & Omit<AriaLinkOptions, 'isDisabled' | 'elementType'> &
39
- AnchorHTMLAttributes<Element>;
38
+ /**
39
+ * Specifies DOM element rendered by a custom component
40
+ * Required when the custom component does not render an '<a>' element
41
+ * @default 'a'
42
+ */
43
+ elementType?: AriaLinkOptions['elementType'];
44
+ } & Omit<AriaLinkOptions, 'elementType' | 'href' | 'isDisabled'>;
45
+
46
+ export type LinkRef<C extends React.ElementType = 'a'> = React.ComponentRef<C>;
47
+
48
+ export type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];
49
+
50
+ export type LinkProps<C extends React.ElementType = 'a'> = BaseLinkProps & {
51
+ /**
52
+ * Type to render
53
+ * @default a
54
+ */
55
+ tag?: C;
56
+ } & Omit<React.ComponentPropsWithoutRef<C>, keyof BaseLinkProps | 'tag'>;
57
+
58
+ export type LinkImplementationProps = BaseLinkProps & {
59
+ className?: string;
60
+ href?: unknown;
61
+ tag?: React.ElementType;
62
+ } & Record<string, unknown>;
63
+
64
+ export type LinkComponent = <C extends React.ElementType = 'a'>(
65
+ props: LinkProps<C> & { ref?: PolymorphicRef<C> },
66
+ ) => React.ReactElement | null;
@@ -12,8 +12,10 @@ import { type ModalDialogFooterProps } from './modal-dialog-footer.types.js';
12
12
 
13
13
  export function ModalDialogFooter({
14
14
  className,
15
+ primaryButtonProps,
15
16
  primaryLabel,
16
17
  primaryOnClick,
18
+ secondaryButtonProps,
17
19
  secondaryLabel,
18
20
  secondaryOnClick,
19
21
  ...props
@@ -29,11 +31,23 @@ export function ModalDialogFooter({
29
31
 
30
32
  return (
31
33
  <div className={styles.base({ className })} {...props}>
32
- <Button look="primary" size="large" className={styles.primaryBtn()} onClick={primaryOnClick}>
34
+ <Button
35
+ {...primaryButtonProps}
36
+ look={primaryButtonProps?.look ?? 'primary'}
37
+ size={primaryButtonProps?.size ?? 'large'}
38
+ className={styles.primaryBtn({ className: primaryButtonProps?.className })}
39
+ onClick={primaryOnClick}
40
+ >
33
41
  {primaryLabel}
34
42
  </Button>
35
43
  {secondaryLabel && (
36
- <Button look="link" size="large" className={styles.secondaryBtn()} onClick={secondaryOnClick}>
44
+ <Button
45
+ {...secondaryButtonProps}
46
+ look={secondaryButtonProps?.look ?? 'link'}
47
+ size={secondaryButtonProps?.size ?? 'large'}
48
+ className={styles.secondaryBtn({ className: secondaryButtonProps?.className })}
49
+ onClick={secondaryOnClick}
50
+ >
37
51
  {secondaryLabel}
38
52
  </Button>
39
53
  )}
@@ -1,10 +1,18 @@
1
1
  import { HTMLAttributes, ReactNode } from 'react';
2
2
 
3
+ import { type ButtonProps } from '../../../../../button/index.js';
4
+
5
+ type ModalFooterButtonProps = Omit<ButtonProps, 'children' | 'onClick'>;
6
+
3
7
  export type ModalDialogFooterProps = {
4
8
  /**
5
9
  * Footer content
6
10
  */
7
11
  children?: ReactNode;
12
+ /**
13
+ * Props passed to the primary button
14
+ */
15
+ primaryButtonProps?: ModalFooterButtonProps;
8
16
  /**
9
17
  * Label for primary button
10
18
  */
@@ -13,6 +21,10 @@ export type ModalDialogFooterProps = {
13
21
  * onClick for primary button
14
22
  */
15
23
  primaryOnClick: () => void;
24
+ /**
25
+ * Props passed to the secondary button
26
+ */
27
+ secondaryButtonProps?: ModalFooterButtonProps;
16
28
  /**
17
29
  * Label for secondary button
18
30
  */
@@ -1,2 +1,2 @@
1
1
  export { PassCode } from './pass-code.component.js';
2
- export { type PassCodeProps } from './pass-code.types.js';
2
+ export { type PassCodeProps, type PassCodeRef } from './pass-code.types.js';