@skyscanner/backpack-web 38.1.0 → 38.2.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.
@@ -24,6 +24,7 @@ import { lockScroll, setA11yTabIndex, useScrollToCard, useIntersectionObserver }
24
24
  import STYLES from "./BpkCardListCarousel.module.css";
25
25
  import { jsx as _jsx } from "react/jsx-runtime";
26
26
  const getClassName = cssModules(STYLES);
27
+ const PAGINATION_INDICATOR_MAX_SHOWN_COUNT = 5;
27
28
  const BpkCardListCarousel = props => {
28
29
  const {
29
30
  carouselLabel = (initiallyShownCards, childrenLength) => `Entering Carousel with ${initiallyShownCards} slides shown at a time, ${childrenLength} slides in total. Please use Pagination below with the Previous and Next buttons to navigate, or the slide dot buttons at the end to jump to slides.`,
@@ -32,6 +33,7 @@ const BpkCardListCarousel = props => {
32
33
  initiallyShownCards,
33
34
  isMobile = false,
34
35
  layout,
36
+ setCurrentIndex,
35
37
  slideLabel = (index, childrenLength) => `slide ${index + 1} of ${childrenLength}`
36
38
  } = props;
37
39
  const shownNumberStyle = {
@@ -56,7 +58,15 @@ const BpkCardListCarousel = props => {
56
58
  // Similar to Virtual Scrolling to improve performance
57
59
  const firstVisibleIndex = Math.max(0, visibilityList.indexOf(1));
58
60
  const lastVisibleIndex = firstVisibleIndex + initiallyShownCards - 1;
59
- const renderList = useMemo(() => visibilityList.map((_, index) => index >= firstVisibleIndex - RENDER_BUFFER_SIZE && index <= lastVisibleIndex + RENDER_BUFFER_SIZE ? 1 : 0), [visibilityList, firstVisibleIndex, lastVisibleIndex]);
61
+ const dynamicRenderBufferSize = useMemo(() => {
62
+ if (childrenLength === 0 || initiallyShownCards === 0 || isMobile) return RENDER_BUFFER_SIZE;
63
+
64
+ // Calculate how many cards to render based on the number of initially shown cards and total children
65
+ const totalPages = Math.ceil(childrenLength / initiallyShownCards);
66
+ const shownIndicatorCount = Math.min(totalPages, PAGINATION_INDICATOR_MAX_SHOWN_COUNT);
67
+ return Math.max(RENDER_BUFFER_SIZE, (shownIndicatorCount - 1) * initiallyShownCards);
68
+ }, [childrenLength, initiallyShownCards, isMobile]);
69
+ const renderList = useMemo(() => visibilityList.map((_, index) => index >= firstVisibleIndex - dynamicRenderBufferSize && index <= lastVisibleIndex + dynamicRenderBufferSize ? 1 : 0), [visibilityList, firstVisibleIndex, lastVisibleIndex, dynamicRenderBufferSize]);
60
70
  const cardRefFns = useMemo(() => Array(childrenLength).fill(null).map((_, i) => el => {
61
71
  cardRefs.current[i] = el;
62
72
  observerVisibility(el, i);
@@ -91,11 +101,20 @@ const BpkCardListCarousel = props => {
91
101
  useEffect(() => {
92
102
  // update hasBeenVisibleRef to include the range of cards that should be visible
93
103
  const start = currentIndex * initiallyShownCards;
94
- const end = start + initiallyShownCards + RENDER_BUFFER_SIZE;
104
+ const end = start + initiallyShownCards + dynamicRenderBufferSize;
95
105
  for (let i = start; i < end && i < childrenLength; i += 1) {
96
106
  hasBeenVisibleRef.current.add(i);
97
107
  }
98
- }, [currentIndex, initiallyShownCards, childrenLength]);
108
+ }, [currentIndex, initiallyShownCards, childrenLength, dynamicRenderBufferSize]);
109
+ useEffect(() => {
110
+ const firstVisible = visibilityList.indexOf(1);
111
+ if (firstVisible >= 0) {
112
+ const newIndex = Math.floor(firstVisible / initiallyShownCards);
113
+ if (newIndex !== currentIndex) {
114
+ setCurrentIndex(newIndex);
115
+ }
116
+ }
117
+ }, [initiallyShownCards]);
99
118
  useEffect(() => {
100
119
  const handleResize = throttle(() => {
101
120
  firstCardWidthRef.current = null;
@@ -15,4 +15,4 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- .bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{display:flex;padding-top:.25rem;padding-bottom:.25rem;overflow-x:hidden;box-sizing:border-box;gap:.25rem;scroll-snap-stop:always;scroll-snap-type:x mandatory;scrollbar-width:none}@media(max-width: 32rem){.bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{overflow-x:scroll}}.bpk-card-list-row-rail__row::-webkit-scrollbar,.bpk-card-list-row-rail__rail::-webkit-scrollbar{display:none}.bpk-card-list-row-rail__row__card,.bpk-card-list-row-rail__rail__card{position:relative;padding:0 .5rem;flex:0 0 calc((100% - .5rem*(var(--initially-shown-cards, 3) - 1))/var(--initially-shown-cards, 3));overflow:visible;box-sizing:border-box;scroll-snap-align:start}@media(max-width: 32rem){.bpk-card-list-row-rail__row__card,.bpk-card-list-row-rail__rail__card{flex:0 0 calc((100% - .5rem*(var(--initially-shown-cards, 3) - 1))/max(1,var(--initially-shown-cards, 3) - .8))}.bpk-card-list-row-rail__row__card:first-child,.bpk-card-list-row-rail__rail__card:first-child{padding-left:.25rem}html[dir=rtl] .bpk-card-list-row-rail__row__card:first-child,html[dir=rtl] .bpk-card-list-row-rail__rail__card:first-child{padding-right:.25rem;padding-left:.5rem}}.bpk-card-list-row-rail__rail{-webkit-overflow-scrolling:touch}
18
+ .bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{display:flex;padding-top:.25rem;padding-bottom:1.5rem;overflow-x:hidden;box-sizing:border-box;gap:.25rem;scroll-snap-stop:always;scroll-snap-type:x mandatory;scrollbar-width:none}@media(max-width: 32rem){.bpk-card-list-row-rail__row,.bpk-card-list-row-rail__rail{padding-bottom:1rem;overflow-x:scroll}}.bpk-card-list-row-rail__row::-webkit-scrollbar,.bpk-card-list-row-rail__rail::-webkit-scrollbar{display:none}.bpk-card-list-row-rail__row__card,.bpk-card-list-row-rail__rail__card{position:relative;padding:0 .5rem;flex:0 0 calc((100% - .5rem*(var(--initially-shown-cards, 3) - 1))/var(--initially-shown-cards, 3));overflow:visible;box-sizing:border-box;scroll-snap-align:start}@media(max-width: 32rem){.bpk-card-list-row-rail__row__card,.bpk-card-list-row-rail__rail__card{flex:0 0 calc((100% - .5rem*(var(--initially-shown-cards, 3) - 1))/max(1,var(--initially-shown-cards, 3) - .8))}.bpk-card-list-row-rail__row__card:first-child,.bpk-card-list-row-rail__rail__card:first-child{padding-left:.25rem}html[dir=rtl] .bpk-card-list-row-rail__row__card:first-child,html[dir=rtl] .bpk-card-list-row-rail__rail__card:first-child{padding-right:.25rem;padding-left:.5rem}}.bpk-card-list-row-rail__rail{-webkit-overflow-scrolling:touch}
@@ -51,6 +51,7 @@ const BpkCardListRowRailContainer = props => {
51
51
  children: [/*#__PURE__*/_jsx(BpkCardListCarousel, {
52
52
  initiallyShownCards: initiallyShownCards,
53
53
  layout: layout,
54
+ setCurrentIndex: setCurrentIndex,
54
55
  currentIndex: currentIndex,
55
56
  isMobile: isMobile,
56
57
  carouselLabel: accessibilityLabels?.carouselLabel,
@@ -1,4 +1,4 @@
1
- import type { ReactElement } from 'react';
1
+ import type { ReactElement, Dispatch, SetStateAction } from 'react';
2
2
  declare const LAYOUTS: {
3
3
  readonly grid: "grid";
4
4
  readonly stack: "stack";
@@ -70,6 +70,7 @@ type CardListCarouselProps = {
70
70
  initiallyShownCards: number;
71
71
  layout: typeof LAYOUTS.row | typeof LAYOUTS.rail;
72
72
  currentIndex: number;
73
+ setCurrentIndex: Dispatch<SetStateAction<number>>;
73
74
  isMobile?: boolean;
74
75
  carouselLabel?: (initiallyShownCards: number, childrenLength: number) => string;
75
76
  slideLabel?: (index: number, childrenLength: number) => string;
@@ -131,25 +131,9 @@ const BpkPopover = ({
131
131
  children: target
132
132
  });
133
133
  const classNames = getClassName('bpk-popover', className);
134
- const bodyClassNames = getClassName('bpk-popover__body', padded && 'bpk-popover__body--padded');
134
+ const bodyClassNames = getClassName(padded && 'bpk-popover__body--padded');
135
135
  const labelId = `bpk-popover-label-${id}`;
136
136
  const renderElement = typeof renderTarget === 'function' ? renderTarget() : renderTarget;
137
- const closeButtonIconElement = /*#__PURE__*/_jsx(BpkCloseButton, {
138
- label: closeButtonText || closeButtonLabel,
139
- onClick: event => {
140
- bindEventSource(EVENT_SOURCES.CLOSE_BUTTON, onClose, event);
141
- setIsOpenState(false);
142
- },
143
- ...closeButtonProps
144
- });
145
- const closeButtonTextElement = /*#__PURE__*/_jsx(BpkButtonLink, {
146
- onClick: event => {
147
- bindEventSource(EVENT_SOURCES.CLOSE_LINK, onClose, event);
148
- setIsOpenState(false);
149
- },
150
- ...closeButtonProps,
151
- children: closeButtonText
152
- });
153
137
  return /*#__PURE__*/_jsxs(_Fragment, {
154
138
  children: [targetElement, isOpenState && /*#__PURE__*/_jsx(FloatingPortal, {
155
139
  root: renderElement,
@@ -189,16 +173,28 @@ const BpkPopover = ({
189
173
  id: labelId,
190
174
  textStyle: TEXT_STYLES.label1,
191
175
  children: label
192
- }), "\xA0", closeButtonIcon ? closeButtonIconElement : !!closeButtonText && closeButtonTextElement]
176
+ }), "\xA0", closeButtonIcon ? /*#__PURE__*/_jsx(BpkCloseButton, {
177
+ label: closeButtonText || closeButtonLabel,
178
+ onClick: event => {
179
+ bindEventSource(EVENT_SOURCES.CLOSE_BUTTON, onClose, event);
180
+ setIsOpenState(false);
181
+ },
182
+ ...closeButtonProps
183
+ }) : closeButtonText && /*#__PURE__*/_jsx(BpkButtonLink, {
184
+ onClick: event => {
185
+ bindEventSource(EVENT_SOURCES.CLOSE_LINK, onClose, event);
186
+ setIsOpenState(false);
187
+ },
188
+ ...closeButtonProps,
189
+ children: closeButtonText
190
+ })]
193
191
  }) : /*#__PURE__*/_jsx("span", {
194
192
  id: labelId,
195
193
  className: getClassName('bpk-popover__label'),
196
194
  children: label
197
- }), /*#__PURE__*/_jsxs("div", {
195
+ }), /*#__PURE__*/_jsx("div", {
198
196
  className: bodyClassNames,
199
- children: [/*#__PURE__*/_jsx("div", {
200
- children: children
201
- }), !labelAsTitle && closeButtonIcon && closeButtonIconElement]
197
+ children: children
202
198
  }), actionText && onAction && /*#__PURE__*/_jsx("div", {
203
199
  className: getClassName('bpk-popover__action'),
204
200
  children: /*#__PURE__*/_jsx(BpkButtonLink, {
@@ -207,7 +203,14 @@ const BpkPopover = ({
207
203
  })
208
204
  }), !labelAsTitle && closeButtonText && /*#__PURE__*/_jsx("footer", {
209
205
  className: getClassName('bpk-popover__footer'),
210
- children: closeButtonTextElement
206
+ children: /*#__PURE__*/_jsx(BpkButtonLink, {
207
+ onClick: event => {
208
+ bindEventSource(EVENT_SOURCES.CLOSE_LINK, onClose, event);
209
+ setIsOpenState(false);
210
+ },
211
+ ...closeButtonProps,
212
+ children: closeButtonText
213
+ })
211
214
  })]
212
215
  })
213
216
  })
@@ -15,4 +15,4 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- .bpk-popover{transition:opacity 200ms ease-in-out;outline:0;background-color:#fff;opacity:1;border-radius:.5rem;box-shadow:0px 4px 14px 0px rgba(37,32,31,.25)}@media(min-width: 32.0625rem){.bpk-popover{max-width:32rem;transition:opacity 50ms ease-in-out}}.bpk-popover--appear{opacity:0}.bpk-popover--appear-active{opacity:1}.bpk-popover__arrow{width:1.5rem;height:1.5rem;fill:#fff}.bpk-popover__arrow[data-hide]{visibility:hidden}.bpk-popover__body{display:flex;column-gap:1rem;justify-content:space-between;align-items:flex-start}.bpk-popover__body--padded{padding:1rem}.bpk-popover__body--padded:not(:last-of-type){padding-bottom:0}.bpk-popover__header{display:flex;padding:1rem 1rem 0;justify-content:space-between}.bpk-popover__header~.bpk-popover__body--padded{padding-top:.5rem}.bpk-popover__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;white-space:nowrap;overflow:hidden;clip:rect(0 0 0 0)}.bpk-popover__footer{padding:.5rem 1rem;text-align:right}html[dir=rtl] .bpk-popover__footer{text-align:left}.bpk-popover__action{padding:0 1rem 1rem 1rem}
18
+ .bpk-popover{transition:opacity 200ms ease-in-out;outline:0;background-color:#fff;opacity:1;border-radius:.5rem;box-shadow:0px 4px 14px 0px rgba(37,32,31,.25)}@media(min-width: 32.0625rem){.bpk-popover{max-width:32rem;transition:opacity 50ms ease-in-out}}.bpk-popover--appear{opacity:0}.bpk-popover--appear-active{opacity:1}.bpk-popover__arrow{width:1.5rem;height:1.5rem;fill:#fff}.bpk-popover__arrow[data-hide]{visibility:hidden}.bpk-popover__body--padded{padding:1rem}.bpk-popover__body--padded:not(:last-of-type){padding-bottom:0}.bpk-popover__header{display:flex;padding:1rem 1rem 0;justify-content:space-between}.bpk-popover__header~.bpk-popover__body--padded{padding-top:.5rem}.bpk-popover__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;white-space:nowrap;overflow:hidden;clip:rect(0 0 0 0)}.bpk-popover__footer{padding:.5rem 1rem;text-align:right}html[dir=rtl] .bpk-popover__footer{text-align:left}.bpk-popover__action{padding:0 1rem 1rem 1rem}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyscanner/backpack-web",
3
- "version": "38.1.0",
3
+ "version": "38.2.0",
4
4
  "description": "Backpack Design System web library",
5
5
  "repository": {
6
6
  "type": "git",