@westpac/ui 0.50.0 → 0.52.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 (84) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/component-type.json +1 -1
  3. package/dist/components/accordion/components/accordion-item/accordion-item.component.js +55 -25
  4. package/dist/components/accordion/components/accordion-item/accordion-item.styles.d.ts +45 -6
  5. package/dist/components/accordion/components/accordion-item/accordion-item.styles.js +19 -6
  6. package/dist/components/autocomplete/autocomplete.component.d.ts +1 -1
  7. package/dist/components/autocomplete/autocomplete.component.js +3 -2
  8. package/dist/components/autocomplete/autocomplete.types.d.ts +6 -0
  9. package/dist/components/badge/badge.styles.js +1 -1
  10. package/dist/components/bottom-sheet/components/bottom-sheet-dialog/bottom-sheet-dialog.styles.js +1 -1
  11. package/dist/components/footer/footer.component.js +4 -11
  12. package/dist/components/footer/footer.styles.d.ts +3 -21
  13. package/dist/components/footer/footer.styles.js +3 -9
  14. package/dist/components/header/header.component.d.ts +1 -1
  15. package/dist/components/header/header.component.js +13 -10
  16. package/dist/components/header/header.types.d.ts +7 -1
  17. package/dist/components/modal/components/modal-dialog/index.d.ts +1 -0
  18. package/dist/components/modal/index.d.ts +1 -0
  19. package/dist/components/popover/components/panel/panel.component.js +5 -4
  20. package/dist/components/select/components/index.d.ts +2 -0
  21. package/dist/components/select/components/index.js +2 -0
  22. package/dist/components/select/components/select-with-tooltip/select-with-tooltip.component.d.ts +13 -0
  23. package/dist/components/select/components/select-with-tooltip/select-with-tooltip.component.js +22 -0
  24. package/dist/components/select/components/select-with-tooltip/select-with-tooltip.styles.d.ts +56 -0
  25. package/dist/components/select/components/select-with-tooltip/select-with-tooltip.styles.js +30 -0
  26. package/dist/components/select/components/styled-select/styled-select.component.d.ts +13 -0
  27. package/dist/components/select/components/styled-select/styled-select.component.js +19 -0
  28. package/dist/components/select/components/styled-select/styled-select.styles.d.ts +104 -0
  29. package/dist/components/select/components/styled-select/styled-select.styles.js +46 -0
  30. package/dist/components/select/select.component.d.ts +1 -0
  31. package/dist/components/select/select.component.js +7 -14
  32. package/dist/components/select/select.styles.d.ts +180 -45
  33. package/dist/components/select/select.styles.js +60 -15
  34. package/dist/components/select/select.types.d.ts +6 -0
  35. package/dist/components/tooltip/components/tooltip-content/tooltip-content.component.d.ts +2 -0
  36. package/dist/components/tooltip/components/tooltip-content/tooltip-content.component.js +9 -0
  37. package/dist/components/tooltip/components/tooltip-content/tooltip-content.styles.d.ts +1 -0
  38. package/dist/components/tooltip/components/tooltip-content/tooltip-content.styles.js +4 -0
  39. package/dist/components/tooltip/components/tooltip-content/tooltip-content.types.d.ts +5 -0
  40. package/dist/components/tooltip/components/tooltip-content/tooltip-content.types.js +1 -0
  41. package/dist/components/tooltip/index.d.ts +1 -0
  42. package/dist/components/tooltip/index.js +1 -0
  43. package/dist/components/tooltip/tooltip.component.d.ts +2 -0
  44. package/dist/components/tooltip/tooltip.component.js +55 -0
  45. package/dist/components/tooltip/tooltip.styles.d.ts +1 -0
  46. package/dist/components/tooltip/tooltip.styles.js +4 -0
  47. package/dist/components/tooltip/tooltip.types.d.ts +7 -0
  48. package/dist/components/tooltip/tooltip.types.js +1 -0
  49. package/dist/css/westpac-ui.css +69 -36
  50. package/dist/css/westpac-ui.min.css +69 -36
  51. package/dist/hook/breakpoints.hook.js +59 -10
  52. package/dist/hook/index.d.ts +1 -0
  53. package/dist/hook/index.js +1 -0
  54. package/package.json +4 -3
  55. package/src/components/accordion/components/accordion-item/accordion-item.component.tsx +64 -28
  56. package/src/components/accordion/components/accordion-item/accordion-item.styles.ts +19 -6
  57. package/src/components/autocomplete/autocomplete.component.tsx +3 -1
  58. package/src/components/autocomplete/autocomplete.types.ts +6 -0
  59. package/src/components/badge/badge.styles.ts +1 -1
  60. package/src/components/bottom-sheet/components/bottom-sheet-dialog/bottom-sheet-dialog.styles.ts +1 -1
  61. package/src/components/footer/footer.component.tsx +8 -12
  62. package/src/components/footer/footer.styles.ts +2 -6
  63. package/src/components/header/header.component.tsx +17 -4
  64. package/src/components/header/header.types.ts +7 -1
  65. package/src/components/modal/components/modal-dialog/index.ts +1 -0
  66. package/src/components/modal/index.ts +1 -0
  67. package/src/components/popover/components/panel/panel.component.tsx +8 -5
  68. package/src/components/select/components/index.ts +2 -0
  69. package/src/components/select/components/select-with-tooltip/select-with-tooltip.component.tsx +31 -0
  70. package/src/components/select/components/select-with-tooltip/select-with-tooltip.styles.ts +26 -0
  71. package/src/components/select/components/styled-select/styled-select.component.tsx +25 -0
  72. package/src/components/select/components/styled-select/styled-select.styles.ts +42 -0
  73. package/src/components/select/select.component.tsx +9 -14
  74. package/src/components/select/select.styles.ts +18 -15
  75. package/src/components/select/select.types.ts +6 -0
  76. package/src/components/tooltip/components/tooltip-content/tooltip-content.component.tsx +12 -0
  77. package/src/components/tooltip/components/tooltip-content/tooltip-content.styles.ts +5 -0
  78. package/src/components/tooltip/components/tooltip-content/tooltip-content.types.ts +6 -0
  79. package/src/components/tooltip/index.ts +1 -0
  80. package/src/components/tooltip/tooltip.component.tsx +61 -0
  81. package/src/components/tooltip/tooltip.styles.ts +3 -0
  82. package/src/components/tooltip/tooltip.types.ts +8 -0
  83. package/src/hook/breakpoints.hook.ts +71 -11
  84. package/src/hook/index.ts +1 -0
@@ -1,21 +1,70 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect } from 'react';
2
+ import { create } from 'zustand';
2
3
  import { BREAKPOINTS } from '../tailwind/constants/index.js';
3
4
  function checkBreakpoint() {
5
+ if (typeof window === 'undefined') {
6
+ return 'initial';
7
+ }
4
8
  const breakpointsAsArray = Object.entries(BREAKPOINTS).reverse();
5
9
  const breakpoint = breakpointsAsArray.find(([, value])=>window.matchMedia(`(min-width: ${value})`).matches);
6
10
  return breakpoint ? breakpoint[0] : 'initial';
7
11
  }
12
+ const BREAKPOINTS_ENTRIES = Object.entries(BREAKPOINTS);
13
+ const BREAKPOINTS_MEDIA = BREAKPOINTS_ENTRIES.reduce((acc, [key, value], index)=>{
14
+ const finalValue = (()=>{
15
+ const nextBreakpoint = BREAKPOINTS_ENTRIES[index + 1];
16
+ if (nextBreakpoint) {
17
+ return `(min-width: ${value}) and (max-width: ${+nextBreakpoint[1].replace('px', '') - 1}px)`;
18
+ }
19
+ return `(min-width: ${value})`;
20
+ })();
21
+ return {
22
+ ...acc,
23
+ [key]: finalValue
24
+ };
25
+ }, {
26
+ initial: `(max-width: ${+BREAKPOINTS_ENTRIES[0][1].replace('px', '') - 1}px)`
27
+ });
28
+ const useBreakpointStore = create()((set, get)=>({
29
+ breakpoint: 'initial',
30
+ mediaQueryListeners: null,
31
+ initialised: false,
32
+ ensureInitialized: ()=>{
33
+ if (get().initialised) {
34
+ return;
35
+ }
36
+ const listeners = Object.entries(BREAKPOINTS_MEDIA).map(([label, query])=>{
37
+ const mq = window.matchMedia(query);
38
+ const listener = (e)=>{
39
+ if (e.matches) {
40
+ set({
41
+ breakpoint: label
42
+ });
43
+ }
44
+ };
45
+ mq.addEventListener('change', listener);
46
+ return {
47
+ mq,
48
+ listener
49
+ };
50
+ });
51
+ set({
52
+ mediaQueryListeners: listeners,
53
+ initialised: true,
54
+ breakpoint: checkBreakpoint()
55
+ });
56
+ },
57
+ removeListeners: ()=>{
58
+ var _get_mediaQueryListeners;
59
+ (_get_mediaQueryListeners = get().mediaQueryListeners) === null || _get_mediaQueryListeners === void 0 ? void 0 : _get_mediaQueryListeners.forEach(({ mq, listener })=>{
60
+ mq.removeEventListener('change', listener);
61
+ });
62
+ }
63
+ }));
8
64
  export function useBreakpoint() {
9
- const [breakpoint, setBreakpoint] = useState(checkBreakpoint());
65
+ const { breakpoint, ensureInitialized: initIfNotInitialised } = useBreakpointStore();
10
66
  useEffect(()=>{
11
- const listener = ()=>{
12
- const breakpoint = checkBreakpoint();
13
- setBreakpoint(breakpoint);
14
- };
15
- window.addEventListener('resize', listener);
16
- return ()=>{
17
- window.removeEventListener('resize', listener);
18
- };
67
+ initIfNotInitialised();
19
68
  }, []);
20
69
  return breakpoint;
21
70
  }
@@ -1 +1,2 @@
1
+ export * from './breakpoints.hook.js';
1
2
  export * from '../components/pagination/pagination.hooks.js';
@@ -1 +1,2 @@
1
+ export * from './breakpoints.hook.js';
1
2
  export * from '../components/pagination/pagination.hooks.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westpac/ui",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -255,8 +255,8 @@
255
255
  "typescript": "^5.5.4",
256
256
  "vite": "^7.0.5",
257
257
  "vitest": "^3.2.4",
258
- "@westpac/eslint-config": "~1.0.1",
259
258
  "@westpac/test-config": "~0.0.0",
259
+ "@westpac/eslint-config": "~1.0.1",
260
260
  "@westpac/ts-config": "~0.0.0"
261
261
  },
262
262
  "dependencies": {
@@ -270,7 +270,8 @@
270
270
  "lodash.throttle": "~4.1.1",
271
271
  "motion": "~12.23.12",
272
272
  "react-aria": "~3.41.1",
273
- "react-stately": "~3.39.0"
273
+ "react-stately": "~3.39.0",
274
+ "zustand": "~4.5.4"
274
275
  },
275
276
  "overrides": {
276
277
  "react-aria": {
@@ -1,8 +1,8 @@
1
1
  /* eslint-disable sonarjs/deprecation */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
3
3
  import { useAccordionItem } from '@react-aria/accordion';
4
- import { AnimatePresence, LazyMotion, m } from 'motion/react';
5
- import React, { useRef } from 'react';
4
+ import { LazyMotion, m, useAnimate } from 'motion/react';
5
+ import React, { useEffect, useRef, useState } from 'react';
6
6
  import { mergeProps, useFocusRing, useHover, useLocale } from 'react-aria';
7
7
 
8
8
  import { ArrowLeftIcon, ArrowRightIcon } from '../../../icon/index.js';
@@ -23,11 +23,58 @@ export function AccordionItem<T = HTMLElement>({
23
23
  const { state, item } = props;
24
24
  const { buttonProps, regionProps } = useAccordionItem<T>(props, state, ref);
25
25
  const { isFocusVisible, focusProps } = useFocusRing();
26
- const isOpen = state.expandedKeys.has(item.key);
26
+ const isOpenState = state.expandedKeys.has(item.key);
27
27
  const isDisabled = state.disabledKeys.has(item.key);
28
28
  const { hoverProps } = useHover({ isDisabled });
29
29
  const { direction } = useLocale();
30
- const styles = accordionItemStyles({ isOpen, isDisabled, look, isFocusVisible, rounded });
30
+ // Open/close animation needs to be done with useAnimate as AnimatePresence will unmount component
31
+ const [scope, animate] = useAnimate();
32
+ const [enableOpenStyle, setEnableOpenStyle] = useState(isOpenState); // styles for opening accordion
33
+ const [enableCloseStyle, setEnableCloseStyle] = useState(!isOpenState); // styles for closing accordion
34
+
35
+ const styles = accordionItemStyles({
36
+ isOpen: enableOpenStyle,
37
+ isClosed: enableCloseStyle,
38
+ isOpenState,
39
+ isDisabled,
40
+ look,
41
+ isFocusVisible,
42
+ rounded,
43
+ });
44
+
45
+ useEffect(() => {
46
+ // setEnableStyle here as opening animation isn't working correctly if done below
47
+ if (isOpenState) setEnableOpenStyle(true);
48
+
49
+ // animates opening accordion
50
+ if (enableOpenStyle) {
51
+ animate(
52
+ scope.current,
53
+ { height: 'auto' },
54
+ { duration: 0.3, ease: [0.25, 0.1, 0.25, 1.0], onComplete: () => setEnableCloseStyle(false) },
55
+ );
56
+ }
57
+
58
+ // animates closing accordion
59
+ if (!isOpenState) {
60
+ animate(
61
+ scope.current,
62
+ { height: '0px' },
63
+ {
64
+ duration: 0.3,
65
+ ease: [0.25, 0.1, 0.25, 1.0],
66
+ // set some styles after animation completes so content doesn't disappear on close
67
+ onPlay: () => {
68
+ setEnableCloseStyle(true);
69
+ },
70
+ onComplete: () => {
71
+ setEnableOpenStyle(false);
72
+ },
73
+ },
74
+ );
75
+ }
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, [isOpenState, enableOpenStyle]);
31
78
 
32
79
  return (
33
80
  <Tag className={styles.base({ className })}>
@@ -46,34 +93,23 @@ export function AccordionItem<T = HTMLElement>({
46
93
  </button>
47
94
  </h3>
48
95
  <div {...regionProps}>
96
+ {/* NOTE: Can potentially replace animation in the future with React 19.2 Activity and AnimateActivity from motion/react once fully released https://motion.dev/docs/react-animate-activity */}
49
97
  <LazyMotion features={loadAnimations}>
50
- <AnimatePresence initial={false}>
51
- <m.div
52
- className="overflow-hidden"
53
- initial={{
54
- height: 0,
55
- }}
56
- animate={{
57
- height: 'auto',
98
+ <m.div className={styles.motionContent()} initial={{ height: isOpenState ? 'auto' : 0 }} ref={scope}>
99
+ <div
100
+ className={styles.content()}
101
+ // TODO: Remove below with updated accordion that uses disclosure as the issue doesn't happen with that version
102
+ // Need to call stopPropagation here as some events from children are bubbling up and focusing the accordion i.e. inputs
103
+ onBlur={e => {
104
+ e.stopPropagation();
58
105
  }}
59
- exit={{
60
- height: 0,
106
+ onFocus={e => {
107
+ e.stopPropagation();
61
108
  }}
62
- transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1.0] }}
63
- key={`${item.index}-${isOpen}`}
64
109
  >
65
- <div
66
- className={styles.content()}
67
- // TODO: Remove below with updated accordion that uses disclosure as the issue doesn't happen with that version
68
- // Need to call stopPropagation here as some events from children are bubbling up and focusing the accordion i.e. inputs
69
- onBlur={e => {
70
- e.stopPropagation();
71
- }}
72
- >
73
- {item.props.children}
74
- </div>
75
- </m.div>
76
- </AnimatePresence>
110
+ {item.props.children}
111
+ </div>
112
+ </m.div>
77
113
  </LazyMotion>
78
114
  </div>
79
115
  </Tag>
@@ -7,7 +7,8 @@ export const styles = tv(
7
7
  itemHeader: 'typography-body-9 flex w-full flex-1 items-center justify-between px-3 py-2 group-first:border-t-0',
8
8
  headerTitleWrapper: 'flex-1 pr-2 text-left',
9
9
  indicator: 'size-3 rotate-90',
10
- content: 'hidden',
10
+ content: '',
11
+ motionContent: '',
11
12
  },
12
13
  variants: {
13
14
  look: {
@@ -19,13 +20,25 @@ export const styles = tv(
19
20
  'mb-[-1px] border-l-[0.375rem] border-r border-border bg-light shadow-[inset_0px_1px_0_0_var(--tw-shadow-color),inset_0_-1px_0_0_var(--tw-shadow-color)] !shadow-border transition-colors',
20
21
  },
21
22
  },
23
+ isOpenState: {
24
+ false: {
25
+ itemHeader: 'background-transition hover:bg-background',
26
+ },
27
+ },
22
28
  isOpen: {
23
29
  true: {
24
- content: 'block border-border p-3',
30
+ content: 'visible block border-border p-3',
25
31
  },
26
32
  false: {
27
- base: '',
28
- itemHeader: 'background-transition hover:bg-background',
33
+ content: 'hidden',
34
+ },
35
+ },
36
+ isClosed: {
37
+ true: {
38
+ motionContent: 'overflow-hidden',
39
+ },
40
+ false: {
41
+ motionContent: 'overflow-visible',
29
42
  },
30
43
  },
31
44
  isDisabled: {
@@ -45,13 +58,13 @@ export const styles = tv(
45
58
  compoundSlots: [
46
59
  {
47
60
  slots: ['indicator'],
48
- isOpen: true,
61
+ isOpenState: true,
49
62
  className: '-rotate-90',
50
63
  },
51
64
  {
52
65
  slots: ['itemHeader'],
53
66
  look: 'lego',
54
- isOpen: true,
67
+ isOpenState: true,
55
68
  className: 'border-l-hero',
56
69
  },
57
70
  {
@@ -41,13 +41,15 @@ function Autocomplete<T extends object>(
41
41
  className,
42
42
  width = 'full',
43
43
  loadingState,
44
+ comboBoxState,
44
45
  ...props
45
46
  }: AutocompleteProps<T>,
46
47
  ref: ForwardedRef<HTMLInputElement>,
47
48
  ) {
48
49
  // eslint-disable-next-line @typescript-eslint/unbound-method
49
50
  const { contains } = useFilter({ sensitivity: 'base' });
50
- const state = useComboBoxState({ isDisabled, ...props, defaultFilter: contains });
51
+ const internalState = useComboBoxState({ isDisabled, ...props, defaultFilter: contains });
52
+ const state = comboBoxState ?? internalState;
51
53
  const { isFocusVisible, focusProps } = useFocusRing();
52
54
  const { isFocusVisible: isInputFocusVisible, focusProps: inputFocusProps } = useFocusRing();
53
55
  const inputRef = React.useRef<HTMLInputElement>(null);
@@ -1,6 +1,7 @@
1
1
  import { type ComboBoxProps } from '@react-types/combobox';
2
2
  import { type AriaLabelingProps } from '@react-types/shared';
3
3
  import { type ReactNode } from 'react';
4
+ import { ComboBoxState } from 'react-stately';
4
5
  import { type VariantProps } from 'tailwind-variants';
5
6
 
6
7
  import { HintProps, InputProps } from '../index.js';
@@ -63,5 +64,10 @@ export type AutocompleteProps<T extends object> = {
63
64
  * Width of autocomplete
64
65
  */
65
66
  width?: InputProps['width'];
67
+ /**
68
+ * Pass through comboBox state from consuming component. If not specified,
69
+ * will be handled internally.
70
+ */
71
+ comboBoxState?: ComboBoxState<T>;
66
72
  } & ComboBoxProps<T> &
67
73
  AriaLabelingProps;
@@ -24,7 +24,7 @@ export const styles = tv(
24
24
  'warning-inverted': 'border-none bg-white text-warning',
25
25
  },
26
26
  type: {
27
- pill: 'typography-body-10 flex h-4 w-fit items-center rounded-xl px-[0.4375rem] font-medium leading-none',
27
+ pill: 'typography-body-10 flex h-4 min-w-4 w-fit items-center justify-center rounded-xl px-[0.4375rem] font-medium leading-none',
28
28
  default: 'h-[1.25rem] rounded-sm px-1 text-[0.75rem] leading-[1.125rem]',
29
29
  },
30
30
  soft: {
@@ -3,7 +3,7 @@ import { tv } from 'tailwind-variants';
3
3
  export const styles = tv(
4
4
  {
5
5
  slots: {
6
- base: 'flex max-h-screen max-w-full flex-1 flex-col overflow-hidden rounded-t-md bg-white shadow-sm md:w-[37.5rem] md:rounded-md',
6
+ base: 'relative flex max-h-screen max-w-full flex-1 flex-col overflow-hidden rounded-t-md bg-white shadow-sm md:w-[37.5rem] md:rounded-md',
7
7
  title: 'typography-body-7 p-7 pb-4 pt-9 font-bold text-text max-md:px-5',
8
8
  body: 'flex-1 overflow-auto px-7 pb-7 text-text max-md:px-5',
9
9
  closeBtn: 'absolute right-3 top-3 p-0',
@@ -3,7 +3,7 @@
3
3
  import React from 'react';
4
4
  import { useFocusRing } from 'react-aria';
5
5
 
6
- import { Grid, GridItem, VisuallyHidden } from '../index.js';
6
+ import { VisuallyHidden } from '../index.js';
7
7
  import {
8
8
  BOMMultibrandSmallLogo,
9
9
  BSAMultibrandSmallLogo,
@@ -54,18 +54,14 @@ export function Footer({
54
54
  return (
55
55
  <footer className={styles.base({ className })} {...props}>
56
56
  <div className={styles.wrapper()}>
57
- <Grid className={styles.topRow()}>
58
- <GridItem span={12}>{children}</GridItem>
59
- </Grid>
57
+ <div>{children}</div>
60
58
  {!hideLogo && (
61
- <Grid>
62
- <GridItem span={{ initial: 12, md: 1 }}>
63
- <a href={logoLink} className={styles.link()} {...focusProps}>
64
- {srOnlyText && <VisuallyHidden>{srOnlyText}</VisuallyHidden>}
65
- <Logo align="right" aria-label={logoAssistiveText} />
66
- </a>
67
- </GridItem>
68
- </Grid>
59
+ <div className={styles.logoWrapper()}>
60
+ <a href={logoLink} className={styles.link()} {...focusProps}>
61
+ {srOnlyText && <VisuallyHidden>{srOnlyText}</VisuallyHidden>}
62
+ <Logo align="right" aria-label={logoAssistiveText} />
63
+ </a>
64
+ </div>
69
65
  )}
70
66
  </div>
71
67
  </footer>
@@ -4,9 +4,9 @@ export const styles = tv(
4
4
  {
5
5
  slots: {
6
6
  base: 'relative overflow-hidden border-t border-t-border',
7
- wrapper: 'pt-3 max-md:px-2 max-md:pb-3 md:px-4 md:pb-4',
8
- topRow: '',
7
+ wrapper: 'pt-3 max-md:px-2 max-md:pb-3 md:px-4 md:pb-3',
9
8
  link: 'float-right block',
9
+ logoWrapper: 'flex justify-end',
10
10
  },
11
11
  variants: {
12
12
  offsetSidebar: {
@@ -18,10 +18,6 @@ export const styles = tv(
18
18
  isFocusVisible: {
19
19
  true: { link: 'focus-outline' },
20
20
  },
21
- hideLogo: {
22
- true: '',
23
- false: { topRow: 'max-md:mb-7 md:mb-4' },
24
- },
25
21
  },
26
22
  },
27
23
  { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
@@ -56,6 +56,7 @@ export function Header({
56
56
  brand,
57
57
  className,
58
58
  children,
59
+ disableLogoLink = false,
59
60
  fixed = false,
60
61
  fixedMaxWidth,
61
62
  isScrolled,
@@ -97,6 +98,13 @@ export function Header({
97
98
 
98
99
  const styles = headerStyles({ logoCenter, fixed, leftIcon, scrolled: isScrolled || scrolled });
99
100
 
101
+ const HeaderLogo = () => (
102
+ <>
103
+ <SmallLogo align={logoAlignment} aria-label={logoAssistiveText} className={styles.smallLogo()} />
104
+ <LargeLogo aria-label={logoAssistiveText} className={styles.largeLogo()} />
105
+ </>
106
+ );
107
+
100
108
  return (
101
109
  <header className={styles.base({ className })} {...props}>
102
110
  <div className={styles.inner()} style={{ maxWidth: fixed ? fixedMaxWidth : undefined }}>
@@ -115,10 +123,15 @@ export function Header({
115
123
  </div>
116
124
  )}
117
125
  {/* useFocusRing was causing this link to need two clicks to activate so focus-visible styling is used instead */}
118
- <a href={logoLink} target={anchorTarget} className={styles.logoLink()} onClick={logoOnClick}>
119
- <SmallLogo align={logoAlignment} aria-label={logoAssistiveText} className={styles.smallLogo()} />
120
- <LargeLogo aria-label={logoAssistiveText} className={styles.largeLogo()} />
121
- </a>
126
+ {disableLogoLink ? (
127
+ <div className={styles.logoLink()}>
128
+ <HeaderLogo />
129
+ </div>
130
+ ) : (
131
+ <a href={logoLink} target={anchorTarget} className={styles.logoLink()} onClick={logoOnClick}>
132
+ <HeaderLogo />
133
+ </a>
134
+ )}
122
135
  {children && <div className={styles.rightContent()}>{children}</div>}
123
136
  </div>
124
137
  </header>
@@ -12,6 +12,11 @@ export type HeaderProps = {
12
12
  * Icon for brand
13
13
  */
14
14
  brand: Exclude<BrandKey, 'btfg'>;
15
+ /**
16
+ * Removes anchor link from logo
17
+ * @default false
18
+ */
19
+ disableLogoLink?: boolean;
15
20
  /**
16
21
  * Enable fixed header
17
22
  */
@@ -46,7 +51,8 @@ export type HeaderProps = {
46
51
  */
47
52
  logoCenter?: boolean;
48
53
  /**
49
- * Link for logo
54
+ * Link for logo, can be disabled with disableLogoLink
55
+ * @default '#'
50
56
  */
51
57
  logoLink?: string;
52
58
  /**
@@ -1,2 +1,3 @@
1
1
  export { ModalDialog } from './modal-dialog.component.js';
2
2
  export { type ModalDialogProps } from './modal-dialog.types.js';
3
+ export { type ModalDialogFooterProps } from './components/modal-dialog-footer/modal-dialog-footer.types.js';
@@ -3,3 +3,4 @@ export { ModalDialogFooter as ModalFooter } from './components/modal-dialog/comp
3
3
 
4
4
  export { Modal } from './modal.component.js';
5
5
  export { type ModalProps } from './modal.types.js';
6
+ export { type ModalDialogFooterProps } from './components/index.js';
@@ -22,16 +22,19 @@ export function BasePanel({
22
22
  const popoverRef = useRef<HTMLDivElement>(null);
23
23
  const arrowRef = useRef<HTMLDivElement>(null);
24
24
  const { popoverPosition, arrowPosition } = usePanel({ state, placement, triggerRef, portal });
25
-
26
25
  const styles = panelStyles({ placement });
27
26
  return (
28
27
  <FocusScope autoFocus restoreFocus>
29
28
  <div style={popoverPosition} className={styles.popover()} test-id="popover" id={id} ref={popoverRef}>
30
29
  <div className={styles.content()}>
31
- <Tag tabIndex={0} className={styles.heading()}>
32
- {heading}
33
- </Tag>
34
- <div className={styles.body()}>{content}</div>
30
+ {heading && (
31
+ <Tag className={styles.heading()} tabIndex={0}>
32
+ {heading}
33
+ </Tag>
34
+ )}
35
+ <div className={styles.body()} tabIndex={0}>
36
+ {content}
37
+ </div>
35
38
  <Button
36
39
  look="link"
37
40
  size="small"
@@ -0,0 +1,2 @@
1
+ export { SelectWithTooltip } from './select-with-tooltip/select-with-tooltip.component.js';
2
+ export { StyledSelect } from './styled-select/styled-select.component.js';
@@ -0,0 +1,31 @@
1
+ import React, { ForwardedRef, forwardRef, useState } from 'react';
2
+
3
+ import { Tooltip } from '../../../tooltip/index.js';
4
+ import { SelectProps } from '../../select.types.js';
5
+ import { StyledSelect } from '../styled-select/styled-select.component.js';
6
+
7
+ import { styles } from './select-with-tooltip.styles.js';
8
+
9
+ function BaseSelectWithTooltip(
10
+ { width = 'auto', children, onChange, ...props }: SelectProps,
11
+ ref: ForwardedRef<HTMLSelectElement>,
12
+ ) {
13
+ const [selectedOption, setSelectedOption] = useState('');
14
+ return (
15
+ <Tooltip tooltip={selectedOption} className={styles({ width })}>
16
+ <StyledSelect
17
+ ref={ref}
18
+ onChange={e => {
19
+ setSelectedOption(e.target.options[e.target.selectedIndex].text);
20
+ onChange?.(e);
21
+ }}
22
+ width={width}
23
+ {...props}
24
+ >
25
+ {children}
26
+ </StyledSelect>
27
+ </Tooltip>
28
+ );
29
+ }
30
+
31
+ export const SelectWithTooltip = forwardRef<HTMLSelectElement, SelectProps>(BaseSelectWithTooltip);
@@ -0,0 +1,26 @@
1
+ import { tv } from 'tailwind-variants';
2
+
3
+ export const styles = tv(
4
+ {
5
+ base: '',
6
+ variants: {
7
+ width: {
8
+ auto: 'flex-1',
9
+ full: 'w-full flex-1',
10
+ 1: 'w-[1.81ex]',
11
+ 2: 'w-[3.62ex]',
12
+ 3: 'w-[5.43ex]',
13
+ 4: 'w-[7.24ex]',
14
+ 5: 'w-[9.05ex]',
15
+ 6: 'w-[10.86ex]',
16
+ 7: 'w-[12.67ex]',
17
+ 8: 'w-[14.48ex]',
18
+ 9: 'w-[16.29ex]',
19
+ 10: 'w-[18.1ex]',
20
+ 20: 'w-[36.2ex]',
21
+ 30: 'w-[54.3ex]',
22
+ },
23
+ },
24
+ },
25
+ { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
26
+ );
@@ -0,0 +1,25 @@
1
+ import React, { ForwardedRef, forwardRef } from 'react';
2
+ import { mergeProps, useFocusRing } from 'react-aria';
3
+
4
+ import { type SelectProps } from '../../select.types.js';
5
+
6
+ import { styles } from './styled-select.styles.js';
7
+
8
+ function BaseSelect(
9
+ { className, size = 'medium', invalid = false, width = 'auto', children, ...props }: SelectProps,
10
+ ref: ForwardedRef<HTMLSelectElement>,
11
+ ) {
12
+ const { isFocused, isFocusVisible, focusProps } = useFocusRing();
13
+
14
+ return (
15
+ <select
16
+ ref={ref}
17
+ className={styles({ className, size, invalid, isFocused, isFocusVisible, width })}
18
+ {...mergeProps(props, focusProps)}
19
+ >
20
+ {children}
21
+ </select>
22
+ );
23
+ }
24
+
25
+ export const StyledSelect = forwardRef<HTMLSelectElement, SelectProps>(BaseSelect);
@@ -0,0 +1,42 @@
1
+ import { tv } from 'tailwind-variants';
2
+
3
+ export const styles = tv(
4
+ {
5
+ base: 'form-control overflow-hidden overflow-ellipsis whitespace-nowrap bg-no-repeat select-caret disabled:form-control-disabled group-first/add-on-before:!rounded-l group-first/add-on-before:rounded-r-none group-first/add-on-before:!border-x group-last/add-on-after:!rounded-r group-last/add-on-after:rounded-l-none group-last/add-on-after:!border-x group-[.input-group-after]:rounded-r-none group-[.input-group-before]:rounded-l-none group-[.input-group-after]:border-r-0 group-[.input-group-before]:border-l-0',
6
+ variants: {
7
+ size: {
8
+ small: 'form-control-small bg-[right_0.5625rem_center] pr-[calc(0.5rem+14px+0.5625rem)]',
9
+ medium: 'form-control-medium bg-[right_0.75rem_center] pr-[calc(0.5rem+14px+0.75rem)]',
10
+ large: 'form-control-large bg-[right_0.9375rem_center] pr-[calc(0.5rem+14px+0.9375rem)]',
11
+ xlarge: 'form-control-xlarge bg-[right_1.125rem_center] pr-[calc(0.5rem+14px+1.125rem)]',
12
+ },
13
+ invalid: {
14
+ true: 'border-danger',
15
+ false: 'border-borderDark',
16
+ },
17
+ isFocusVisible: {
18
+ true: 'focus-outline',
19
+ },
20
+ isFocused: {
21
+ true: 'outline-none',
22
+ },
23
+ width: {
24
+ auto: 'flex-1',
25
+ full: 'w-full flex-1',
26
+ 1: 'box-content w-[1.81ex]',
27
+ 2: 'box-content w-[3.62ex]',
28
+ 3: 'box-content w-[5.43ex]',
29
+ 4: 'box-content w-[7.24ex]',
30
+ 5: 'box-content w-[9.05ex]',
31
+ 6: 'box-content w-[10.86ex]',
32
+ 7: 'box-content w-[12.67ex]',
33
+ 8: 'box-content w-[14.48ex]',
34
+ 9: 'box-content w-[16.29ex]',
35
+ 10: 'box-content w-[18.1ex]',
36
+ 20: 'box-content w-[36.2ex]',
37
+ 30: 'box-content w-[54.3ex]',
38
+ },
39
+ },
40
+ },
41
+ { responsiveVariants: ['xsl', 'sm', 'md', 'lg', 'xl'] },
42
+ );