@westpac/ui 0.58.0 → 0.59.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.
@@ -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.58.0",
3
+ "version": "0.59.1",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -259,9 +259,9 @@
259
259
  "typescript": "^5.5.4",
260
260
  "vite": "^7.0.8",
261
261
  "vitest": "^3.2.4",
262
+ "@westpac/test-config": "~0.0.0",
262
263
  "@westpac/eslint-config": "~1.0.1",
263
- "@westpac/ts-config": "~0.0.0",
264
- "@westpac/test-config": "~0.0.0"
264
+ "@westpac/ts-config": "~0.0.0"
265
265
  },
266
266
  "dependencies": {
267
267
  "@duetds/date-picker": "~1.4.0",
@@ -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
  )}
@@ -24,6 +24,9 @@ export function Compacta({
24
24
  children,
25
25
  titleTag: Tag = 'h3',
26
26
  addText = 'Add another',
27
+ initialCompactas,
28
+ onAdd,
29
+ onRemove,
27
30
  ...props
28
31
  }: CompactaProps) {
29
32
  const [initial, setInitial] = useState(true);
@@ -63,11 +66,16 @@ export function Compacta({
63
66
  ]);
64
67
 
65
68
  setAction({ type: 'add', index: newItems.length });
69
+
70
+ onAdd?.();
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
66
72
  }, [id, items]);
67
73
 
68
74
  const handleRemove = useCallback((id: string, index: number) => {
69
75
  setItems(items => items.filter(item => item.id !== id));
70
76
  setAction({ type: 'remove', index, id });
77
+ onRemove?.(id, index);
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
79
  }, []);
72
80
 
73
81
  const handleToggle = useCallback(
@@ -118,6 +126,26 @@ export function Compacta({
118
126
  }
119
127
  }, [items.length, action]);
120
128
 
129
+ useEffect(() => {
130
+ if (initialCompactas) {
131
+ const newItems = initialCompactas.map((item, index) => {
132
+ const itemId = item.id ?? `${id}-${generateID()}`;
133
+ return {
134
+ id: itemId,
135
+ open: item.open ? item.open : index === initialCompactas.length - 1,
136
+ delay: false,
137
+ title: {
138
+ primary: item.title?.primary ?? '',
139
+ secondary: item.title?.secondary ?? '',
140
+ tertiary: item.title?.tertiary ?? '',
141
+ },
142
+ };
143
+ });
144
+ setItems(newItems);
145
+ }
146
+ // eslint-disable-next-line react-hooks/exhaustive-deps
147
+ }, []);
148
+
121
149
  const styles = compactaStyles({});
122
150
 
123
151
  return (
@@ -177,6 +205,7 @@ export function Compacta({
177
205
  <div className={styles.content()} id={`gel-compacta-content-${item.id}`}>
178
206
  {children({
179
207
  id: item.id,
208
+ index,
180
209
  setPrimaryTitle: (title: string) => setTitle(item.id, 'primary', title),
181
210
  setSecondaryTitle: (title: string) => setTitle(item.id, 'secondary', title),
182
211
  setTertiaryTitle: (title: string) => setTitle(item.id, 'tertiary', title),
@@ -9,6 +9,26 @@ export type CompactaProps = {
9
9
  * Component to repeat
10
10
  */
11
11
  children: (...props: ContentProps[]) => ReactNode;
12
+ /**
13
+ * The initial compactas to render. Each compacta needs a unique id if you want to pre-fill values.
14
+ * Each object contained within the array represents a compacta.
15
+ */
16
+ initialCompactas?: {
17
+ // Compacta id
18
+ id?: string;
19
+ // If compacta should be open on initial render
20
+ open?: boolean;
21
+ // Titles to pre-fill, won't be done automatically
22
+ title?: { primary?: string; secondary?: string; tertiary?: string };
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;
12
32
  /**
13
33
  * Tag for primary title
14
34
  * @default h3
@@ -18,6 +38,7 @@ export type CompactaProps = {
18
38
 
19
39
  type ContentProps = {
20
40
  id: string;
41
+ index: number;
21
42
  setPrimaryTitle: (title: string) => unknown;
22
43
  setSecondaryTitle: (title: string) => unknown;
23
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();
@@ -51,15 +52,62 @@ export type PanelHookProps = {
51
52
  * @returns {Object} return.popoverPosition - The calculated position styles for the popover.
52
53
  * @returns {Object} return.arrowPosition - The calculated position styles for the popover arrow.
53
54
  */
54
- export function usePanel({ state, placement = 'bottom', triggerRef, portal }: PanelHookProps) {
55
+ export function usePanel({ state, placement = 'top', triggerRef, portal, popoverRef }: PanelHookProps) {
56
+ if (!triggerRef.current) {
57
+ throw new Error('You must pass valid refs.');
58
+ }
55
59
  // using documentElement to get the width of the viewport excluding scrollbar
56
60
  const [screenWidth, setScreenWidth] = useState<number>(document.documentElement.clientWidth);
61
+ const [localPlacement, setLocalPlacement] = useState<PanelProps['placement']>(placement);
62
+ const [originalPosition, setOriginalPosition] = useState<DOMRect | null>(null);
63
+ const trigger = triggerRef.current.getBoundingClientRect();
64
+
65
+ // used for flipping popover if there is no space
66
+ const getVerticalPosition = useCallback(() => {
67
+ // handle vertical position with portal
68
+ if (portal instanceof Element) {
69
+ const portalRect = portal.getBoundingClientRect();
70
+ if (originalPosition && originalPosition?.top < portalRect.top) {
71
+ setLocalPlacement('bottom');
72
+ }
73
+ if (originalPosition && originalPosition?.bottom > portalRect.bottom) {
74
+ setLocalPlacement('top');
75
+ }
76
+ return;
77
+ }
78
+ // handle vertical position with no portal
79
+ if (originalPosition && originalPosition?.height > trigger.top) {
80
+ setLocalPlacement('bottom');
81
+ }
82
+ if (originalPosition && originalPosition?.bottom > document.documentElement.clientHeight) {
83
+ setLocalPlacement('top');
84
+ }
85
+ if (
86
+ originalPosition &&
87
+ originalPosition?.bottom <= document.documentElement.clientHeight &&
88
+ placement === 'bottom'
89
+ ) {
90
+ setLocalPlacement('bottom');
91
+ }
92
+ }, [placement, portal, originalPosition, trigger.top]);
93
+
94
+ useLayoutEffect(() => {
95
+ setOriginalPosition(popoverRef?.current?.getBoundingClientRect() || null);
96
+ }, [popoverRef]);
97
+
98
+ // So popover can be in correct position on open and doesn't move when opened
99
+ useLayoutEffect(() => {
100
+ getVerticalPosition();
101
+ }, [getVerticalPosition]);
102
+
57
103
  useEffect(() => {
58
104
  const handleResize = () => {
59
105
  if (portal instanceof Element) {
60
106
  setScreenWidth(portal.clientWidth);
107
+ getVerticalPosition();
61
108
  } else {
62
109
  setScreenWidth(document.documentElement.clientWidth);
110
+ getVerticalPosition();
63
111
  }
64
112
  };
65
113
 
@@ -67,7 +115,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
67
115
  return () => {
68
116
  window.removeEventListener('resize', handleResize);
69
117
  };
70
- }, [portal]);
118
+ }, [portal, getVerticalPosition]);
71
119
 
72
120
  const popoverPosition = useMemo(() => {
73
121
  const triggerDOMRect = triggerRef.current?.getBoundingClientRect();
@@ -75,7 +123,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
75
123
  const leftOffset = triggerRef.current ? getLeftOffsetPerHorizontalPosition(triggerRef.current, screenWidth) : 0;
76
124
  // If it is not portal, we can simplify the logic
77
125
  if (!portal) {
78
- switch (placement) {
126
+ switch (localPlacement) {
79
127
  case 'top':
80
128
  return {
81
129
  bottom: '100%',
@@ -92,7 +140,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
92
140
 
93
141
  // If it is portal, we need to considerate the scroll if there is a scroll in the portal
94
142
  const portalElement = portal as Element;
95
- switch (placement) {
143
+ switch (localPlacement) {
96
144
  case 'top':
97
145
  return {
98
146
  // The top is calculated according to the portal element
@@ -109,7 +157,7 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
109
157
  };
110
158
  }
111
159
  // eslint-disable-next-line react-hooks/exhaustive-deps
112
- }, [placement, portal, triggerRef, state.isOpen, screenWidth]);
160
+ }, [localPlacement, portal, triggerRef, state.isOpen, screenWidth]);
113
161
 
114
162
  const arrowPosition = useMemo(() => {
115
163
  const triggerDOMRect = triggerRef.current?.getBoundingClientRect();
@@ -118,13 +166,14 @@ export function usePanel({ state, placement = 'bottom', triggerRef, portal }: Pa
118
166
  : 0;
119
167
 
120
168
  return {
121
- left: `${(triggerDOMRect?.width || 0) / 2 + leftOffset}px`,
169
+ left: `${leftOffset - ARROW_HEIGHT + (triggerDOMRect?.width || 0) / 2}px`,
122
170
  };
123
171
  // eslint-disable-next-line react-hooks/exhaustive-deps
124
- }, [triggerRef, state.isOpen, screenWidth]);
172
+ }, [triggerRef, state.isOpen, screenWidth, originalPosition]);
125
173
 
126
174
  return {
127
175
  popoverPosition,
128
176
  arrowPosition,
177
+ localPlacement,
129
178
  };
130
179
  }
@@ -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
  },