@westpac/ui 0.53.2 → 0.55.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/component-type.json +1 -1
  3. package/dist/components/accordion/components/accordion-item/accordion-item.styles.js +9 -1
  4. package/dist/components/autocomplete/components/autocomplete-list-box/autocomplete-list-box.component.js +1 -1
  5. package/dist/components/autocomplete/components/autocomplete-popover/autocomplete-popover.component.js +1 -1
  6. package/dist/components/button/button.component.d.ts +1 -0
  7. package/dist/components/button/button.component.js +3 -2
  8. package/dist/components/button/button.styles.d.ts +9 -0
  9. package/dist/components/button/button.styles.js +12 -1
  10. package/dist/components/button/button.types.d.ts +5 -0
  11. package/dist/components/button-dropdown/button-dropdown.component.d.ts +1 -1
  12. package/dist/components/button-dropdown/button-dropdown.component.js +2 -2
  13. package/dist/components/button-dropdown/button-dropdown.types.d.ts +5 -0
  14. package/dist/components/button-group/button-group.component.js +1 -0
  15. package/dist/components/checkbox-group/checkbox-group.component.js +1 -0
  16. package/dist/components/header/header.component.js +2 -1
  17. package/dist/components/header/header.types.d.ts +2 -1
  18. package/dist/components/modal/components/modal-backdrop/modal-backdrop.component.d.ts +1 -1
  19. package/dist/components/modal/components/modal-backdrop/modal-backdrop.component.js +3 -3
  20. package/dist/components/modal/components/modal-backdrop/modal-backdrop.styles.d.ts +39 -20
  21. package/dist/components/modal/components/modal-backdrop/modal-backdrop.styles.js +52 -11
  22. package/dist/components/modal/components/modal-backdrop/modal-backdrop.types.d.ts +5 -0
  23. package/dist/components/modal/components/modal-dialog/components/modal-dialog-body/modal-dialog-body.component.js +8 -2
  24. package/dist/components/modal/components/modal-dialog/components/modal-dialog-body/modal-dialog-body.styles.d.ts +54 -0
  25. package/dist/components/modal/components/modal-dialog/components/modal-dialog-body/modal-dialog-body.styles.js +64 -4
  26. package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.js +9 -3
  27. package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.styles.d.ts +12 -0
  28. package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.styles.js +25 -8
  29. package/dist/components/modal/components/modal-dialog/modal-dialog.component.d.ts +1 -1
  30. package/dist/components/modal/components/modal-dialog/modal-dialog.component.js +49 -4
  31. package/dist/components/modal/components/modal-dialog/modal-dialog.styles.d.ts +27 -0
  32. package/dist/components/modal/components/modal-dialog/modal-dialog.styles.js +39 -6
  33. package/dist/components/modal/components/modal-dialog/modal-dialog.types.d.ts +33 -0
  34. package/dist/components/modal/modal.component.d.ts +1 -1
  35. package/dist/components/modal/modal.component.js +4 -2
  36. package/dist/components/popover/components/panel/panel.styles.js +1 -1
  37. package/dist/components/radio-group/radio-group.component.js +1 -0
  38. package/dist/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.js +1 -0
  39. package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.js +1 -0
  40. package/dist/css/westpac-ui.css +226 -25
  41. package/dist/css/westpac-ui.min.css +226 -25
  42. package/package.json +6 -6
  43. package/src/components/accordion/components/accordion-item/accordion-item.styles.ts +7 -1
  44. package/src/components/autocomplete/components/autocomplete-list-box/autocomplete-list-box.component.tsx +1 -1
  45. package/src/components/autocomplete/components/autocomplete-popover/autocomplete-popover.component.tsx +1 -4
  46. package/src/components/button/button.component.tsx +2 -0
  47. package/src/components/button/button.styles.ts +10 -1
  48. package/src/components/button/button.types.ts +5 -0
  49. package/src/components/button-dropdown/button-dropdown.component.tsx +2 -1
  50. package/src/components/button-dropdown/button-dropdown.types.ts +5 -0
  51. package/src/components/button-group/button-group.component.tsx +1 -0
  52. package/src/components/checkbox-group/checkbox-group.component.tsx +1 -0
  53. package/src/components/header/header.component.tsx +3 -1
  54. package/src/components/header/header.types.ts +2 -1
  55. package/src/components/modal/components/modal-backdrop/modal-backdrop.component.tsx +5 -2
  56. package/src/components/modal/components/modal-backdrop/modal-backdrop.styles.ts +35 -15
  57. package/src/components/modal/components/modal-backdrop/modal-backdrop.types.ts +5 -0
  58. package/src/components/modal/components/modal-dialog/components/modal-dialog-body/modal-dialog-body.component.tsx +7 -3
  59. package/src/components/modal/components/modal-dialog/components/modal-dialog-body/modal-dialog-body.styles.ts +45 -3
  60. package/src/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.tsx +8 -3
  61. package/src/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.styles.ts +22 -6
  62. package/src/components/modal/components/modal-dialog/modal-dialog.component.tsx +51 -5
  63. package/src/components/modal/components/modal-dialog/modal-dialog.styles.ts +28 -5
  64. package/src/components/modal/components/modal-dialog/modal-dialog.types.ts +33 -0
  65. package/src/components/modal/modal.component.tsx +4 -2
  66. package/src/components/popover/components/panel/panel.styles.ts +1 -1
  67. package/src/components/radio-group/radio-group.component.tsx +1 -0
  68. package/src/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.tsx +1 -0
  69. package/src/components/selector/components/selector-radio-group/selector-radio-group.component.tsx +1 -0
@@ -37,10 +37,7 @@ export function AutocompletePopover(props: AutocompletePopoverProps) {
37
37
  {...popoverProps}
38
38
  style={{ ...popoverProps.style, width: width ? `${width}px` : undefined }}
39
39
  ref={popoverRef}
40
- className={clsx(
41
- 'z-10 mt-1 max-h-[400px] overflow-auto rounded border border-border bg-white shadow-lg',
42
- className,
43
- )}
40
+ className={clsx('z-10 mt-1 max-h-[400px] rounded border border-border bg-white shadow-lg', className)}
44
41
  >
45
42
  {!isNonModal && <DismissButton onDismiss={() => state.close()} />}
46
43
  {children}
@@ -22,6 +22,7 @@ function BaseButton(
22
22
  iconColor,
23
23
  iconSize,
24
24
  children,
25
+ removeLinkPadding,
25
26
  ...props
26
27
  }: ButtonProps,
27
28
  ref: Ref<ButtonRef>,
@@ -36,6 +37,7 @@ function BaseButton(
36
37
  justify,
37
38
  isFocusVisible,
38
39
  hasChildren: !!children,
40
+ removeLinkPadding,
39
41
  });
40
42
 
41
43
  return (
@@ -33,7 +33,7 @@ export const styles = tv(
33
33
  },
34
34
  hero: { base: 'border border-hero bg-hero text-white hover:bg-hero-70 active:bg-hero-50' },
35
35
  faint: { base: 'border border-borderDark bg-light text-muted hover:bg-white active:bg-white' },
36
- link: { base: 'px-0 text-link underline' },
36
+ link: { base: 'text-link underline' },
37
37
  unstyled: { base: 'p-0 text-left' },
38
38
  },
39
39
  soft: {
@@ -53,6 +53,9 @@ export const styles = tv(
53
53
  true: { base: 'focus-outline' },
54
54
  false: { base: 'outline-none' },
55
55
  },
56
+ removeLinkPadding: {
57
+ true: '',
58
+ },
56
59
  },
57
60
  compoundSlots: [
58
61
  {
@@ -73,6 +76,12 @@ export const styles = tv(
73
76
  soft: true,
74
77
  className: 'hover:bg-light active:bg-light',
75
78
  },
79
+ {
80
+ slots: ['base'],
81
+ look: 'link',
82
+ removeLinkPadding: true,
83
+ className: 'px-0',
84
+ },
76
85
  {
77
86
  slots: ['iconBefore'],
78
87
  hasChildren: true,
@@ -54,6 +54,11 @@ export type ButtonProps = {
54
54
  * @default hero
55
55
  */
56
56
  look?: Variants['look'];
57
+ /**
58
+ * Removes horizontal padding from the 'link' look button
59
+ * @default false
60
+ */
61
+ removeLinkPadding?: boolean;
57
62
  /**
58
63
  * Size of the button
59
64
  * @default medium
@@ -25,6 +25,7 @@ export function ButtonDropdown({
25
25
  soft = false,
26
26
  block = false,
27
27
  dropDownIcon: Icon = DropDownIcon,
28
+ placement = 'bottom start',
28
29
  shouldFlip,
29
30
  }: ButtonDropdownProps) {
30
31
  const ref = useRef<HTMLButtonElement & HTMLAnchorElement & HTMLSpanElement & HTMLDivElement>(null);
@@ -73,7 +74,7 @@ export function ButtonDropdown({
73
74
  {state.isOpen && (
74
75
  <ButtonDropdownPanel
75
76
  className={styles.panel({ className })}
76
- placement="bottom start"
77
+ placement={placement}
77
78
  triggerRef={ref}
78
79
  state={state}
79
80
  block={block}
@@ -1,4 +1,5 @@
1
1
  import { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import { Placement } from 'react-aria';
2
3
  import { type VariantProps } from 'tailwind-variants';
3
4
 
4
5
  import { ButtonProps } from '../button/index.js';
@@ -26,6 +27,10 @@ export type ButtonDropdownProps = {
26
27
  * State of whether the Popover is open
27
28
  */
28
29
  open?: boolean;
30
+ /**
31
+ * placement of the popover
32
+ */
33
+ placement?: Placement;
29
34
  /**
30
35
  * Soft look button
31
36
  */
@@ -17,6 +17,7 @@ export const ButtonGroupContext = createContext<ButtonGroupContextState>({
17
17
  state: {
18
18
  // TODO: Remove deprecated name prop once React Aria removes it from RadioGroupState
19
19
  name: '',
20
+ defaultSelectedValue: null,
20
21
  isDisabled: false,
21
22
  isReadOnly: false,
22
23
  isRequired: false,
@@ -16,6 +16,7 @@ export const CheckboxGroupContext = createContext<CheckboxGroupContextState>({
16
16
  orientation: 'vertical',
17
17
  size: 'medium',
18
18
  state: {
19
+ defaultValue: [],
19
20
  value: [],
20
21
  isDisabled: false,
21
22
  isReadOnly: false,
@@ -105,6 +105,8 @@ export function Header({
105
105
  </>
106
106
  );
107
107
 
108
+ const defaultAssistiveText = leftIcon === 'arrow' ? 'Back' : 'Menu';
109
+
108
110
  return (
109
111
  <header className={styles.base({ className })} {...props}>
110
112
  <div className={styles.inner()} style={{ maxWidth: fixed ? fixedMaxWidth : undefined }}>
@@ -116,7 +118,7 @@ export function Header({
116
118
  iconAfter={ButtonIcon}
117
119
  iconSize={leftIcon === 'arrow' ? 'medium' : 'small'}
118
120
  onClick={leftOnClick}
119
- aria-label={leftAssistiveText}
121
+ aria-label={leftAssistiveText ?? defaultAssistiveText}
120
122
  className={styles.leftButton()}
121
123
  iconColor="text"
122
124
  />
@@ -31,7 +31,8 @@ export type HeaderProps = {
31
31
  */
32
32
  isScrolled?: boolean;
33
33
  /**
34
- * Visually hidden text for left button
34
+ * Aria-label for the arrow/hamburger button
35
+ * @default leftIcon === 'arrow' ? 'Back' : 'Menu'
35
36
  */
36
37
  leftAssistiveText?: string;
37
38
  /**
@@ -8,11 +8,14 @@ import { type ModalBackdropProps } from './modal-backdrop.types.js';
8
8
  /**
9
9
  * @private
10
10
  */
11
- export function ModalBackdrop({ zIndex = 100, portalContainer, size, ...props }: ModalBackdropProps) {
11
+ export function ModalBackdrop({ zIndex = 100, portalContainer, size, compact, ...props }: ModalBackdropProps) {
12
12
  const { children, state, className } = props;
13
13
 
14
14
  const ref = useRef(null);
15
- const styles = backdropStyles({ fullscreen: size === 'full', fluid: size === 'fluid' });
15
+ const styles = backdropStyles({
16
+ size,
17
+ compact: compact && (size === 'md' || size === 'lg'),
18
+ });
16
19
 
17
20
  const { modalProps, underlayProps } = useModalOverlay(props, state, ref);
18
21
 
@@ -1,21 +1,41 @@
1
1
  import { tv } from 'tailwind-variants';
2
2
 
3
- export const styles = tv({
4
- slots: {
5
- base: 'fixed inset-0 flex animate-fadeIn items-center justify-center overflow-y-auto bg-black/50 p-2',
6
- modal: 'relative top-3 z-10 h-fit w-full animate-fadeInDown',
7
- },
8
- variants: {
9
- fullscreen: {
10
- true: {
11
- modal: 'top-0 flex flex-1 flex-col',
12
- base: 'flex flex-col p-0',
13
- },
3
+ export const styles = tv(
4
+ {
5
+ slots: {
6
+ base: 'fixed inset-0 flex animate-fadeIn justify-center bg-black/50 px-4',
7
+ modal: 'relative top-[5vh] z-10 size-fit max-w-full animate-fadeInDown',
14
8
  },
15
- fluid: {
16
- true: {
17
- modal: 'px-2',
9
+ variants: {
10
+ size: {
11
+ fluid: { base: 'px-4' },
12
+ full: {
13
+ modal: '!top-0 flex w-full flex-1 flex-col p-0',
14
+ base: 'flex flex-col p-0',
15
+ },
16
+ lg: '',
17
+ md: '',
18
+ sm: '',
19
+ },
20
+ compact: {
21
+ true: '',
22
+ false: '',
18
23
  },
19
24
  },
25
+ compoundSlots: [
26
+ {
27
+ slots: ['base'],
28
+ size: ['sm', 'md', 'lg', 'fluid'],
29
+ compact: false,
30
+ className: 'overflow-y-auto',
31
+ },
32
+ {
33
+ slots: ['modal'],
34
+ size: ['sm', 'md', 'lg', 'fluid'],
35
+ compact: false,
36
+ className: 'pb-[5vh]',
37
+ },
38
+ ],
20
39
  },
21
- });
40
+ { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
41
+ );
@@ -12,6 +12,11 @@ export type ModalBackdropProps = {
12
12
  * Clasname
13
13
  */
14
14
  className?: string;
15
+ /**
16
+ * For medium and large sizes.
17
+ * Keeps entire modal in view by adding internal scrolling.
18
+ */
19
+ compact?: boolean;
15
20
  /**
16
21
  * Element where backdrop will be placed
17
22
  */
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import React from 'react';
2
4
 
3
5
  import { useModalDialogContext } from '../../modal-dialog.component.js';
@@ -6,10 +8,12 @@ import { styles as modalBodyStyles } from './modal-dialog-body.styles.js';
6
8
  import { type ModalDialogBodyProps } from './modal-dialog-body.types.js';
7
9
 
8
10
  export function ModalDialogBody({ className, children, ...props }: ModalDialogBodyProps) {
9
- const { size } = useModalDialogContext();
10
- const styles = modalBodyStyles({ size });
11
+ const { size, scrollingRef, canScroll, compact, footerPresent, scrollAtBottom } = useModalDialogContext();
12
+
13
+ const styles = modalBodyStyles({ size, canScroll, scrollAtBottom, compact, footerPresent });
14
+
11
15
  return (
12
- <div className={styles.base({ className })} {...props}>
16
+ <div className={styles.base({ className })} ref={scrollingRef} {...props}>
13
17
  {children}
14
18
  </div>
15
19
  );
@@ -2,16 +2,58 @@ import { tv } from 'tailwind-variants';
2
2
 
3
3
  export const styles = tv(
4
4
  {
5
- slots: { base: 'flex-1 overflow-auto' },
5
+ slots: { base: 'flex-1 transition-shadow delay-0 duration-200 ease-[ease]' },
6
6
  variants: {
7
7
  size: {
8
8
  full: { base: 'px-4 py-3' },
9
9
  lg: { base: 'px-12 pb-12' },
10
10
  md: { base: 'px-7 pb-7' },
11
- sm: { base: 'px-5 pb-7' },
12
- fluid: { base: 'px-5 pb-7' },
11
+ sm: { base: 'px-5 pb-5' },
12
+ fluid: { base: 'px-5 pb-5' },
13
+ },
14
+ canScroll: {
15
+ true: {
16
+ base: 'shadow-[0px_-4px_5px_-2px_inset] shadow-black/30',
17
+ },
18
+ },
19
+ scrollAtBottom: {
20
+ true: { base: 'shadow-[0_0_0_0_inset]' },
21
+ },
22
+ compact: {
23
+ true: '',
24
+ false: '',
25
+ },
26
+ footerPresent: {
27
+ true: '',
28
+ false: '',
13
29
  },
14
30
  },
31
+ compoundSlots: [
32
+ {
33
+ slots: ['base'],
34
+ size: ['lg'],
35
+ compact: true,
36
+ className: 'overflow-y-auto px-5 pb-3',
37
+ },
38
+ {
39
+ slots: ['base'],
40
+ size: ['md'],
41
+ compact: true,
42
+ className: 'overflow-y-auto px-5 pb-2',
43
+ },
44
+ {
45
+ slots: ['base'],
46
+ size: ['md'],
47
+ footerPresent: true,
48
+ className: 'pb-5',
49
+ },
50
+ {
51
+ slots: ['base'],
52
+ size: ['lg'],
53
+ footerPresent: true,
54
+ className: 'pb-6',
55
+ },
56
+ ],
15
57
  },
16
58
  { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
17
59
  );
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React from 'react';
3
+ import React, { useEffect } from 'react';
4
4
 
5
5
  import { Button } from '../../../../../button/index.js';
6
6
  import { useModalDialogContext } from '../../modal-dialog.component.js';
@@ -16,8 +16,13 @@ export function ModalDialogFooter({
16
16
  secondaryOnClick,
17
17
  ...props
18
18
  }: ModalDialogFooterProps) {
19
- const { size } = useModalDialogContext();
20
- const styles = modalFooterStyles({ size });
19
+ const { size, compact, setFooterPresent } = useModalDialogContext();
20
+ const styles = modalFooterStyles({ size, compact });
21
+
22
+ useEffect(() => {
23
+ setFooterPresent?.(true);
24
+ }, [setFooterPresent]);
25
+
21
26
  return (
22
27
  <div className={styles.base({ className })} {...props}>
23
28
  <Button look="primary" size="large" className={styles.primaryBtn()} onClick={primaryOnClick}>
@@ -2,16 +2,32 @@ import { tv } from 'tailwind-variants';
2
2
 
3
3
  export const styles = tv(
4
4
  {
5
- slots: { base: 'flex gap-1', primaryBtn: '', secondaryBtn: 'no-underline hover:underline' },
5
+ slots: {
6
+ base: 'flex gap-1 rounded-b-[3px] bg-white',
7
+ primaryBtn: '',
8
+ secondaryBtn: '',
9
+ },
6
10
  variants: {
7
11
  size: {
8
- full: { base: 'px-4 py-3' },
9
- lg: { base: '-mt-6 px-12 pb-12' },
10
- md: { base: '-mt-2 px-7 pb-7' },
11
- sm: { base: '-mt-2 flex-col px-5 pb-5' },
12
- fluid: { base: '-mt-2 px-5 pb-5 max-md:flex-col' },
12
+ full: { base: 'px-4 pb-3' },
13
+ lg: { base: 'px-12 pb-12' },
14
+ md: { base: 'px-7 pb-7' },
15
+ sm: { base: 'flex-col px-5 pb-5 ' },
16
+ fluid: { base: 'px-5 pb-5 max-md:flex-col' },
17
+ },
18
+ compact: {
19
+ true: '',
20
+ false: '',
13
21
  },
14
22
  },
23
+ compoundSlots: [
24
+ {
25
+ slots: ['base'],
26
+ size: ['lg', 'md'],
27
+ compact: true,
28
+ className: 'min-h-[90px] px-5 pb-5 pt-3',
29
+ },
30
+ ],
15
31
  },
16
32
  { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
17
33
  );
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, useContext, useRef } from 'react';
3
+ import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
4
4
  import { useDialog, useFocusRing } from 'react-aria';
5
5
 
6
6
  import { CloseIcon } from '../../../../components/icon/index.js';
@@ -12,19 +12,63 @@ import { ModalDialogContextValue, type ModalDialogProps } from './modal-dialog.t
12
12
 
13
13
  const ModalDialogContext = createContext<ModalDialogContextValue>({ size: 'md' });
14
14
 
15
+ const SCROLL_BUFFER = 10;
16
+
15
17
  export const useModalDialogContext = () => useContext(ModalDialogContext);
18
+
16
19
  /**
17
20
  * @private
18
21
  */
19
- export function ModalDialog({ className, body, onClose, size = 'md', ...props }: ModalDialogProps) {
22
+ export function ModalDialog({ className, body, onClose, size, compact, ...props }: ModalDialogProps) {
20
23
  const { children } = props;
21
24
  const { isFocusVisible, focusProps } = useFocusRing();
22
- const styles = dialogStyles({ size, isFocusVisible });
25
+ const [scrolled, setScrolled] = useState(false);
26
+ const [scrollAtBottom, setScrollAtBottom] = useState(false);
27
+ const styles = dialogStyles({ size, isFocusVisible, compact, scrolled });
28
+ const [canScroll, setCanScroll] = useState(false);
29
+ const [footerPresent, setFooterPresent] = useState<boolean>(false);
23
30
 
24
31
  const ref = useRef(null);
32
+ const bodyRef = useRef<HTMLDivElement>(null);
25
33
 
26
34
  const { dialogProps, titleProps } = useDialog(props, ref);
27
35
 
36
+ const handleScroll = useCallback(() => {
37
+ if (bodyRef?.current) {
38
+ const { scrollTop, scrollHeight, clientHeight } = bodyRef.current;
39
+ setScrolled(scrollTop > SCROLL_BUFFER);
40
+ setScrollAtBottom(scrollTop + clientHeight >= scrollHeight - SCROLL_BUFFER);
41
+ }
42
+ }, [bodyRef]);
43
+
44
+ useEffect(() => {
45
+ const bodyElement = bodyRef.current;
46
+
47
+ if (!bodyElement) {
48
+ setCanScroll(false);
49
+ return;
50
+ }
51
+
52
+ bodyElement.addEventListener('scroll', handleScroll);
53
+
54
+ const updateCanScroll = () => {
55
+ setCanScroll(bodyElement.scrollHeight > bodyElement.clientHeight);
56
+ };
57
+
58
+ updateCanScroll();
59
+
60
+ const resizeObserver = new ResizeObserver(() => {
61
+ updateCanScroll();
62
+ });
63
+ resizeObserver.observe(bodyElement);
64
+
65
+ return () => {
66
+ resizeObserver.disconnect();
67
+ bodyElement.removeEventListener('scroll', handleScroll);
68
+ };
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps
70
+ }, [bodyRef]);
71
+
28
72
  return (
29
73
  <div {...dialogProps} ref={ref} className={styles.base({ className })}>
30
74
  {onClose && (
@@ -37,12 +81,14 @@ export function ModalDialog({ className, body, onClose, size = 'md', ...props }:
37
81
  {props.title}
38
82
  </h3>
39
83
  )}
40
- <ModalDialogContext.Provider value={{ size }}>
84
+
85
+ <ModalDialogContext.Provider
86
+ value={{ size, scrollingRef: bodyRef, canScroll, compact, footerPresent, setFooterPresent, scrollAtBottom }}
87
+ >
41
88
  {body ? <ModalDialogBody>{children}</ModalDialogBody> : children}
42
89
  </ModalDialogContext.Provider>
43
90
  </div>
44
91
  );
45
92
  }
46
-
47
93
  ModalDialog.Body = ModalDialogBody;
48
94
  ModalDialog.Footer = ModalDialogFooter;
@@ -3,14 +3,14 @@ import { tv } from 'tailwind-variants';
3
3
  export const styles = tv(
4
4
  {
5
5
  slots: {
6
- base: 'relative mx-auto flex max-w-full flex-col overflow-hidden rounded bg-white text-text outline-none',
7
- title: 'typography-body-7 pb-4 pt-9 font-bold text-text',
6
+ base: 'relative flex max-h-full max-w-full flex-col rounded bg-white text-text outline-none',
7
+ title: 'typography-body-7 pb-4 pt-9 font-bold text-text transition-shadow delay-0 duration-200 ease-[ease]',
8
8
  close: 'absolute right-0 top-0 block p-3',
9
9
  },
10
10
  variants: {
11
11
  size: {
12
12
  full: {
13
- base: 'max-h-screen w-full flex-1',
13
+ base: 'h-screen max-h-screen w-full flex-1 rounded-none',
14
14
  close: 'p-2',
15
15
  title: 'px-4 py-3',
16
16
  },
@@ -20,14 +20,14 @@ export const styles = tv(
20
20
  },
21
21
  md: {
22
22
  base: 'w-[37.5rem]',
23
- title: 'px-7 ',
23
+ title: 'px-7',
24
24
  },
25
25
  sm: {
26
26
  base: 'w-[25rem]',
27
27
  title: 'px-5',
28
28
  },
29
29
  fluid: {
30
- base: 'w-full',
30
+ base: 'w-screen',
31
31
  title: 'px-5',
32
32
  },
33
33
  },
@@ -35,7 +35,30 @@ export const styles = tv(
35
35
  true: { close: '!outline-offset-[-3px] focus-outline' },
36
36
  false: { close: 'outline-none' },
37
37
  },
38
+ compact: {
39
+ true: '',
40
+ false: '',
41
+ },
42
+ scrolled: {
43
+ true: {
44
+ title: 'shadow-[0px_2px_5px_0px] shadow-black/30',
45
+ },
46
+ },
38
47
  },
48
+ compoundSlots: [
49
+ {
50
+ slots: ['base'],
51
+ size: ['md', 'lg'],
52
+ compact: true,
53
+ className: 'max-h-[80vh] overflow-hidden',
54
+ },
55
+ {
56
+ slots: ['title'],
57
+ size: ['lg', 'md'],
58
+ compact: true,
59
+ className: 'min-h-[90px] px-5 pb-4 pt-6',
60
+ },
61
+ ],
39
62
  },
40
63
  { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
41
64
  );
@@ -1,3 +1,4 @@
1
+ import { RefObject } from 'react';
1
2
  import { type AriaDialogProps } from 'react-aria';
2
3
  import { type VariantProps } from 'tailwind-variants';
3
4
 
@@ -18,6 +19,12 @@ export type ModalDialogProps = {
18
19
  * Additional className for Dialog
19
20
  */
20
21
  className?: string;
22
+ /**
23
+ * Alternate styling for **medium** and **large** sizes. Other sizes will continue to scroll within the backdrop.
24
+ *
25
+ * Keeps entire modal in view by adding internal scrolling and reducing internal padding.
26
+ */
27
+ compact?: boolean;
21
28
  /**
22
29
  * Full screen
23
30
  */
@@ -37,8 +44,34 @@ export type ModalDialogProps = {
37
44
  } & AriaDialogProps;
38
45
 
39
46
  export type ModalDialogContextValue = {
47
+ /**
48
+ * Whether container can scroll
49
+ */
50
+ canScroll?: boolean;
51
+ /**
52
+ * Alternate styling for **medium** and **large** sizes. Other sizes will continue to scroll within the backdrop.
53
+ *
54
+ * Keeps entire modal in view by adding internal scrolling and reducing internal padding.
55
+ */
56
+ compact?: boolean;
57
+ /**
58
+ * Whether footer is present for styling
59
+ */
60
+ footerPresent?: boolean;
61
+ /**
62
+ * Sets whether footer is present for styling
63
+ */
64
+ setFooterPresent?: (present: boolean) => void;
40
65
  /**
41
66
  * Size of dialog
42
67
  */
43
68
  size?: Variants['size'];
69
+ /**
70
+ * Whether scroll is at bottom of the scrollable area
71
+ */
72
+ scrollAtBottom?: boolean;
73
+ /**
74
+ * Ref to use for scrolling animations
75
+ */
76
+ scrollingRef?: RefObject<HTMLDivElement>;
44
77
  };
@@ -11,13 +11,14 @@ export function Modal({
11
11
  title,
12
12
  role,
13
13
  body,
14
- size,
14
+ size = 'md',
15
15
  className,
16
16
  fullscreen,
17
+ compact = false,
17
18
  ...props
18
19
  }: ModalProps) {
19
20
  return (
20
- <ModalBackdrop size={size} className={backdropStyle} {...props}>
21
+ <ModalBackdrop size={size} className={backdropStyle} compact={compact} {...props}>
21
22
  <ModalDialog
22
23
  fullscreen={fullscreen}
23
24
  onClose={props.isDismissable ? () => props.state.close() : undefined}
@@ -26,6 +27,7 @@ export function Modal({
26
27
  body={body}
27
28
  size={size}
28
29
  className={className}
30
+ compact={compact}
29
31
  >
30
32
  {children}
31
33
  </ModalDialog>
@@ -10,7 +10,7 @@ export const styles = tv(
10
10
  after:left-[1.5px] after:top-0 after:size-0 after:border-x-[6.5px] after:border-t-[11px] after:border-x-[transparent] after:border-t-white
11
11
  `,
12
12
  closeBtn: 'absolute right-1 top-1 h-3 p-0 hover:opacity-80',
13
- content: 'w-[17.625rem] rounded-[3px] bg-white py-4 pl-3 pr-5',
13
+ content: 'w-[18.75rem] rounded-[3px] bg-white py-4 pl-3 pr-5',
14
14
  heading: 'typography-body-9 mb-2 font-medium text-text focus-visible:focus-outline',
15
15
  body: 'typography-body-10 text-text focus-visible:focus-outline',
16
16
  },
@@ -17,6 +17,7 @@ export const RadioGroupContext = createContext<RadioGroupContextState>({
17
17
  size: 'medium',
18
18
  state: {
19
19
  name: '',
20
+ defaultSelectedValue: null,
20
21
  isDisabled: false,
21
22
  isReadOnly: false,
22
23
  isRequired: false,
@@ -15,6 +15,7 @@ import {
15
15
 
16
16
  export const SelectorCheckboxGroupContext = createContext<SelectorCheckboxGroupContextState>({
17
17
  value: [],
18
+ defaultValue: [],
18
19
  isDisabled: false,
19
20
  isReadOnly: false,
20
21
  isSelected: () => false,
@@ -15,6 +15,7 @@ export const SelectorRadioGroupContext = createContext<SelectorRadioGroupContext
15
15
  state: {
16
16
  // TODO: Remove deprecated name prop once React Aria removes it from RadioGroupState
17
17
  name: '',
18
+ defaultSelectedValue: null,
18
19
  isDisabled: false,
19
20
  isReadOnly: false,
20
21
  isRequired: false,