@westpac/ui 0.59.0 → 0.59.2

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.
@@ -4266,20 +4266,16 @@ body {
4266
4266
  --tw-translate-x: 50%;
4267
4267
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
4268
4268
  }
4269
- .translate-x-\[-6px\] {
4270
- --tw-translate-x: -6px;
4269
+ .translate-x-\[-2\.5px\] {
4270
+ --tw-translate-x: -2.5px;
4271
4271
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
4272
4272
  }
4273
4273
  .translate-x-\[0\.125rem\] {
4274
4274
  --tw-translate-x: 0.125rem;
4275
4275
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
4276
4276
  }
4277
- .translate-x-\[6px\] {
4278
- --tw-translate-x: 6px;
4279
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
4280
- }
4281
- .translate-y-0 {
4282
- --tw-translate-y: 0rem;
4277
+ .translate-x-\[14px\] {
4278
+ --tw-translate-x: 14px;
4283
4279
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
4284
4280
  }
4285
4281
  .-rotate-45 {
@@ -5990,6 +5986,9 @@ body {
5990
5986
  .pb-\[2\.75rem\] {
5991
5987
  padding-bottom: 2.75rem;
5992
5988
  }
5989
+ .pb-\[200px\] {
5990
+ padding-bottom: 200px;
5991
+ }
5993
5992
  .pb-\[5vh\] {
5994
5993
  padding-bottom: 5vh;
5995
5994
  }
@@ -7972,10 +7971,6 @@ body {
7972
7971
  content: var(--tw-content);
7973
7972
  bottom: 1.5rem;
7974
7973
  }
7975
- .after\:bottom-\[1px\]::after {
7976
- content: var(--tw-content);
7977
- bottom: 1px;
7978
- }
7979
7974
  .after\:left-0::after {
7980
7975
  content: var(--tw-content);
7981
7976
  left: 0rem;
@@ -9368,20 +9363,16 @@ body {
9368
9363
  --tw-translate-x: 50%;
9369
9364
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
9370
9365
  }
9371
- .xsl\:translate-x-\[-6px\] {
9372
- --tw-translate-x: -6px;
9366
+ .xsl\:translate-x-\[-2\.5px\] {
9367
+ --tw-translate-x: -2.5px;
9373
9368
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
9374
9369
  }
9375
9370
  .xsl\:translate-x-\[0\.125rem\] {
9376
9371
  --tw-translate-x: 0.125rem;
9377
9372
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
9378
9373
  }
9379
- .xsl\:translate-x-\[6px\] {
9380
- --tw-translate-x: 6px;
9381
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
9382
- }
9383
- .xsl\:translate-y-0 {
9384
- --tw-translate-y: 0rem;
9374
+ .xsl\:translate-x-\[14px\] {
9375
+ --tw-translate-x: 14px;
9385
9376
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
9386
9377
  }
9387
9378
  .xsl\:-rotate-45 {
@@ -10215,10 +10206,6 @@ body {
10215
10206
  top: 1.25rem;
10216
10207
  bottom: 1.25rem;
10217
10208
  }
10218
- .xsl\:after\:bottom-\[1px\]::after {
10219
- content: var(--tw-content);
10220
- bottom: 1px;
10221
- }
10222
10209
  .xsl\:after\:left-1::after {
10223
10210
  content: var(--tw-content);
10224
10211
  left: 0.375rem;
@@ -11132,20 +11119,16 @@ body {
11132
11119
  --tw-translate-x: 50%;
11133
11120
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
11134
11121
  }
11135
- .sm\:translate-x-\[-6px\] {
11136
- --tw-translate-x: -6px;
11122
+ .sm\:translate-x-\[-2\.5px\] {
11123
+ --tw-translate-x: -2.5px;
11137
11124
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
11138
11125
  }
11139
11126
  .sm\:translate-x-\[0\.125rem\] {
11140
11127
  --tw-translate-x: 0.125rem;
11141
11128
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
11142
11129
  }
11143
- .sm\:translate-x-\[6px\] {
11144
- --tw-translate-x: 6px;
11145
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
11146
- }
11147
- .sm\:translate-y-0 {
11148
- --tw-translate-y: 0rem;
11130
+ .sm\:translate-x-\[14px\] {
11131
+ --tw-translate-x: 14px;
11149
11132
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
11150
11133
  }
11151
11134
  .sm\:-rotate-45 {
@@ -12002,10 +11985,6 @@ body {
12002
11985
  top: 1.25rem;
12003
11986
  bottom: 1.25rem;
12004
11987
  }
12005
- .sm\:after\:bottom-\[1px\]::after {
12006
- content: var(--tw-content);
12007
- bottom: 1px;
12008
- }
12009
11988
  .sm\:after\:left-1::after {
12010
11989
  content: var(--tw-content);
12011
11990
  left: 0.375rem;
@@ -12981,20 +12960,16 @@ body {
12981
12960
  --tw-translate-x: 50%;
12982
12961
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
12983
12962
  }
12984
- .md\:translate-x-\[-6px\] {
12985
- --tw-translate-x: -6px;
12963
+ .md\:translate-x-\[-2\.5px\] {
12964
+ --tw-translate-x: -2.5px;
12986
12965
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
12987
12966
  }
12988
12967
  .md\:translate-x-\[0\.125rem\] {
12989
12968
  --tw-translate-x: 0.125rem;
12990
12969
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
12991
12970
  }
12992
- .md\:translate-x-\[6px\] {
12993
- --tw-translate-x: 6px;
12994
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
12995
- }
12996
- .md\:translate-y-0 {
12997
- --tw-translate-y: 0rem;
12971
+ .md\:translate-x-\[14px\] {
12972
+ --tw-translate-x: 14px;
12998
12973
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
12999
12974
  }
13000
12975
  .md\:-rotate-45 {
@@ -13859,10 +13834,6 @@ body {
13859
13834
  top: 1.25rem;
13860
13835
  bottom: 1.25rem;
13861
13836
  }
13862
- .md\:after\:bottom-\[1px\]::after {
13863
- content: var(--tw-content);
13864
- bottom: 1px;
13865
- }
13866
13837
  .md\:after\:left-1::after {
13867
13838
  content: var(--tw-content);
13868
13839
  left: 0.375rem;
@@ -14829,20 +14800,16 @@ body {
14829
14800
  --tw-translate-x: 50%;
14830
14801
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
14831
14802
  }
14832
- .lg\:translate-x-\[-6px\] {
14833
- --tw-translate-x: -6px;
14803
+ .lg\:translate-x-\[-2\.5px\] {
14804
+ --tw-translate-x: -2.5px;
14834
14805
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
14835
14806
  }
14836
14807
  .lg\:translate-x-\[0\.125rem\] {
14837
14808
  --tw-translate-x: 0.125rem;
14838
14809
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
14839
14810
  }
14840
- .lg\:translate-x-\[6px\] {
14841
- --tw-translate-x: 6px;
14842
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
14843
- }
14844
- .lg\:translate-y-0 {
14845
- --tw-translate-y: 0rem;
14811
+ .lg\:translate-x-\[14px\] {
14812
+ --tw-translate-x: 14px;
14846
14813
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
14847
14814
  }
14848
14815
  .lg\:-rotate-45 {
@@ -15688,10 +15655,6 @@ body {
15688
15655
  top: 1.25rem;
15689
15656
  bottom: 1.25rem;
15690
15657
  }
15691
- .lg\:after\:bottom-\[1px\]::after {
15692
- content: var(--tw-content);
15693
- bottom: 1px;
15694
- }
15695
15658
  .lg\:after\:left-1::after {
15696
15659
  content: var(--tw-content);
15697
15660
  left: 0.375rem;
@@ -16604,20 +16567,16 @@ body {
16604
16567
  --tw-translate-x: 50%;
16605
16568
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
16606
16569
  }
16607
- .xl\:translate-x-\[-6px\] {
16608
- --tw-translate-x: -6px;
16570
+ .xl\:translate-x-\[-2\.5px\] {
16571
+ --tw-translate-x: -2.5px;
16609
16572
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
16610
16573
  }
16611
16574
  .xl\:translate-x-\[0\.125rem\] {
16612
16575
  --tw-translate-x: 0.125rem;
16613
16576
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
16614
16577
  }
16615
- .xl\:translate-x-\[6px\] {
16616
- --tw-translate-x: 6px;
16617
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
16618
- }
16619
- .xl\:translate-y-0 {
16620
- --tw-translate-y: 0rem;
16578
+ .xl\:translate-x-\[14px\] {
16579
+ --tw-translate-x: 14px;
16621
16580
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
16622
16581
  }
16623
16582
  .xl\:-rotate-45 {
@@ -17445,10 +17404,6 @@ body {
17445
17404
  top: 1.25rem;
17446
17405
  bottom: 1.25rem;
17447
17406
  }
17448
- .xl\:after\:bottom-\[1px\]::after {
17449
- content: var(--tw-content);
17450
- bottom: 1px;
17451
- }
17452
17407
  .xl\:after\:left-1::after {
17453
17408
  content: var(--tw-content);
17454
17409
  left: 0.375rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westpac/ui",
3
- "version": "0.59.0",
3
+ "version": "0.59.2",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -23,6 +23,7 @@ function BaseButton(
23
23
  iconSize,
24
24
  children,
25
25
  removeLinkPadding,
26
+ type = Tag === 'button' ? 'button' : undefined,
26
27
  ...props
27
28
  }: ButtonProps,
28
29
  ref: Ref<ButtonRef>,
@@ -41,7 +42,7 @@ function BaseButton(
41
42
  });
42
43
 
43
44
  return (
44
- <Tag ref={ref} className={styles.base({ className })} {...mergeProps(props, focusProps)}>
45
+ <Tag ref={ref} className={styles.base({ className })} {...mergeProps(props, focusProps)} type={type}>
45
46
  {IconBefore && (
46
47
  <IconBefore look={iconLook} size={btnIconSize} className={styles.iconBefore()} color={iconColor} aria-hidden />
47
48
  )}
@@ -25,6 +25,8 @@ export function Compacta({
25
25
  titleTag: Tag = 'h3',
26
26
  addText = 'Add another',
27
27
  initialCompactas,
28
+ onAdd,
29
+ onRemove,
28
30
  ...props
29
31
  }: CompactaProps) {
30
32
  const [initial, setInitial] = useState(true);
@@ -64,11 +66,16 @@ export function Compacta({
64
66
  ]);
65
67
 
66
68
  setAction({ type: 'add', index: newItems.length });
69
+
70
+ onAdd?.();
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
72
  }, [id, items]);
68
73
 
69
74
  const handleRemove = useCallback((id: string, index: number) => {
70
75
  setItems(items => items.filter(item => item.id !== id));
71
76
  setAction({ type: 'remove', index, id });
77
+ onRemove?.(id, index);
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
79
  }, []);
73
80
 
74
81
  const handleToggle = useCallback(
@@ -125,7 +132,7 @@ export function Compacta({
125
132
  const itemId = item.id ?? `${id}-${generateID()}`;
126
133
  return {
127
134
  id: itemId,
128
- open: index === initialCompactas.length - 1 ? true : false,
135
+ open: item.open ? item.open : index === initialCompactas.length - 1,
129
136
  delay: false,
130
137
  title: {
131
138
  primary: item.title?.primary ?? '',
@@ -198,6 +205,7 @@ export function Compacta({
198
205
  <div className={styles.content()} id={`gel-compacta-content-${item.id}`}>
199
206
  {children({
200
207
  id: item.id,
208
+ index,
201
209
  setPrimaryTitle: (title: string) => setTitle(item.id, 'primary', title),
202
210
  setSecondaryTitle: (title: string) => setTitle(item.id, 'secondary', title),
203
211
  setTertiaryTitle: (title: string) => setTitle(item.id, 'tertiary', title),
@@ -16,9 +16,19 @@ export type CompactaProps = {
16
16
  initialCompactas?: {
17
17
  // Compacta id
18
18
  id?: string;
19
+ // If compacta should be open on initial render
20
+ open?: boolean;
19
21
  // Titles to pre-fill, won't be done automatically
20
22
  title?: { primary?: string; secondary?: string; tertiary?: string };
21
23
  }[];
24
+ /**
25
+ * Callback when a compacta is added
26
+ */
27
+ onAdd?: () => void;
28
+ /**
29
+ * Callback when a compacta is removed, provides the id and index of the removed compacta
30
+ */
31
+ onRemove?: (id: string, index: number) => void;
22
32
  /**
23
33
  * Tag for primary title
24
34
  * @default h3
@@ -28,6 +38,7 @@ export type CompactaProps = {
28
38
 
29
39
  type ContentProps = {
30
40
  id: string;
41
+ index: number;
31
42
  setPrimaryTitle: (title: string) => unknown;
32
43
  setSecondaryTitle: (title: string) => unknown;
33
44
  setTertiaryTitle: (title: string) => unknown;
@@ -14,15 +14,21 @@ export function BasePanel({
14
14
  heading,
15
15
  headingTag: Tag = 'h1',
16
16
  content,
17
- placement = 'bottom',
17
+ placement = 'top',
18
18
  id,
19
19
  triggerRef,
20
20
  portal,
21
21
  }: PanelProps) {
22
22
  const popoverRef = useRef<HTMLDivElement>(null);
23
23
  const arrowRef = useRef<HTMLDivElement>(null);
24
- const { popoverPosition, arrowPosition } = usePanel({ state, placement, triggerRef, portal });
25
- const styles = panelStyles({ placement });
24
+ const { popoverPosition, arrowPosition, localPlacement } = usePanel({
25
+ state,
26
+ placement,
27
+ triggerRef,
28
+ portal,
29
+ popoverRef,
30
+ });
31
+ const styles = panelStyles({ placement: localPlacement });
26
32
  return (
27
33
  <FocusScope autoFocus restoreFocus>
28
34
  <div style={popoverPosition} className={styles.popover()} test-id="popover" id={id} ref={popoverRef}>
@@ -1,8 +1,9 @@
1
- import { RefObject, useEffect, useMemo, useState } from 'react';
1
+ import { RefObject, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
2
2
 
3
3
  import { PanelProps } from './panel.types.js';
4
4
 
5
5
  const PANEL_WIDTH_SIZE = 300;
6
+ const ARROW_HEIGHT = 7; // the border-x value for the before pseudo element in the styles, 2*ARROW_HEIGHT gives the width of the arrow
6
7
 
7
8
  const getHorizontalPositionPopover = (element: HTMLDivElement, screenWidth: number) => {
8
9
  const triggerDOMRect = element.getBoundingClientRect();
@@ -29,10 +30,18 @@ const getLeftOffsetPerHorizontalPosition = (element: HTMLDivElement, screenWidth
29
30
  case 'right':
30
31
  // For smaller screens, if adjusting for right makes it go off screen on the left, adjust it to have a 16px space from the edge
31
32
  if (triggerDOMRect.left + rightOffset <= 0) {
33
+ // For extra small screens extra adjustment is needed e.g. <320px wide
34
+ if (triggerDOMRect.left + rightOffset - PANEL_WIDTH_SIZE <= -screenWidth) {
35
+ return rightOffset - (triggerDOMRect.left + rightOffset) + (screenWidth - PANEL_WIDTH_SIZE) / 2;
36
+ }
32
37
  return rightOffset - (triggerDOMRect.left + rightOffset) + 16;
33
38
  }
34
39
  return rightOffset;
35
40
  default:
41
+ // For extra small screens extra adjustment is needed e.g. <320px wide
42
+ if (triggerDOMRect?.left + PANEL_WIDTH_SIZE >= screenWidth) {
43
+ return -(triggerDOMRect?.left || 0) + (screenWidth - PANEL_WIDTH_SIZE) / 2;
44
+ }
36
45
  return 0;
37
46
  }
38
47
  };
@@ -51,15 +60,62 @@ export type PanelHookProps = {
51
60
  * @returns {Object} return.popoverPosition - The calculated position styles for the popover.
52
61
  * @returns {Object} return.arrowPosition - The calculated position styles for the popover arrow.
53
62
  */
54
- export function usePanel({ state, placement = 'bottom', triggerRef, portal }: PanelHookProps) {
63
+ export function usePanel({ state, placement = 'top', triggerRef, portal, popoverRef }: PanelHookProps) {
64
+ if (!triggerRef.current) {
65
+ throw new Error('You must pass valid refs.');
66
+ }
55
67
  // using documentElement to get the width of the viewport excluding scrollbar
56
68
  const [screenWidth, setScreenWidth] = useState<number>(document.documentElement.clientWidth);
69
+ const [localPlacement, setLocalPlacement] = useState<PanelProps['placement']>(placement);
70
+ const [originalPosition, setOriginalPosition] = useState<DOMRect | null>(null);
71
+ const trigger = triggerRef.current.getBoundingClientRect();
72
+
73
+ // used for flipping popover if there is no space
74
+ const getVerticalPosition = useCallback(() => {
75
+ // handle vertical position with portal
76
+ if (portal instanceof Element) {
77
+ const portalRect = portal.getBoundingClientRect();
78
+ if (originalPosition && originalPosition?.top < portalRect.top) {
79
+ setLocalPlacement('bottom');
80
+ }
81
+ if (originalPosition && originalPosition?.bottom > portalRect.bottom) {
82
+ setLocalPlacement('top');
83
+ }
84
+ return;
85
+ }
86
+ // handle vertical position with no portal
87
+ if (originalPosition && originalPosition?.height > trigger.top) {
88
+ setLocalPlacement('bottom');
89
+ }
90
+ if (originalPosition && originalPosition?.bottom > document.documentElement.clientHeight) {
91
+ setLocalPlacement('top');
92
+ }
93
+ if (
94
+ originalPosition &&
95
+ originalPosition?.bottom <= document.documentElement.clientHeight &&
96
+ placement === 'bottom'
97
+ ) {
98
+ setLocalPlacement('bottom');
99
+ }
100
+ }, [placement, portal, originalPosition, trigger.top]);
101
+
102
+ useLayoutEffect(() => {
103
+ setOriginalPosition(popoverRef?.current?.getBoundingClientRect() || null);
104
+ }, [popoverRef]);
105
+
106
+ // So popover can be in correct position on open and doesn't move when opened
107
+ useLayoutEffect(() => {
108
+ getVerticalPosition();
109
+ }, [getVerticalPosition]);
110
+
57
111
  useEffect(() => {
58
112
  const handleResize = () => {
59
113
  if (portal instanceof Element) {
60
114
  setScreenWidth(portal.clientWidth);
115
+ getVerticalPosition();
61
116
  } else {
62
117
  setScreenWidth(document.documentElement.clientWidth);
118
+ getVerticalPosition();
63
119
  }
64
120
  };
65
121
 
@@ -67,7 +123,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
67
123
  return () => {
68
124
  window.removeEventListener('resize', handleResize);
69
125
  };
70
- }, [portal]);
126
+ }, [portal, getVerticalPosition]);
71
127
 
72
128
  const popoverPosition = useMemo(() => {
73
129
  const triggerDOMRect = triggerRef.current?.getBoundingClientRect();
@@ -75,7 +131,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
75
131
  const leftOffset = triggerRef.current ? getLeftOffsetPerHorizontalPosition(triggerRef.current, screenWidth) : 0;
76
132
  // If it is not portal, we can simplify the logic
77
133
  if (!portal) {
78
- switch (placement) {
134
+ switch (localPlacement) {
79
135
  case 'top':
80
136
  return {
81
137
  bottom: '100%',
@@ -92,7 +148,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
92
148
 
93
149
  // If it is portal, we need to considerate the scroll if there is a scroll in the portal
94
150
  const portalElement = portal as Element;
95
- switch (placement) {
151
+ switch (localPlacement) {
96
152
  case 'top':
97
153
  return {
98
154
  // The top is calculated according to the portal element
@@ -109,7 +165,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
109
165
  };
110
166
  }
111
167
  // eslint-disable-next-line react-hooks/exhaustive-deps
112
- }, [placement, portal, triggerRef, state.isOpen, screenWidth]);
168
+ }, [localPlacement, portal, triggerRef, state.isOpen, screenWidth]);
113
169
 
114
170
  const arrowPosition = useMemo(() => {
115
171
  const triggerDOMRect = triggerRef.current?.getBoundingClientRect();
@@ -118,13 +174,14 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
118
174
  : 0;
119
175
 
120
176
  return {
121
- left: `${(triggerDOMRect?.width || 0) / 2 + leftOffset}px`,
177
+ left: `${leftOffset - ARROW_HEIGHT + (triggerDOMRect?.width || 0) / 2}px`,
122
178
  };
123
179
  // eslint-disable-next-line react-hooks/exhaustive-deps
124
- }, [triggerRef, state.isOpen, screenWidth]);
180
+ }, [triggerRef, state.isOpen, screenWidth, originalPosition]);
125
181
 
126
182
  return {
127
183
  popoverPosition,
128
184
  arrowPosition,
185
+ localPlacement,
129
186
  };
130
187
  }
@@ -18,11 +18,11 @@ export const styles = tv(
18
18
  placement: {
19
19
  top: {
20
20
  popover: '-mt-2 mb-2',
21
- arrow: 'top-full translate-x-[-6px] translate-y-0',
21
+ arrow: 'top-full translate-x-[-2.5px]',
22
22
  },
23
23
  bottom: {
24
24
  popover: 'mt-2',
25
- arrow: 'bottom-full translate-x-[6px] rotate-180 after:bottom-[1px]',
25
+ arrow: 'bottom-full translate-x-[14px] rotate-180', // rotate-180 moves the arrow to the left, translate needed for centering
26
26
  },
27
27
  },
28
28
  },