@jobber/components 6.119.0 → 6.120.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.
@@ -73,12 +73,26 @@ export interface ModalActionsProps {
73
73
  * Useful for actions like "Cancel" that are not destructive.
74
74
  */
75
75
  readonly tertiary?: ButtonProps;
76
+ /**
77
+ * Controls how the actions are positioned within the modal content.
78
+ * - "inline": actions flow with content
79
+ * - "sticky": actions stay visible at the bottom while content scrolls
80
+ * @default "inline"
81
+ */
82
+ readonly variant?: "inline" | "sticky";
76
83
  }
77
84
  interface HeaderPropsWithoutChildren {
78
85
  /**
79
86
  * Title of the modal.
80
87
  */
81
88
  readonly title: string;
89
+ /**
90
+ * Controls how the header is positioned within the modal content.
91
+ * - "inline": header flows with content
92
+ * - "sticky": header stays visible while content scrolls
93
+ * @default "inline"
94
+ */
95
+ readonly variant?: "inline" | "sticky";
82
96
  /**
83
97
  * Whether the modal is dismissible.
84
98
  */
@@ -88,7 +102,15 @@ interface HeaderPropsWithoutChildren {
88
102
  */
89
103
  onRequestClose?(): void;
90
104
  }
91
- type HeaderWithChildren = PropsWithChildren;
105
+ type HeaderWithChildren = PropsWithChildren<{
106
+ /**
107
+ * Controls how the header is positioned within the modal content.
108
+ * - "inline": header flows with content
109
+ * - "sticky": header stays visible while content scrolls
110
+ * @default "inline"
111
+ */
112
+ readonly variant?: "inline" | "sticky";
113
+ }>;
92
114
  export type HeaderProps = XOR<HeaderPropsWithoutChildren, HeaderWithChildren>;
93
115
  export interface ModalLegacyProps {
94
116
  /**
@@ -172,7 +172,7 @@ function useModalContext() {
172
172
  return React.useContext(ModalContext);
173
173
  }
174
174
 
175
- var styles = {"overlay":"cRhQeXmZobs-","overlayBackground":"PO6ZUDxQoFE-","modal":"OiqCKNmbHZ0-","modalBody":"zM9SWwAd5Dg-","header":"GDWGHwmjgAc-","closeButton":"KJlGo4z-E6Q-","actionBar":"ZGrhWCAymCw-","leftAction":"hOiEWds2Vq8-","rightAction":"K31NzxPZP9s-","spinning":"SKeimJFlB-g-"};
175
+ var styles = {"overlay":"cRhQeXmZobs-","overlayBackground":"PO6ZUDxQoFE-","modal":"OiqCKNmbHZ0-","modalWithStickyRegions":"_6dGKr5VVf9Y-","stickyHeaderRegion":"uo6BCADQuLU-","stickyActionsRegion":"Vq0y9ggAxtI-","modalBody":"zM9SWwAd5Dg-","header":"GDWGHwmjgAc-","closeButton":"KJlGo4z-E6Q-","actionBar":"ZGrhWCAymCw-","leftAction":"hOiEWds2Vq8-","rightAction":"K31NzxPZP9s-","spinning":"SKeimJFlB-g-"};
176
176
 
177
177
  function useModalStyles(size) {
178
178
  return {
@@ -229,6 +229,10 @@ function ModalOverlay({ children }) {
229
229
  function ModalContent({ children }) {
230
230
  const { open, floatingContext, activatorRef, floatingRefs, size, floatingNodeId, modalLabelledBy, ariaLabel, getFloatingProps, startedInsideRef, } = useModalContext();
231
231
  const { modal } = useModalStyles(size);
232
+ const childrenArray = React.Children.toArray(children);
233
+ const { sticky: stickyHeaders, rest: afterHeaders } = extractSticky(childrenArray, ModalHeader, props => props.variant === "sticky");
234
+ const { sticky: stickyActions, rest: bodyChildren } = extractSticky(afterHeaders, ModalActions, props => props.variant === "sticky");
235
+ const hasStickyRegions = stickyHeaders.length > 0 || stickyActions.length > 0;
232
236
  return (React.createElement(framerMotion.AnimatePresence, null, open && (React.createElement(floatingUi_react.FloatingNode, { id: floatingNodeId },
233
237
  React.createElement(floatingUi_react.FloatingPortal, null,
234
238
  React.createElement(AtlantisPortalContent.AtlantisPortalContent, null,
@@ -239,7 +243,9 @@ function ModalContent({ children }) {
239
243
  ease: "easeInOut",
240
244
  }, ref: floatingRefs === null || floatingRefs === void 0 ? void 0 : floatingRefs.setFloating, "data-modal-node-id": floatingNodeId }, getFloatingProps({
241
245
  role: "dialog",
242
- className: modal,
246
+ className: hasStickyRegions
247
+ ? `${modal} ${styles.modalWithStickyRegions}`
248
+ : modal,
243
249
  "aria-labelledby": modalLabelledBy,
244
250
  "aria-label": ariaLabel,
245
251
  "aria-modal": true,
@@ -248,7 +254,22 @@ function ModalContent({ children }) {
248
254
  if (startedInsideRef)
249
255
  startedInsideRef.current = true;
250
256
  } }),
251
- React.createElement("div", { className: styles.modalBody, tabIndex: -1 }, children))))))))));
257
+ stickyHeaders.length > 0 && (React.createElement("div", { className: styles.stickyHeaderRegion }, stickyHeaders)),
258
+ React.createElement("div", { className: styles.modalBody, tabIndex: -1 }, bodyChildren),
259
+ stickyActions.length > 0 && (React.createElement("div", { className: styles.stickyActionsRegion }, stickyActions)))))))))));
260
+ }
261
+ function extractSticky(children, componentType, match) {
262
+ const sticky = [];
263
+ const rest = children.filter(child => {
264
+ if (React.isValidElement(child) &&
265
+ child.type === componentType &&
266
+ match(child.props)) {
267
+ sticky.push(child);
268
+ return false;
269
+ }
270
+ return true;
271
+ });
272
+ return { sticky, rest };
252
273
  }
253
274
 
254
275
  function Modal(props) {
@@ -170,7 +170,7 @@ function useModalContext() {
170
170
  return useContext(ModalContext);
171
171
  }
172
172
 
173
- var styles = {"overlay":"cRhQeXmZobs-","overlayBackground":"PO6ZUDxQoFE-","modal":"OiqCKNmbHZ0-","modalBody":"zM9SWwAd5Dg-","header":"GDWGHwmjgAc-","closeButton":"KJlGo4z-E6Q-","actionBar":"ZGrhWCAymCw-","leftAction":"hOiEWds2Vq8-","rightAction":"K31NzxPZP9s-","spinning":"SKeimJFlB-g-"};
173
+ var styles = {"overlay":"cRhQeXmZobs-","overlayBackground":"PO6ZUDxQoFE-","modal":"OiqCKNmbHZ0-","modalWithStickyRegions":"_6dGKr5VVf9Y-","stickyHeaderRegion":"uo6BCADQuLU-","stickyActionsRegion":"Vq0y9ggAxtI-","modalBody":"zM9SWwAd5Dg-","header":"GDWGHwmjgAc-","closeButton":"KJlGo4z-E6Q-","actionBar":"ZGrhWCAymCw-","leftAction":"hOiEWds2Vq8-","rightAction":"K31NzxPZP9s-","spinning":"SKeimJFlB-g-"};
174
174
 
175
175
  function useModalStyles(size) {
176
176
  return {
@@ -227,6 +227,10 @@ function ModalOverlay({ children }) {
227
227
  function ModalContent({ children }) {
228
228
  const { open, floatingContext, activatorRef, floatingRefs, size, floatingNodeId, modalLabelledBy, ariaLabel, getFloatingProps, startedInsideRef, } = useModalContext();
229
229
  const { modal } = useModalStyles(size);
230
+ const childrenArray = React__default.Children.toArray(children);
231
+ const { sticky: stickyHeaders, rest: afterHeaders } = extractSticky(childrenArray, ModalHeader, props => props.variant === "sticky");
232
+ const { sticky: stickyActions, rest: bodyChildren } = extractSticky(afterHeaders, ModalActions, props => props.variant === "sticky");
233
+ const hasStickyRegions = stickyHeaders.length > 0 || stickyActions.length > 0;
230
234
  return (React__default.createElement(AnimatePresence, null, open && (React__default.createElement(FloatingNode, { id: floatingNodeId },
231
235
  React__default.createElement(FloatingPortal, null,
232
236
  React__default.createElement(AtlantisPortalContent, null,
@@ -237,7 +241,9 @@ function ModalContent({ children }) {
237
241
  ease: "easeInOut",
238
242
  }, ref: floatingRefs === null || floatingRefs === void 0 ? void 0 : floatingRefs.setFloating, "data-modal-node-id": floatingNodeId }, getFloatingProps({
239
243
  role: "dialog",
240
- className: modal,
244
+ className: hasStickyRegions
245
+ ? `${modal} ${styles.modalWithStickyRegions}`
246
+ : modal,
241
247
  "aria-labelledby": modalLabelledBy,
242
248
  "aria-label": ariaLabel,
243
249
  "aria-modal": true,
@@ -246,7 +252,22 @@ function ModalContent({ children }) {
246
252
  if (startedInsideRef)
247
253
  startedInsideRef.current = true;
248
254
  } }),
249
- React__default.createElement("div", { className: styles.modalBody, tabIndex: -1 }, children))))))))));
255
+ stickyHeaders.length > 0 && (React__default.createElement("div", { className: styles.stickyHeaderRegion }, stickyHeaders)),
256
+ React__default.createElement("div", { className: styles.modalBody, tabIndex: -1 }, bodyChildren),
257
+ stickyActions.length > 0 && (React__default.createElement("div", { className: styles.stickyActionsRegion }, stickyActions)))))))))));
258
+ }
259
+ function extractSticky(children, componentType, match) {
260
+ const sticky = [];
261
+ const rest = children.filter(child => {
262
+ if (React__default.isValidElement(child) &&
263
+ child.type === componentType &&
264
+ match(child.props)) {
265
+ sticky.push(child);
266
+ return false;
267
+ }
268
+ return true;
269
+ });
270
+ return { sticky, rest };
250
271
  }
251
272
 
252
273
  function Modal(props) {
package/dist/styles.css CHANGED
@@ -4940,6 +4940,7 @@ a._7BLGtYNuJOU-.zgRx3ehZ2z8-:hover {
4940
4940
  --modal--max-height: calc(100dvh - 2 * var(--space-base));
4941
4941
  --modal--margin: auto;
4942
4942
  --modal--border-radius: var(--radius-base);
4943
+ --modal--sticky-region-fade-size: var(--space-large);
4943
4944
  --modal--padding-horizontal: var(--space-base);
4944
4945
  --modal--padding-vertical: var(--space-base);
4945
4946
  --modal--padding: var(--modal--padding-vertical)
@@ -5016,6 +5017,62 @@ a._7BLGtYNuJOU-.zgRx3ehZ2z8-:hover {
5016
5017
  flex: 0 0 auto;
5017
5018
  }
5018
5019
 
5020
+ ._6dGKr5VVf9Y- {
5021
+ display: -ms-flexbox;
5022
+ display: flex;
5023
+ -ms-flex-direction: column;
5024
+ flex-direction: column;
5025
+ }
5026
+
5027
+ .uo6BCADQuLU-,
5028
+ .Vq0y9ggAxtI- {
5029
+ position: relative;
5030
+ z-index: 1;
5031
+ background-color: rgba(255, 255, 255, 1);
5032
+ background-color: var(--color-surface);
5033
+ }
5034
+
5035
+ .uo6BCADQuLU-::after,
5036
+ .Vq0y9ggAxtI-::before {
5037
+ content: "";
5038
+ position: absolute;
5039
+ right: 0;
5040
+ left: 0;
5041
+ height: 24px;
5042
+ height: var(--modal--sticky-region-fade-size);
5043
+ pointer-events: none;
5044
+ }
5045
+
5046
+ .uo6BCADQuLU-::after {
5047
+ bottom: calc(24px * -1);
5048
+ bottom: calc(var(--modal--sticky-region-fade-size) * -1);
5049
+ background: linear-gradient(
5050
+ to bottom,
5051
+ rgba(255, 255, 255, 1) 0%,
5052
+ transparent 100%
5053
+ );
5054
+ background: linear-gradient(
5055
+ to bottom,
5056
+ var(--color-surface) 0%,
5057
+ transparent 100%
5058
+ );
5059
+ }
5060
+
5061
+ .Vq0y9ggAxtI-::before {
5062
+ top: calc(24px * -1);
5063
+ top: calc(var(--modal--sticky-region-fade-size) * -1);
5064
+ background: linear-gradient(
5065
+ to top,
5066
+ rgba(255, 255, 255, 1) 0%,
5067
+ transparent 100%
5068
+ );
5069
+ background: linear-gradient(
5070
+ to top,
5071
+ var(--color-surface) 0%,
5072
+ transparent 100%
5073
+ );
5074
+ }
5075
+
5019
5076
  .OiqCKNmbHZ0-:focus-visible {
5020
5077
  box-shadow: 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px hsl(198, 12%, 57%);
5021
5078
  box-shadow: var(--shadow-focus);
@@ -5029,6 +5086,12 @@ a._7BLGtYNuJOU-.zgRx3ehZ2z8-:hover {
5029
5086
  overflow-y: auto;
5030
5087
  }
5031
5088
 
5089
+ ._6dGKr5VVf9Y- .zM9SWwAd5Dg- {
5090
+ -ms-flex: 1 1 auto;
5091
+ flex: 1 1 auto;
5092
+ min-height: 0;
5093
+ }
5094
+
5032
5095
  /* Adjust `Content` and `Tab` components public padding to match the modal */
5033
5096
 
5034
5097
  .zM9SWwAd5Dg- > * {
@@ -5074,8 +5137,8 @@ a._7BLGtYNuJOU-.zgRx3ehZ2z8-:hover {
5074
5137
  16px;
5075
5138
  padding: var(--modal--padding);
5076
5139
  padding-top: 0;
5077
- -ms-flex: 1 1 100%;
5078
- flex: 1 1 100%;
5140
+ -ms-flex: 0 0 auto;
5141
+ flex: 0 0 auto;
5079
5142
  -ms-flex-pack: end;
5080
5143
  justify-content: flex-end;
5081
5144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components",
3
- "version": "6.119.0",
3
+ "version": "6.120.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -552,5 +552,5 @@
552
552
  "> 1%",
553
553
  "IE 10"
554
554
  ],
555
- "gitHead": "e618aa8d9b5da2a7817060e16efb2f3ec2899b70"
555
+ "gitHead": "7bfcb010e0da7d73f3772ec5708229d63822bc01"
556
556
  }