@jobber/components 9.8.0 → 9.10.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.
@@ -14,5 +14,11 @@ export interface DataTableSortableHeaderProps {
14
14
  * When undefined, the header renders as non-interactive.
15
15
  */
16
16
  readonly onSort?: () => void;
17
+ /**
18
+ * The horizontal alignment of the header content and sort icon.
19
+ *
20
+ * @default "start"
21
+ */
22
+ readonly align?: "start" | "center" | "end";
17
23
  }
18
24
  export declare function DataTableSortableHeader(props: DataTableSortableHeaderProps & React.HTMLAttributes<HTMLTableCellElement>): React.JSX.Element;
@@ -39,7 +39,7 @@ function SortIcon({ direction }) {
39
39
  React.createElement("path", { className: finalStyle.downArrowStyle, d: "M8.29292 13.3339C8.68345 12.9434 9.31661 12.9434 9.70714 13.3339L12.2968 15.8573L14.8787 13.3339C15.2692 12.9434 15.9024 12.9434 16.2929 13.3339C16.6834 13.7244 16.6834 14.3576 16.2929 14.7481L13 18.041C12.6095 18.4315 11.9763 18.4315 11.5858 18.041L8.29292 14.7481C7.9024 14.3576 7.9024 13.7244 8.29292 13.3339Z" }))));
40
40
  }
41
41
 
42
- var styles = {"container":"_1-DPmbxOkZ8-","tableElement":"kJeYcsgg700-","header":"_5d2OcsTVgzU-","headerCell":"LTx0tCU3F2I-","sortableButton":"WTQCrd2d8nw-","focusArea":"RipbH3rRU-E-","row":"sJQbtw-NEHU-","clickableRow":"J2p1EeT252A-","cell":"_1UpLYTK64uY-","footer":"qe-6UrAFLTo-","actions":"_2fZsLSaNPnI-","rowActions":"pElxuwpGmCI-","pagination":"thJ-CSoe0ps-","spinning":"EBpmC7cZzCs-"};
42
+ var styles = {"container":"_1-DPmbxOkZ8-","tableElement":"kJeYcsgg700-","header":"_5d2OcsTVgzU-","headerCell":"LTx0tCU3F2I-","sortableButton":"WTQCrd2d8nw-","focusArea":"RipbH3rRU-E-","alignCenter":"w4UHxTf-Z0Q-","alignEnd":"O7R9Tt281nY-","row":"sJQbtw-NEHU-","clickableRow":"J2p1EeT252A-","cell":"_1UpLYTK64uY-","footer":"qe-6UrAFLTo-","actions":"_2fZsLSaNPnI-","rowActions":"pElxuwpGmCI-","pagination":"thJ-CSoe0ps-","spinning":"EBpmC7cZzCs-"};
43
43
 
44
44
  function DataTableActions(props) {
45
45
  return (React.createElement("div", Object.assign({}, props, { className: classnames(styles.actions, props.className) }), props.children));
@@ -94,13 +94,18 @@ function DataTableRowActions(props) {
94
94
  }
95
95
 
96
96
  function DataTableSortableHeader(props) {
97
- const { children, direction, onSort } = props, rest = tslib_es6.__rest(props, ["children", "direction", "onSort"]);
97
+ const { children, direction, onSort, align = "start" } = props, rest = tslib_es6.__rest(props, ["children", "direction", "onSort", "align"]);
98
98
  const isSortable = direction !== undefined && onSort !== undefined;
99
+ const alignmentClass = {
100
+ start: undefined,
101
+ center: styles.alignCenter,
102
+ end: styles.alignEnd,
103
+ }[align];
99
104
  if (!isSortable) {
100
- return React.createElement(DataTableHeaderCell, Object.assign({}, rest), children);
105
+ return (React.createElement(DataTableHeaderCell, Object.assign({}, rest, { className: classnames(rest.className, alignmentClass) }), children));
101
106
  }
102
107
  return (React.createElement(DataTableHeaderCell, Object.assign({}, rest),
103
- React.createElement("button", { type: "button", onClick: onSort, className: styles.sortableButton },
108
+ React.createElement("button", { type: "button", onClick: onSort, className: classnames(styles.sortableButton, alignmentClass) },
104
109
  React.createElement("span", { className: styles.focusArea },
105
110
  children,
106
111
  React.createElement(SortIcon, { direction: direction })))));
@@ -37,7 +37,7 @@ function SortIcon({ direction }) {
37
37
  React__default.createElement("path", { className: finalStyle.downArrowStyle, d: "M8.29292 13.3339C8.68345 12.9434 9.31661 12.9434 9.70714 13.3339L12.2968 15.8573L14.8787 13.3339C15.2692 12.9434 15.9024 12.9434 16.2929 13.3339C16.6834 13.7244 16.6834 14.3576 16.2929 14.7481L13 18.041C12.6095 18.4315 11.9763 18.4315 11.5858 18.041L8.29292 14.7481C7.9024 14.3576 7.9024 13.7244 8.29292 13.3339Z" }))));
38
38
  }
39
39
 
40
- var styles = {"container":"_1-DPmbxOkZ8-","tableElement":"kJeYcsgg700-","header":"_5d2OcsTVgzU-","headerCell":"LTx0tCU3F2I-","sortableButton":"WTQCrd2d8nw-","focusArea":"RipbH3rRU-E-","row":"sJQbtw-NEHU-","clickableRow":"J2p1EeT252A-","cell":"_1UpLYTK64uY-","footer":"qe-6UrAFLTo-","actions":"_2fZsLSaNPnI-","rowActions":"pElxuwpGmCI-","pagination":"thJ-CSoe0ps-","spinning":"EBpmC7cZzCs-"};
40
+ var styles = {"container":"_1-DPmbxOkZ8-","tableElement":"kJeYcsgg700-","header":"_5d2OcsTVgzU-","headerCell":"LTx0tCU3F2I-","sortableButton":"WTQCrd2d8nw-","focusArea":"RipbH3rRU-E-","alignCenter":"w4UHxTf-Z0Q-","alignEnd":"O7R9Tt281nY-","row":"sJQbtw-NEHU-","clickableRow":"J2p1EeT252A-","cell":"_1UpLYTK64uY-","footer":"qe-6UrAFLTo-","actions":"_2fZsLSaNPnI-","rowActions":"pElxuwpGmCI-","pagination":"thJ-CSoe0ps-","spinning":"EBpmC7cZzCs-"};
41
41
 
42
42
  function DataTableActions(props) {
43
43
  return (React__default.createElement("div", Object.assign({}, props, { className: classnames(styles.actions, props.className) }), props.children));
@@ -92,13 +92,18 @@ function DataTableRowActions(props) {
92
92
  }
93
93
 
94
94
  function DataTableSortableHeader(props) {
95
- const { children, direction, onSort } = props, rest = __rest(props, ["children", "direction", "onSort"]);
95
+ const { children, direction, onSort, align = "start" } = props, rest = __rest(props, ["children", "direction", "onSort", "align"]);
96
96
  const isSortable = direction !== undefined && onSort !== undefined;
97
+ const alignmentClass = {
98
+ start: undefined,
99
+ center: styles.alignCenter,
100
+ end: styles.alignEnd,
101
+ }[align];
97
102
  if (!isSortable) {
98
- return React__default.createElement(DataTableHeaderCell, Object.assign({}, rest), children);
103
+ return (React__default.createElement(DataTableHeaderCell, Object.assign({}, rest, { className: classnames(rest.className, alignmentClass) }), children));
99
104
  }
100
105
  return (React__default.createElement(DataTableHeaderCell, Object.assign({}, rest),
101
- React__default.createElement("button", { type: "button", onClick: onSort, className: styles.sortableButton },
106
+ React__default.createElement("button", { type: "button", onClick: onSort, className: classnames(styles.sortableButton, alignmentClass) },
102
107
  React__default.createElement("span", { className: styles.focusArea },
103
108
  children,
104
109
  React__default.createElement(SortIcon, { direction: direction })))));
@@ -1,5 +1,6 @@
1
1
  import type { IconNames } from "../Icon";
2
2
  import type { FileUpload } from "../InputFile";
3
+ import type { PresentedImage } from "../LightBox";
3
4
  export type Sizes = "small" | "base" | "large" | "extraLarge";
4
5
  export type FileIconsNames = Extract<IconNames, "file" | "excel" | "pdf" | "video" | "word">;
5
6
  export interface GalleryProps {
@@ -23,7 +24,7 @@ export interface GalleryProps {
23
24
  */
24
25
  onDelete?(file: GalleryFile): void;
25
26
  }
26
- export interface GalleryFile extends Pick<FileUpload, "key" | "name" | "type" | "size" | "progress"> {
27
+ export interface GalleryFile extends Pick<FileUpload, "key" | "name" | "type" | "size" | "progress">, Pick<PresentedImage, "downloadOptions"> {
27
28
  /**
28
29
  * The thumbnail url of the file.
29
30
  */
@@ -34,6 +34,24 @@ require('../_baseFor-cjs.js');
34
34
  require('../keysIn-cjs.js');
35
35
  require('../_isIterateeCall-cjs.js');
36
36
  require('../_setToString-cjs.js');
37
+ require('../Menu-cjs.js');
38
+ require('../useFormFieldFocus-cjs.js');
39
+ require('../maxHeight-cjs.js');
40
+ require('../useRenderElement-cjs.js');
41
+ require('../buttonRenderAdapter-cjs.js');
42
+ require('../OverlaySeparator-cjs.js');
43
+ require('../Separator-cjs.js');
44
+ require('../BottomSheet-cjs.js');
45
+ require('../ScrollAreaViewport-cjs.js');
46
+ require('../useScrollLock-cjs.js');
47
+ require('../scrollEdges-cjs.js');
48
+ require('../HelperText-cjs.js');
49
+ require('../SelectPrimitive-cjs.js');
50
+ require('../mergeClassName-cjs.js');
51
+ require('../SelectGroupLabel-cjs.js');
52
+ require('../useCompositeListItem-cjs.js');
53
+ require('../DrawerTitle-cjs.js');
54
+ require('../MenuSubmenuTrigger-cjs.js');
37
55
  require('../FormatFile-cjs.js');
38
56
  require('filesize');
39
57
  require('../Thumbnail-cjs.js');
@@ -1,2 +1,3 @@
1
1
  export { Gallery } from "./Gallery";
2
2
  export type { GalleryFile, GalleryProps } from "./GalleryTypes";
3
+ export type { LightBoxDownloadOption as GalleryDownloadOption } from "../LightBox";
@@ -32,6 +32,24 @@ import '../_baseFor-es.js';
32
32
  import '../keysIn-es.js';
33
33
  import '../_isIterateeCall-es.js';
34
34
  import '../_setToString-es.js';
35
+ import '../Menu-es.js';
36
+ import '../useFormFieldFocus-es.js';
37
+ import '../maxHeight-es.js';
38
+ import '../useRenderElement-es.js';
39
+ import '../buttonRenderAdapter-es.js';
40
+ import '../OverlaySeparator-es.js';
41
+ import '../Separator-es.js';
42
+ import '../BottomSheet-es.js';
43
+ import '../ScrollAreaViewport-es.js';
44
+ import '../useScrollLock-es.js';
45
+ import '../scrollEdges-es.js';
46
+ import '../HelperText-es.js';
47
+ import '../SelectPrimitive-es.js';
48
+ import '../mergeClassName-es.js';
49
+ import '../SelectGroupLabel-es.js';
50
+ import '../useCompositeListItem-es.js';
51
+ import '../DrawerTitle-es.js';
52
+ import '../MenuSubmenuTrigger-es.js';
35
53
  import '../FormatFile-es.js';
36
54
  import 'filesize';
37
55
  import '../Thumbnail-es.js';
@@ -69,7 +69,11 @@ function generateImagesArray(files) {
69
69
  let imageIndex = 0;
70
70
  for (let i = 0; i < files.length; i++) {
71
71
  if (Thumbnail.isPreviewableImage(files[i].type)) {
72
- images.push({ title: files[i].name, url: yield getFileSrc(files[i]) });
72
+ images.push({
73
+ title: files[i].name,
74
+ url: yield getFileSrc(files[i]),
75
+ downloadOptions: files[i].downloadOptions,
76
+ });
73
77
  filesToImageIndex.push(imageIndex);
74
78
  imageIndex++;
75
79
  }
@@ -67,7 +67,11 @@ function generateImagesArray(files) {
67
67
  let imageIndex = 0;
68
68
  for (let i = 0; i < files.length; i++) {
69
69
  if (isPreviewableImage(files[i].type)) {
70
- images.push({ title: files[i].name, url: yield getFileSrc(files[i]) });
70
+ images.push({
71
+ title: files[i].name,
72
+ url: yield getFileSrc(files[i]),
73
+ downloadOptions: files[i].downloadOptions,
74
+ });
71
75
  filesToImageIndex.push(imageIndex);
72
76
  imageIndex++;
73
77
  }
@@ -21,6 +21,17 @@ declare function LightBoxOverlay({ className }: {
21
21
  * and a close button. Styled for dark backgrounds.
22
22
  */
23
23
  declare function LightBoxToolbar(): React.JSX.Element;
24
+ /**
25
+ * Downloads one of the consumer-provided formats for the currently visible
26
+ * image. Included in the default `LightBox.Toolbar` when options are supplied.
27
+ */
28
+ declare function LightBoxDownloadAction({ ariaLabel, }: {
29
+ /**
30
+ * Accessible label for the action, also used as the tooltip message.
31
+ * @default "Download as"
32
+ */
33
+ readonly ariaLabel?: string;
34
+ }): React.JSX.Element | null;
24
35
  /**
25
36
  * Opens the current image in a new browser tab. Included in the default
26
37
  * `LightBox.Toolbar`. Returns `null` if the current image has no `url`.
@@ -141,6 +152,7 @@ declare namespace LightBox {
141
152
  var Background: typeof LightBoxBackground;
142
153
  var Overlay: typeof LightBoxOverlay;
143
154
  var Toolbar: typeof LightBoxToolbar;
155
+ var DownloadAction: typeof LightBoxDownloadAction;
144
156
  var OpenInNewTabAction: typeof LightBoxOpenInNewTabAction;
145
157
  var Slides: typeof LightBoxSlides;
146
158
  var Navigation: typeof LightBoxNavigation;
@@ -15,6 +15,10 @@ export declare const lightBoxMessages: {
15
15
  en: string;
16
16
  es: string;
17
17
  };
18
+ downloadAs: {
19
+ en: string;
20
+ es: string;
21
+ };
18
22
  openInNewTab: {
19
23
  en: string;
20
24
  es: string;
@@ -1,10 +1,28 @@
1
1
  import type { CSSProperties, ReactNode, RefObject } from "react";
2
2
  import type { PanInfo } from "framer-motion";
3
+ export interface LightBoxDownloadOption {
4
+ /**
5
+ * The label shown in the Download as menu.
6
+ */
7
+ readonly label: string;
8
+ /**
9
+ * The URL activated when this format is downloaded.
10
+ */
11
+ readonly url: string;
12
+ /**
13
+ * Supplemental, non-interactive information shown beside the label.
14
+ */
15
+ readonly detail?: string;
16
+ }
3
17
  export interface PresentedImage {
4
18
  title?: string;
5
19
  caption?: string;
6
20
  alt?: string;
7
21
  url: string;
22
+ /**
23
+ * Optional formats available from the Download as action.
24
+ */
25
+ downloadOptions?: LightBoxDownloadOption[];
8
26
  }
9
27
  export interface RequestCloseOptions {
10
28
  lastPosition: number;
@@ -33,6 +33,24 @@ require('../_baseFor-cjs.js');
33
33
  require('../keysIn-cjs.js');
34
34
  require('../_isIterateeCall-cjs.js');
35
35
  require('../_setToString-cjs.js');
36
+ require('../Menu-cjs.js');
37
+ require('../useFormFieldFocus-cjs.js');
38
+ require('../maxHeight-cjs.js');
39
+ require('../useRenderElement-cjs.js');
40
+ require('../buttonRenderAdapter-cjs.js');
41
+ require('../OverlaySeparator-cjs.js');
42
+ require('../Separator-cjs.js');
43
+ require('../BottomSheet-cjs.js');
44
+ require('../ScrollAreaViewport-cjs.js');
45
+ require('../useScrollLock-cjs.js');
46
+ require('../scrollEdges-cjs.js');
47
+ require('../HelperText-cjs.js');
48
+ require('../SelectPrimitive-cjs.js');
49
+ require('../mergeClassName-cjs.js');
50
+ require('../SelectGroupLabel-cjs.js');
51
+ require('../useCompositeListItem-cjs.js');
52
+ require('../DrawerTitle-cjs.js');
53
+ require('../MenuSubmenuTrigger-cjs.js');
36
54
 
37
55
 
38
56
 
@@ -1,3 +1,3 @@
1
1
  export { LightBox } from "./LightBox";
2
- export type { LightBoxProps, LightBoxProviderProps, PresentedImage, } from "./LightBox.types";
2
+ export type { LightBoxDownloadOption, LightBoxProps, LightBoxProviderProps, PresentedImage, } from "./LightBox.types";
3
3
  export { useLightBoxContext } from "./LightBoxContext";
@@ -31,3 +31,21 @@ import '../_baseFor-es.js';
31
31
  import '../keysIn-es.js';
32
32
  import '../_isIterateeCall-es.js';
33
33
  import '../_setToString-es.js';
34
+ import '../Menu-es.js';
35
+ import '../useFormFieldFocus-es.js';
36
+ import '../maxHeight-es.js';
37
+ import '../useRenderElement-es.js';
38
+ import '../buttonRenderAdapter-es.js';
39
+ import '../OverlaySeparator-es.js';
40
+ import '../Separator-es.js';
41
+ import '../BottomSheet-es.js';
42
+ import '../ScrollAreaViewport-es.js';
43
+ import '../useScrollLock-es.js';
44
+ import '../scrollEdges-es.js';
45
+ import '../HelperText-es.js';
46
+ import '../SelectPrimitive-es.js';
47
+ import '../mergeClassName-es.js';
48
+ import '../SelectGroupLabel-es.js';
49
+ import '../useCompositeListItem-es.js';
50
+ import '../DrawerTitle-es.js';
51
+ import '../MenuSubmenuTrigger-es.js';
@@ -14,8 +14,9 @@ var Button = require('./Button-cjs.js');
14
14
  var Heading = require('./Heading-cjs.js');
15
15
  var Tooltip = require('./Tooltip-cjs.js');
16
16
  var AtlantisThemeContext = require('./AtlantisThemeContext-cjs.js');
17
+ var Menu = require('./Menu-cjs.js');
17
18
 
18
- var styles = {"backgroundImage":"i9Tw1T65W-k-","next":"Q8amcRaTGf0-","prev":"W9FVb24yJrk-","buttonHidden":"nsN0TPWsBXI-","buttonVisible":"dkLYp7AD2jE-","lightboxWrapper":"_5p2iAj4JfoE-","toolbar":"rMK4cKdOxFw-","closeButton":"_0m6vb11DgiA-","slideNumber":"kCc68gGuTgg-","leftAction":"_3DY1o57MHxg-","image":"yYFVVScosfQ-","imageArea":"UskuwLHR6fg-","captionWrapper":"OGjhge-r-U4-","title":"tZU2g-NYdIs-","blurOverlay":"GKIdLTmvcvQ-","thumbnailBar":"_3TfQLQEE3GQ-","thumbnailImage":"eBMzUOlcfQ4-","thumbnail":"eapm2zruLn8-","selected":"PeLn2u-QB0k-","spinning":"_8tDoqjgfLcw-"};
19
+ var styles = {"backgroundImage":"i9Tw1T65W-k-","next":"Q8amcRaTGf0-","prev":"W9FVb24yJrk-","buttonHidden":"nsN0TPWsBXI-","buttonVisible":"dkLYp7AD2jE-","lightboxWrapper":"_5p2iAj4JfoE-","toolbar":"rMK4cKdOxFw-","closeButton":"_0m6vb11DgiA-","slideNumber":"kCc68gGuTgg-","leftActions":"OunDmtPpa9g-","downloadOptionContent":"TPSJq-8p0vo-","downloadOptionLabel":"vZU6ZSl8QLI-","downloadOptionDetail":"vubHTSF4aYA-","image":"yYFVVScosfQ-","imageArea":"UskuwLHR6fg-","captionWrapper":"OGjhge-r-U4-","title":"tZU2g-NYdIs-","blurOverlay":"GKIdLTmvcvQ-","thumbnailBar":"_3TfQLQEE3GQ-","thumbnailImage":"eBMzUOlcfQ4-","thumbnail":"eapm2zruLn8-","selected":"PeLn2u-QB0k-","spinning":"_8tDoqjgfLcw-"};
19
20
 
20
21
  // A little bit more than the transition's duration
21
22
  // We're doing this to prevent a bug from framer-motion
@@ -188,6 +189,10 @@ const lightBoxMessages = useAtlantisI18n.defineMessages({
188
189
  en: "Next image",
189
190
  es: "Imagen siguiente",
190
191
  },
192
+ downloadAs: {
193
+ en: "Download as",
194
+ es: "Descargar como",
195
+ },
191
196
  openInNewTab: {
192
197
  en: "Open image in new tab",
193
198
  es: "Abrir imagen en nueva pestaña",
@@ -249,12 +254,36 @@ function LightBoxToolbar() {
249
254
  const { t } = useAtlantisI18n.useAtlantisI18n(lightBoxMessages);
250
255
  return (React.createElement(AtlantisThemeContext.AtlantisThemeContextProvider, { dangerouslyOverrideTheme: "dark" },
251
256
  React.createElement("div", { className: styles.toolbar },
252
- React.createElement(LightBoxOpenInNewTabAction, null),
257
+ React.createElement("div", { className: styles.leftActions },
258
+ React.createElement(LightBox.DownloadAction, null),
259
+ React.createElement(LightBoxOpenInNewTabAction, null)),
253
260
  React.createElement("div", { className: styles.slideNumber },
254
261
  React.createElement(Text.Text, null, `${currentImageIndex + 1}/${images.length}`)),
255
262
  React.createElement("div", { className: styles.closeButton },
256
263
  React.createElement(ButtonDismiss.ButtonDismiss, { ariaLabel: t("close"), onClick: handleRequestClose })))));
257
264
  }
265
+ /**
266
+ * Downloads one of the consumer-provided formats for the currently visible
267
+ * image. Included in the default `LightBox.Toolbar` when options are supplied.
268
+ */
269
+ function LightBoxDownloadAction({ ariaLabel, }) {
270
+ var _a;
271
+ const { images, currentImageIndex } = useLightBoxContext();
272
+ const { t } = useAtlantisI18n.useAtlantisI18n(lightBoxMessages);
273
+ const downloadOptions = (_a = images[currentImageIndex]) === null || _a === void 0 ? void 0 : _a.downloadOptions;
274
+ const effectiveAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : t("downloadAs");
275
+ if (!(downloadOptions === null || downloadOptions === void 0 ? void 0 : downloadOptions.length))
276
+ return null;
277
+ return (React.createElement(Menu.Menu, null,
278
+ React.createElement(Tooltip.Tooltip, { message: effectiveAriaLabel },
279
+ React.createElement(Menu.Menu.Trigger, null,
280
+ React.createElement(Button.Button, { size: "small", variation: "subtle", type: "secondary", icon: "download", ariaLabel: effectiveAriaLabel }))),
281
+ React.createElement(Menu.Menu.Content, { style: { width: "240px" }, preferredPlacement: "bottom" }, downloadOptions.map(option => (React.createElement(Menu.Menu.Item, { key: `${option.label}-${option.url}`, textValue: [option.label, option.detail].filter(Boolean).join(" "), href: option.url },
282
+ React.createElement(Menu.Menu.ItemLabel, null,
283
+ React.createElement("span", { className: styles.downloadOptionContent },
284
+ React.createElement("span", { className: styles.downloadOptionLabel }, option.label),
285
+ option.detail ? (React.createElement("span", { className: styles.downloadOptionDetail }, option.detail)) : null))))))));
286
+ }
258
287
  /**
259
288
  * Opens the current image in a new browser tab. Included in the default
260
289
  * `LightBox.Toolbar`. Returns `null` if the current image has no `url`.
@@ -266,9 +295,8 @@ function LightBoxOpenInNewTabAction({ ariaLabel, }) {
266
295
  const currentImage = images[currentImageIndex];
267
296
  if (!(currentImage === null || currentImage === void 0 ? void 0 : currentImage.url))
268
297
  return null;
269
- return (React.createElement("div", { className: styles.leftAction },
270
- React.createElement(Tooltip.Tooltip, { message: effectiveAriaLabel },
271
- React.createElement(Button.Button, { size: "small", variation: "subtle", type: "secondary", icon: "export", ariaLabel: effectiveAriaLabel, url: currentImage.url, external: true }))));
298
+ return (React.createElement(Tooltip.Tooltip, { message: effectiveAriaLabel },
299
+ React.createElement(Button.Button, { size: "small", variation: "subtle", type: "secondary", icon: "export", ariaLabel: effectiveAriaLabel, url: currentImage.url, external: true })));
272
300
  }
273
301
  /**
274
302
  * The animated hero image with swipe-to-navigate and slide animation.
@@ -420,6 +448,7 @@ LightBox.Content = LightBoxContent;
420
448
  LightBox.Background = LightBoxBackground;
421
449
  LightBox.Overlay = LightBoxOverlay;
422
450
  LightBox.Toolbar = LightBoxToolbar;
451
+ LightBox.DownloadAction = LightBoxDownloadAction;
423
452
  LightBox.OpenInNewTabAction = LightBoxOpenInNewTabAction;
424
453
  LightBox.Slides = LightBoxSlides;
425
454
  LightBox.Navigation = LightBoxNavigation;
@@ -12,8 +12,9 @@ import { B as Button } from './Button-es.js';
12
12
  import { H as Heading } from './Heading-es.js';
13
13
  import { T as Tooltip } from './Tooltip-es.js';
14
14
  import { A as AtlantisThemeContextProvider } from './AtlantisThemeContext-es.js';
15
+ import { M as Menu } from './Menu-es.js';
15
16
 
16
- var styles = {"backgroundImage":"i9Tw1T65W-k-","next":"Q8amcRaTGf0-","prev":"W9FVb24yJrk-","buttonHidden":"nsN0TPWsBXI-","buttonVisible":"dkLYp7AD2jE-","lightboxWrapper":"_5p2iAj4JfoE-","toolbar":"rMK4cKdOxFw-","closeButton":"_0m6vb11DgiA-","slideNumber":"kCc68gGuTgg-","leftAction":"_3DY1o57MHxg-","image":"yYFVVScosfQ-","imageArea":"UskuwLHR6fg-","captionWrapper":"OGjhge-r-U4-","title":"tZU2g-NYdIs-","blurOverlay":"GKIdLTmvcvQ-","thumbnailBar":"_3TfQLQEE3GQ-","thumbnailImage":"eBMzUOlcfQ4-","thumbnail":"eapm2zruLn8-","selected":"PeLn2u-QB0k-","spinning":"_8tDoqjgfLcw-"};
17
+ var styles = {"backgroundImage":"i9Tw1T65W-k-","next":"Q8amcRaTGf0-","prev":"W9FVb24yJrk-","buttonHidden":"nsN0TPWsBXI-","buttonVisible":"dkLYp7AD2jE-","lightboxWrapper":"_5p2iAj4JfoE-","toolbar":"rMK4cKdOxFw-","closeButton":"_0m6vb11DgiA-","slideNumber":"kCc68gGuTgg-","leftActions":"OunDmtPpa9g-","downloadOptionContent":"TPSJq-8p0vo-","downloadOptionLabel":"vZU6ZSl8QLI-","downloadOptionDetail":"vubHTSF4aYA-","image":"yYFVVScosfQ-","imageArea":"UskuwLHR6fg-","captionWrapper":"OGjhge-r-U4-","title":"tZU2g-NYdIs-","blurOverlay":"GKIdLTmvcvQ-","thumbnailBar":"_3TfQLQEE3GQ-","thumbnailImage":"eBMzUOlcfQ4-","thumbnail":"eapm2zruLn8-","selected":"PeLn2u-QB0k-","spinning":"_8tDoqjgfLcw-"};
17
18
 
18
19
  // A little bit more than the transition's duration
19
20
  // We're doing this to prevent a bug from framer-motion
@@ -186,6 +187,10 @@ const lightBoxMessages = defineMessages({
186
187
  en: "Next image",
187
188
  es: "Imagen siguiente",
188
189
  },
190
+ downloadAs: {
191
+ en: "Download as",
192
+ es: "Descargar como",
193
+ },
189
194
  openInNewTab: {
190
195
  en: "Open image in new tab",
191
196
  es: "Abrir imagen en nueva pestaña",
@@ -247,12 +252,36 @@ function LightBoxToolbar() {
247
252
  const { t } = useAtlantisI18n(lightBoxMessages);
248
253
  return (React__default.createElement(AtlantisThemeContextProvider, { dangerouslyOverrideTheme: "dark" },
249
254
  React__default.createElement("div", { className: styles.toolbar },
250
- React__default.createElement(LightBoxOpenInNewTabAction, null),
255
+ React__default.createElement("div", { className: styles.leftActions },
256
+ React__default.createElement(LightBox.DownloadAction, null),
257
+ React__default.createElement(LightBoxOpenInNewTabAction, null)),
251
258
  React__default.createElement("div", { className: styles.slideNumber },
252
259
  React__default.createElement(Text, null, `${currentImageIndex + 1}/${images.length}`)),
253
260
  React__default.createElement("div", { className: styles.closeButton },
254
261
  React__default.createElement(ButtonDismiss, { ariaLabel: t("close"), onClick: handleRequestClose })))));
255
262
  }
263
+ /**
264
+ * Downloads one of the consumer-provided formats for the currently visible
265
+ * image. Included in the default `LightBox.Toolbar` when options are supplied.
266
+ */
267
+ function LightBoxDownloadAction({ ariaLabel, }) {
268
+ var _a;
269
+ const { images, currentImageIndex } = useLightBoxContext();
270
+ const { t } = useAtlantisI18n(lightBoxMessages);
271
+ const downloadOptions = (_a = images[currentImageIndex]) === null || _a === void 0 ? void 0 : _a.downloadOptions;
272
+ const effectiveAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : t("downloadAs");
273
+ if (!(downloadOptions === null || downloadOptions === void 0 ? void 0 : downloadOptions.length))
274
+ return null;
275
+ return (React__default.createElement(Menu, null,
276
+ React__default.createElement(Tooltip, { message: effectiveAriaLabel },
277
+ React__default.createElement(Menu.Trigger, null,
278
+ React__default.createElement(Button, { size: "small", variation: "subtle", type: "secondary", icon: "download", ariaLabel: effectiveAriaLabel }))),
279
+ React__default.createElement(Menu.Content, { style: { width: "240px" }, preferredPlacement: "bottom" }, downloadOptions.map(option => (React__default.createElement(Menu.Item, { key: `${option.label}-${option.url}`, textValue: [option.label, option.detail].filter(Boolean).join(" "), href: option.url },
280
+ React__default.createElement(Menu.ItemLabel, null,
281
+ React__default.createElement("span", { className: styles.downloadOptionContent },
282
+ React__default.createElement("span", { className: styles.downloadOptionLabel }, option.label),
283
+ option.detail ? (React__default.createElement("span", { className: styles.downloadOptionDetail }, option.detail)) : null))))))));
284
+ }
256
285
  /**
257
286
  * Opens the current image in a new browser tab. Included in the default
258
287
  * `LightBox.Toolbar`. Returns `null` if the current image has no `url`.
@@ -264,9 +293,8 @@ function LightBoxOpenInNewTabAction({ ariaLabel, }) {
264
293
  const currentImage = images[currentImageIndex];
265
294
  if (!(currentImage === null || currentImage === void 0 ? void 0 : currentImage.url))
266
295
  return null;
267
- return (React__default.createElement("div", { className: styles.leftAction },
268
- React__default.createElement(Tooltip, { message: effectiveAriaLabel },
269
- React__default.createElement(Button, { size: "small", variation: "subtle", type: "secondary", icon: "export", ariaLabel: effectiveAriaLabel, url: currentImage.url, external: true }))));
296
+ return (React__default.createElement(Tooltip, { message: effectiveAriaLabel },
297
+ React__default.createElement(Button, { size: "small", variation: "subtle", type: "secondary", icon: "export", ariaLabel: effectiveAriaLabel, url: currentImage.url, external: true })));
270
298
  }
271
299
  /**
272
300
  * The animated hero image with swipe-to-navigate and slide animation.
@@ -418,6 +446,7 @@ LightBox.Content = LightBoxContent;
418
446
  LightBox.Background = LightBoxBackground;
419
447
  LightBox.Overlay = LightBoxOverlay;
420
448
  LightBox.Toolbar = LightBoxToolbar;
449
+ LightBox.DownloadAction = LightBoxDownloadAction;
421
450
  LightBox.OpenInNewTabAction = LightBoxOpenInNewTabAction;
422
451
  LightBox.Slides = LightBoxSlides;
423
452
  LightBox.Navigation = LightBoxNavigation;
@@ -305,6 +305,20 @@ The `direction` prop accepts:
305
305
  When both `direction` and `onSort` are provided, the header becomes interactive
306
306
  with a sort icon. When either is missing, it renders as a regular header cell.
307
307
 
308
+ Use the `align` prop to align the header content and sort icon. This is useful
309
+ for numerical columns, where both the values and the header should be
310
+ end-aligned:
311
+
312
+ ```tsx
313
+ <DataTable.SortableHeader
314
+ align="end"
315
+ direction={currentSort}
316
+ onSort={() => handleColumnSort("totalPrice")}
317
+ >
318
+ Total price
319
+ </DataTable.SortableHeader>
320
+ ```
321
+
308
322
  The `direction` prop represents the current sort state of the column. Your
309
323
  `onSort` callback should handle the state transitions (ascending → descending →
310
324
  equilibrium) based on your application's logic.
@@ -526,6 +540,7 @@ const table = useReactTable({
526
540
 
527
541
  | Prop | Type | Required | Default | Description |
528
542
  |------|------|----------|---------|-------------|
543
+ | `align` | `"center" | "end" | "start"` | No | `"start"` | The horizontal alignment of the header content and sort icon. |
529
544
  | `children` | `ReactNode` | No | — | The header content to display (text, icons, etc.) |
530
545
  | `direction` | `SortDirection` | No | — | The current sort direction for this column. When undefined, the header renders as non-interactive. |
531
546
  | `onSort` | `() => void` | No | — | Callback function triggered when the sortable header is clicked. When undefined, the header renders as non-interactive. |
@@ -24,6 +24,14 @@ beside each other before wrapping subsequent thumbnails to a new line.
24
24
  Each of the thumbnails the Gallery displays are tab navigatable. A hover state
25
25
  will also slightly dim a particular thumbnail when the cursor hovers over it.
26
26
 
27
+ ## Download formats
28
+
29
+ For previewable images, provide `downloadOptions` on a `GalleryFile` to show a
30
+ Download as action in the LightBox toolbar. Each option has a `label` and `url`;
31
+ an optional `detail` is shown alongside the label. The URL is activated only
32
+ when the user selects that option, allowing applications to create transformed
33
+ formats on demand.
34
+
27
35
  ## Mockup
28
36
 
29
37
 
@@ -15,6 +15,13 @@ The LightBox should only be used when displaying an image at a greater scale is
15
15
  the primary function of opening it. Additional content types would be better
16
16
  suited by using a dialogue or alternative method.
17
17
 
18
+ ## Download formats
19
+
20
+ Images can supply `downloadOptions` with a `label`, `url`, and optional
21
+ `detail`. When the current image has options, the toolbar shows Download as
22
+ beside Open image in new tab. The selected URL is activated only after the user
23
+ chooses a format.
24
+
18
25
 
19
26
  ## Configuration
20
27
 
@@ -39,9 +46,10 @@ components, a more robust solution should be considered.
39
46
 
40
47
  LightBox exposes its internal building blocks as subcomponents:
41
48
  `LightBox.Provider`, `LightBox.Content`, `LightBox.Background`,
42
- `LightBox.Overlay`, `LightBox.Toolbar`, `LightBox.Slides`,
43
- `LightBox.Navigation`, `LightBox.Caption`, and `LightBox.Thumbnails`. This gives
44
- you more control over the LightBox's appearance and behaviour.
49
+ `LightBox.Overlay`, `LightBox.Toolbar`, `LightBox.DownloadAction`,
50
+ `LightBox.OpenInNewTabAction`, `LightBox.Slides`, `LightBox.Navigation`,
51
+ `LightBox.Caption`, and `LightBox.Thumbnails`. This gives you more control over
52
+ the LightBox's appearance and behaviour.
45
53
 
46
54
  The thumbnail strip is itself composed of smaller subcomponents you can use
47
55
  directly: `LightBox.ThumbnailBar`, `LightBox.Thumbnail`, and
@@ -219,6 +227,12 @@ window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
219
227
  |------|------|----------|---------|-------------|
220
228
  | `className` | `string` | No | — | |
221
229
 
230
+ #### LightBox.DownloadAction
231
+
232
+ | Prop | Type | Required | Default | Description |
233
+ |------|------|----------|---------|-------------|
234
+ | `ariaLabel` | `string` | No | `"Download as"` | Accessible label for the action, also used as the tooltip message. |
235
+
222
236
  #### LightBox.Navigation
223
237
 
224
238
  | Prop | Type | Required | Default | Description |
package/dist/styles.css CHANGED
@@ -9565,6 +9565,24 @@ a._7BLGtYNuJOU-.zgRx3ehZ2z8-:hover {
9565
9565
  box-shadow: var(--shadow-focus);
9566
9566
  }
9567
9567
 
9568
+ .WTQCrd2d8nw-.w4UHxTf-Z0Q- {
9569
+ -ms-flex-pack: center;
9570
+ justify-content: center;
9571
+ }
9572
+
9573
+ .WTQCrd2d8nw-.O7R9Tt281nY- {
9574
+ -ms-flex-pack: end;
9575
+ justify-content: flex-end;
9576
+ }
9577
+
9578
+ .LTx0tCU3F2I-.w4UHxTf-Z0Q- {
9579
+ text-align: center;
9580
+ }
9581
+
9582
+ .LTx0tCU3F2I-.O7R9Tt281nY- {
9583
+ text-align: right;
9584
+ }
9585
+
9568
9586
  .sJQbtw-NEHU- {
9569
9587
  position: relative;
9570
9588
  }
@@ -11953,10 +11971,30 @@ html.atlantisLightBoxActive {
11953
11971
  grid-column: 2;
11954
11972
  justify-self: center;
11955
11973
  }
11956
- ._3DY1o57MHxg- {
11974
+ .OunDmtPpa9g- {
11957
11975
  grid-column: 1;
11976
+ display: -ms-flexbox;
11977
+ display: flex;
11978
+ gap: 8px;
11979
+ gap: var(--space-small);
11958
11980
  justify-self: start;
11959
11981
  }
11982
+ .TPSJq-8p0vo- {
11983
+ display: -ms-flexbox;
11984
+ display: flex;
11985
+ -ms-flex-align: start;
11986
+ align-items: flex-start;
11987
+ -ms-flex-direction: column;
11988
+ flex-direction: column;
11989
+ }
11990
+ .vZU6ZSl8QLI-,
11991
+ .vubHTSF4aYA- {
11992
+ white-space: nowrap;
11993
+ }
11994
+ .vubHTSF4aYA- {
11995
+ color: var(--color-text--subdued);
11996
+ font-weight: normal;
11997
+ }
11960
11998
  .yYFVVScosfQ- {
11961
11999
  position: absolute;
11962
12000
  width: 100%;
@@ -195,6 +195,7 @@
195
195
  "LightBox.Background",
196
196
  "LightBox.Caption",
197
197
  "LightBox.Content",
198
+ "LightBox.DownloadAction",
198
199
  "LightBox.Navigation",
199
200
  "LightBox.OpenInNewTabAction",
200
201
  "LightBox.Overlay",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components",
3
- "version": "9.8.0",
3
+ "version": "9.10.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -549,5 +549,5 @@
549
549
  "> 1%",
550
550
  "IE 10"
551
551
  ],
552
- "gitHead": "740cda8097c2d00fd68de5ed04e3af839f3f18b9"
552
+ "gitHead": "1b56b3a659458dee5f3fa2893122355b9075c7d0"
553
553
  }