@westpac/ui 1.14.0 → 1.15.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/component-type.json +1 -1
  3. package/dist/components/flyout/components/flyout-backdrop/flyout-backdrop.component.d.ts +5 -0
  4. package/dist/components/flyout/components/flyout-backdrop/flyout-backdrop.component.js +39 -0
  5. package/dist/components/flyout/components/flyout-backdrop/flyout-backdrop.styles.d.ts +34 -0
  6. package/dist/components/flyout/components/flyout-backdrop/flyout-backdrop.styles.js +16 -0
  7. package/dist/components/flyout/components/flyout-backdrop/flyout-backdrop.types.d.ts +30 -0
  8. package/dist/components/flyout/components/flyout-backdrop/flyout-backdrop.types.js +1 -0
  9. package/dist/components/flyout/components/flyout-backdrop/index.d.ts +2 -0
  10. package/dist/components/flyout/components/flyout-backdrop/index.js +1 -0
  11. package/dist/components/flyout/components/flyout-dialog/flyout-dialog.component.d.ts +5 -0
  12. package/dist/components/flyout/components/flyout-dialog/flyout-dialog.component.js +67 -0
  13. package/dist/components/flyout/components/flyout-dialog/flyout-dialog.styles.d.ts +82 -0
  14. package/dist/components/flyout/components/flyout-dialog/flyout-dialog.styles.js +66 -0
  15. package/dist/components/flyout/components/flyout-dialog/flyout-dialog.types.d.ts +40 -0
  16. package/dist/components/flyout/components/flyout-dialog/flyout-dialog.types.js +1 -0
  17. package/dist/components/flyout/components/flyout-dialog/index.d.ts +2 -0
  18. package/dist/components/flyout/components/flyout-dialog/index.js +1 -0
  19. package/dist/components/flyout/components/index.d.ts +2 -0
  20. package/dist/components/flyout/components/index.js +2 -0
  21. package/dist/components/flyout/flyout.component.d.ts +2 -0
  22. package/dist/components/flyout/flyout.component.js +26 -0
  23. package/dist/components/flyout/flyout.types.d.ts +7 -0
  24. package/dist/components/flyout/flyout.types.js +1 -0
  25. package/dist/components/flyout/index.d.ts +2 -0
  26. package/dist/components/flyout/index.js +1 -0
  27. package/dist/components/index.d.ts +1 -0
  28. package/dist/components/index.js +1 -0
  29. package/dist/components/input-group/input-group.component.js +2 -1
  30. package/dist/css/westpac-ui.css +43 -0
  31. package/dist/css/westpac-ui.min.css +43 -0
  32. package/package.json +4 -1
  33. package/src/components/flyout/components/flyout-backdrop/flyout-backdrop.component.tsx +49 -0
  34. package/src/components/flyout/components/flyout-backdrop/flyout-backdrop.styles.ts +17 -0
  35. package/src/components/flyout/components/flyout-backdrop/flyout-backdrop.types.ts +32 -0
  36. package/src/components/flyout/components/flyout-backdrop/index.ts +2 -0
  37. package/src/components/flyout/components/flyout-dialog/flyout-dialog.component.tsx +86 -0
  38. package/src/components/flyout/components/flyout-dialog/flyout-dialog.styles.ts +59 -0
  39. package/src/components/flyout/components/flyout-dialog/flyout-dialog.types.ts +44 -0
  40. package/src/components/flyout/components/flyout-dialog/index.ts +2 -0
  41. package/src/components/flyout/components/index.ts +2 -0
  42. package/src/components/flyout/flyout.component.tsx +53 -0
  43. package/src/components/flyout/flyout.types.ts +9 -0
  44. package/src/components/flyout/index.ts +2 -0
  45. package/src/components/index.ts +1 -0
  46. package/src/components/input-group/input-group.component.tsx +3 -2
@@ -769,6 +769,9 @@
769
769
  .h-8 {
770
770
  height: calc(0.375rem * 8);
771
771
  }
772
+ .h-9 {
773
+ height: calc(0.375rem * 9);
774
+ }
772
775
  .h-10 {
773
776
  height: calc(0.375rem * 10);
774
777
  }
@@ -781,6 +784,9 @@
781
784
  .h-30 {
782
785
  height: calc(0.375rem * 30);
783
786
  }
787
+ .h-50 {
788
+ height: calc(0.375rem * 50);
789
+ }
784
790
  .h-\[0\.625rem\] {
785
791
  height: 0.625rem;
786
792
  }
@@ -979,6 +985,12 @@
979
985
  .w-23 {
980
986
  width: calc(0.375rem * 23);
981
987
  }
988
+ .w-50 {
989
+ width: calc(0.375rem * 50);
990
+ }
991
+ .w-70 {
992
+ width: calc(0.375rem * 70);
993
+ }
982
994
  .w-\[1\.81ex\] {
983
995
  width: 1.81ex;
984
996
  }
@@ -1348,6 +1360,14 @@
1348
1360
  .shrink-0 {
1349
1361
  flex-shrink: 0;
1350
1362
  }
1363
+ .-translate-x-full {
1364
+ --tw-translate-x: -100%;
1365
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1366
+ }
1367
+ .translate-x-0 {
1368
+ --tw-translate-x: calc(0.375rem * 0);
1369
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1370
+ }
1351
1371
  .translate-x-\[-2\.5px\] {
1352
1372
  --tw-translate-x: -2.5px;
1353
1373
  translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1360,6 +1380,10 @@
1360
1380
  --tw-translate-x: 14px;
1361
1381
  translate: var(--tw-translate-x) var(--tw-translate-y);
1362
1382
  }
1383
+ .translate-x-full {
1384
+ --tw-translate-x: 100%;
1385
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1386
+ }
1363
1387
  .-translate-y-1\/2 {
1364
1388
  --tw-translate-y: calc(calc(1/2 * 100%) * -1);
1365
1389
  translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1542,6 +1566,13 @@
1542
1566
  margin-inline-end: calc(calc(0.375rem * 1) * calc(1 - var(--tw-space-x-reverse)));
1543
1567
  }
1544
1568
  }
1569
+ .space-x-4 {
1570
+ :where(& > :not(:last-child)) {
1571
+ --tw-space-x-reverse: 0;
1572
+ margin-inline-start: calc(calc(0.375rem * 4) * var(--tw-space-x-reverse));
1573
+ margin-inline-end: calc(calc(0.375rem * 4) * calc(1 - var(--tw-space-x-reverse)));
1574
+ }
1575
+ }
1545
1576
  .divide-y {
1546
1577
  :where(& > :not(:last-child)) {
1547
1578
  --tw-divide-y-reverse: 0;
@@ -3150,6 +3181,10 @@
3150
3181
  --tw-shadow: 0 0.375rem 0.75rem var(--tw-shadow-color, rgba(0,0,0,0.175));
3151
3182
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
3152
3183
  }
3184
+ .shadow-\[0_2px_5px_rgba\(0\,0\,0\,0\.3\)\] {
3185
+ --tw-shadow: 0 2px 5px var(--tw-shadow-color, rgba(0,0,0,0.3));
3186
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
3187
+ }
3153
3188
  .shadow-\[0_2px_12px_rgba\(0\,0\,0\,0\.2\)\] {
3154
3189
  --tw-shadow: 0 2px 12px var(--tw-shadow-color, rgba(0,0,0,0.2));
3155
3190
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -3309,6 +3344,10 @@
3309
3344
  --tw-duration: 300ms;
3310
3345
  transition-duration: 300ms;
3311
3346
  }
3347
+ .duration-500 {
3348
+ --tw-duration: 500ms;
3349
+ transition-duration: 500ms;
3350
+ }
3312
3351
  .duration-\[\.3s\] {
3313
3352
  --tw-duration: .3s;
3314
3353
  transition-duration: .3s;
@@ -3317,6 +3356,10 @@
3317
3356
  --tw-duration: .6s;
3318
3357
  transition-duration: .6s;
3319
3358
  }
3359
+ .ease-\[cubic-bezier\(0\.23\,1\,0\.32\,1\)\] {
3360
+ --tw-ease: cubic-bezier(0.23,1,0.32,1);
3361
+ transition-timing-function: cubic-bezier(0.23,1,0.32,1);
3362
+ }
3320
3363
  .ease-\[cubic-bezier\(0\.25\,0\.1\,0\.25\,1\)\] {
3321
3364
  --tw-ease: cubic-bezier(0.25,0.1,0.25,1);
3322
3365
  transition-timing-function: cubic-bezier(0.25,0.1,0.25,1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westpac/ui",
3
- "version": "1.14.0",
3
+ "version": "1.15.1",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -88,6 +88,9 @@
88
88
  "./flexi-cell": {
89
89
  "default": "./dist/components/flexi-cell/index.js"
90
90
  },
91
+ "./flyout": {
92
+ "default": "./dist/components/flyout/index.js"
93
+ },
91
94
  "./footer": {
92
95
  "default": "./dist/components/footer/index.js"
93
96
  },
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+
3
+ import { useExitAnimation } from '@react-aria/utils';
4
+ import React, { useMemo, useRef } from 'react';
5
+ import { Overlay, useModalOverlay } from 'react-aria';
6
+
7
+ import { styles as backdropStyles } from './flyout-backdrop.styles.js';
8
+ import { type FlyoutBackdropProps } from './flyout-backdrop.types.js';
9
+
10
+ /**
11
+ * @private
12
+ */
13
+ export function FlyoutBackdrop({
14
+ children,
15
+ className,
16
+ isDismissable = true,
17
+ portalContainer,
18
+ state,
19
+ zIndex = 100,
20
+ ...props
21
+ }: FlyoutBackdropProps) {
22
+ const ref = useRef<HTMLDivElement>(null);
23
+ const isExiting = useExitAnimation(ref, state.isOpen);
24
+ const styles = backdropStyles({ isOpen: state.isOpen });
25
+ const { modalProps, underlayProps } = useModalOverlay({ isDismissable, ...props }, state, ref);
26
+
27
+ // This is required so branding applies correctly by default due to portal location, can be overridden with portalContainer prop
28
+ const brandContainer = useMemo(() => {
29
+ if (typeof window !== 'undefined') {
30
+ return (
31
+ document.querySelector('[data-brand]') ||
32
+ document.querySelector('[class^="theme-"], [class*=" theme-"]') ||
33
+ document.body
34
+ );
35
+ }
36
+ }, []);
37
+
38
+ if (!state.isOpen && !isExiting) {
39
+ return null;
40
+ }
41
+
42
+ return (
43
+ <Overlay isExiting={isExiting} portalContainer={portalContainer || brandContainer}>
44
+ <div className={styles.base({ className })} style={{ zIndex }} {...underlayProps}>
45
+ {children({ flyoutRef: ref, modalProps })}
46
+ </div>
47
+ </Overlay>
48
+ );
49
+ }
@@ -0,0 +1,17 @@
1
+ import { tv } from 'tailwind-variants';
2
+
3
+ export const styles = tv({
4
+ slots: {
5
+ base: 'fixed inset-0',
6
+ },
7
+ variants: {
8
+ isOpen: {
9
+ true: {
10
+ base: 'animate-fadeIn bg-black/65',
11
+ },
12
+ false: {
13
+ base: 'pointer-events-none',
14
+ },
15
+ },
16
+ },
17
+ });
@@ -0,0 +1,32 @@
1
+ import { type ReactNode, type RefObject } from 'react';
2
+ import { type AriaModalOverlayProps, type useModalOverlay } from 'react-aria';
3
+ import { type OverlayTriggerState } from 'react-stately';
4
+
5
+ export type FlyoutBackdropRenderProps = {
6
+ flyoutRef: RefObject<HTMLDivElement>;
7
+ modalProps: ReturnType<typeof useModalOverlay>['modalProps'];
8
+ };
9
+
10
+ export type FlyoutBackdropProps = {
11
+ /**
12
+ * Function that renders the flyout dialog within the backdrop
13
+ */
14
+ children: (props: FlyoutBackdropRenderProps) => ReactNode;
15
+ /**
16
+ * Additional class name for the backdrop
17
+ */
18
+ className?: string;
19
+ /**
20
+ * Element where the backdrop will be rendered
21
+ */
22
+ portalContainer?: Element;
23
+ /**
24
+ * The flyout opening and closing state
25
+ */
26
+ state: OverlayTriggerState;
27
+ /**
28
+ * z-index of the backdrop
29
+ * @default 100
30
+ */
31
+ zIndex?: number;
32
+ } & AriaModalOverlayProps;
@@ -0,0 +1,2 @@
1
+ export { FlyoutBackdrop } from './flyout-backdrop.component.js';
2
+ export { type FlyoutBackdropProps, type FlyoutBackdropRenderProps } from './flyout-backdrop.types.js';
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import { useEnterAnimation } from '@react-aria/utils';
4
+ import { clsx } from 'clsx';
5
+ import React, { useEffect, useRef, useState } from 'react';
6
+ import { mergeProps, useDialog } from 'react-aria';
7
+
8
+ import { Button } from '../../../button/index.js';
9
+ import { CloseIcon } from '../../../icon/index.js';
10
+
11
+ import { styles as dialogStyles } from './flyout-dialog.styles.js';
12
+ import { type FlyoutDialogInternalProps } from './flyout-dialog.types.js';
13
+
14
+ /**
15
+ * @private
16
+ */
17
+ export function FlyoutDialog({
18
+ children,
19
+ className,
20
+ closeAssistiveText = 'Close flyout',
21
+ flyoutRef,
22
+ heading,
23
+ headingTag: HeadingTag = 'h2',
24
+ isOpen,
25
+ modalProps,
26
+ onClose,
27
+ position = 'right',
28
+ ...props
29
+ }: FlyoutDialogInternalProps) {
30
+ const isEntering = useEnterAnimation(flyoutRef);
31
+ const styles = dialogStyles({ isEntering, isOpen, position });
32
+ const { dialogProps, titleProps } = useDialog(props, flyoutRef);
33
+ const [scrolled, setScrolled] = useState<boolean>(false);
34
+ const listRef = useRef<HTMLDivElement>(null);
35
+
36
+ useEffect(() => {
37
+ const element = listRef.current;
38
+ if (!element) {
39
+ return;
40
+ }
41
+ const listener = () => {
42
+ const y = listRef.current?.scrollTop || 0;
43
+ setScrolled(y > 0);
44
+ };
45
+ listRef?.current?.addEventListener('scroll', listener);
46
+ return () => {
47
+ listRef?.current?.removeEventListener('scroll', listener);
48
+ };
49
+ }, []);
50
+
51
+ return (
52
+ <div
53
+ {...mergeProps(modalProps, dialogProps, props)}
54
+ aria-hidden={!isOpen || undefined}
55
+ className={styles.base({ className })}
56
+ ref={flyoutRef}
57
+ >
58
+ <div className={styles.content()}>
59
+ <div
60
+ className={clsx(styles.header(), {
61
+ 'shadow-[0_2px_5px_rgba(0,0,0,0.3)]': scrolled,
62
+ })}
63
+ >
64
+ {heading && (
65
+ <HeadingTag {...titleProps} className={styles.heading()}>
66
+ {heading}
67
+ </HeadingTag>
68
+ )}
69
+ {onClose && (
70
+ <Button
71
+ aria-label={closeAssistiveText}
72
+ className={styles.button()}
73
+ iconAfter={() => <CloseIcon color="primary" size="medium" aria-hidden />}
74
+ look="link"
75
+ onClick={onClose}
76
+ type="button"
77
+ />
78
+ )}
79
+ </div>
80
+ <div className={styles.body()} ref={listRef}>
81
+ {children}
82
+ </div>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,59 @@
1
+ import { tv } from 'tailwind-variants';
2
+
3
+ export const styles = tv({
4
+ slots: {
5
+ base: 'fixed inset-y-0 flex w-50 flex-col bg-background-white transition-transform duration-500 ease-[cubic-bezier(0.23,1,0.32,1)]',
6
+ body: 'min-h-0 flex-1 overflow-auto',
7
+ button: 'ml-auto shrink-0',
8
+ content: 'flex h-full flex-col',
9
+ header: 'flex h-9 w-full shrink-0 items-center pl-2',
10
+ heading: 'min-w-0 typography-body-10 font-bold text-text-body',
11
+ },
12
+ variants: {
13
+ isEntering: {
14
+ true: {},
15
+ },
16
+ isOpen: {
17
+ true: {
18
+ base: 'translate-x-0',
19
+ },
20
+ false: {
21
+ content: 'hidden',
22
+ },
23
+ },
24
+ position: {
25
+ left: {
26
+ base: 'left-0 border-r border-border-muted-soft',
27
+ },
28
+ right: {
29
+ base: 'right-0 border-l border-border-muted-soft',
30
+ },
31
+ },
32
+ },
33
+ compoundSlots: [
34
+ {
35
+ slots: ['base'],
36
+ isOpen: false,
37
+ position: 'left',
38
+ className: '-translate-x-full',
39
+ },
40
+ {
41
+ slots: ['base'],
42
+ isOpen: false,
43
+ position: 'right',
44
+ className: 'translate-x-full',
45
+ },
46
+ {
47
+ slots: ['base'],
48
+ isEntering: true,
49
+ position: 'left',
50
+ className: '-translate-x-full',
51
+ },
52
+ {
53
+ slots: ['base'],
54
+ isEntering: true,
55
+ position: 'right',
56
+ className: 'translate-x-full',
57
+ },
58
+ ],
59
+ });
@@ -0,0 +1,44 @@
1
+ import { type HTMLAttributes, type ReactNode, type RefObject } from 'react';
2
+ import { type AriaDialogProps, type useModalOverlay } from 'react-aria';
3
+ import { type VariantProps } from 'tailwind-variants';
4
+
5
+ import { styles } from './flyout-dialog.styles.js';
6
+
7
+ type Variants = VariantProps<typeof styles>;
8
+
9
+ export type FlyoutDialogProps = {
10
+ /**
11
+ * Flyout content
12
+ */
13
+ children?: ReactNode;
14
+ /**
15
+ * Assistive text for the close button
16
+ * @default Close flyout
17
+ */
18
+ closeAssistiveText?: string;
19
+ /**
20
+ * The heading of the flyout
21
+ */
22
+ heading?: ReactNode;
23
+ /**
24
+ * The flyout heading tag may be overridden for semantic reasons
25
+ * @default h2
26
+ */
27
+ headingTag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
28
+ /**
29
+ * Called when the close button is pressed
30
+ */
31
+ onClose?: () => void;
32
+ /**
33
+ * Side of the page from which the flyout opens
34
+ * @default right
35
+ */
36
+ position?: Variants['position'];
37
+ } & AriaDialogProps &
38
+ HTMLAttributes<HTMLDivElement>;
39
+
40
+ export type FlyoutDialogInternalProps = FlyoutDialogProps & {
41
+ flyoutRef: RefObject<HTMLDivElement>;
42
+ isOpen: boolean;
43
+ modalProps: ReturnType<typeof useModalOverlay>['modalProps'];
44
+ };
@@ -0,0 +1,2 @@
1
+ export { FlyoutDialog } from './flyout-dialog.component.js';
2
+ export { type FlyoutDialogProps } from './flyout-dialog.types.js';
@@ -0,0 +1,2 @@
1
+ export * from './flyout-backdrop/index.js';
2
+ export * from './flyout-dialog/index.js';
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { FlyoutBackdrop, FlyoutDialog } from './components/index.js';
6
+ import { type FlyoutProps } from './flyout.types.js';
7
+
8
+ export function Flyout({
9
+ backdropClassName,
10
+ children,
11
+ className,
12
+ closeAssistiveText,
13
+ heading,
14
+ headingTag,
15
+ isDismissable = true,
16
+ isKeyboardDismissDisabled,
17
+ portalContainer,
18
+ position = 'right',
19
+ shouldCloseOnInteractOutside,
20
+ state,
21
+ zIndex,
22
+ ...props
23
+ }: FlyoutProps) {
24
+ return (
25
+ <FlyoutBackdrop
26
+ className={backdropClassName}
27
+ isDismissable={isDismissable}
28
+ isKeyboardDismissDisabled={isKeyboardDismissDisabled}
29
+ portalContainer={portalContainer}
30
+ shouldCloseOnInteractOutside={shouldCloseOnInteractOutside}
31
+ state={state}
32
+ zIndex={zIndex}
33
+ >
34
+ {({ flyoutRef, modalProps }) => (
35
+ <FlyoutDialog
36
+ {...props}
37
+ className={className}
38
+ closeAssistiveText={closeAssistiveText}
39
+ flyoutRef={flyoutRef}
40
+ heading={heading}
41
+ headingTag={headingTag}
42
+ isOpen={state.isOpen}
43
+ modalProps={modalProps}
44
+ onClose={isDismissable ? () => state.close() : undefined}
45
+ position={position}
46
+ key={position}
47
+ >
48
+ {children}
49
+ </FlyoutDialog>
50
+ )}
51
+ </FlyoutBackdrop>
52
+ );
53
+ }
@@ -0,0 +1,9 @@
1
+ import { type FlyoutBackdropProps, type FlyoutDialogProps } from './components/index.js';
2
+
3
+ export type FlyoutProps = Omit<FlyoutBackdropProps, 'children' | 'className'> &
4
+ Omit<FlyoutDialogProps, 'onClose'> & {
5
+ /**
6
+ * Additional class name for the backdrop
7
+ */
8
+ backdropClassName?: string;
9
+ };
@@ -0,0 +1,2 @@
1
+ export { Flyout } from './flyout.component.js';
2
+ export { type FlyoutProps } from './flyout.types.js';
@@ -47,3 +47,4 @@ export * from './progress-indicator/index.js';
47
47
  export * from './header/index.js';
48
48
  export * from './footer/index.js';
49
49
  export * from './multi-select/index.js';
50
+ export * from './flyout/index.js';
@@ -10,6 +10,7 @@ import React, {
10
10
  useId,
11
11
  useMemo,
12
12
  } from 'react';
13
+ import { twMerge } from 'tailwind-merge';
13
14
 
14
15
  import { useBreakpoint } from '../../hook/breakpoints.hook.js';
15
16
  import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
@@ -94,14 +95,14 @@ export function InputGroup({
94
95
 
95
96
  const renderChildren = useCallback(() => {
96
97
  return Children.map<ReactNode, ReactNode>(children, child => {
97
- if (isValidElement(child)) {
98
+ if (isValidElement<{ className?: string }>(child)) {
98
99
  return cloneElement(child, {
99
100
  size: resolvedSize,
100
101
  id: propID || id,
101
102
  'aria-labelledby': ariaLabelledBy,
102
103
  'aria-describedby': ariaDescribedBy || ariaDescribedByValue,
103
104
  'aria-label': ariaLabel,
104
- className: 'focus:z-10', // for focus ring visibility
105
+ className: twMerge('focus:z-10', child.props.className), // for focus ring visibility
105
106
  ...(resolvedWidth !== 'full' ? { width: resolvedWidth } : {}),
106
107
  } as Partial<unknown> & Attributes);
107
108
  }