@transferwise/components 46.73.0 → 46.74.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.
Files changed (71) hide show
  1. package/build/common/bottomSheet/BottomSheet.js +3 -1
  2. package/build/common/bottomSheet/BottomSheet.js.map +1 -1
  3. package/build/common/bottomSheet/BottomSheet.mjs +3 -1
  4. package/build/common/bottomSheet/BottomSheet.mjs.map +1 -1
  5. package/build/common/responsivePanel/ResponsivePanel.js +7 -1
  6. package/build/common/responsivePanel/ResponsivePanel.js.map +1 -1
  7. package/build/common/responsivePanel/ResponsivePanel.mjs +7 -1
  8. package/build/common/responsivePanel/ResponsivePanel.mjs.map +1 -1
  9. package/build/dimmer/Dimmer.js +3 -1
  10. package/build/dimmer/Dimmer.js.map +1 -1
  11. package/build/dimmer/Dimmer.mjs +3 -1
  12. package/build/dimmer/Dimmer.mjs.map +1 -1
  13. package/build/drawer/Drawer.js +2 -0
  14. package/build/drawer/Drawer.js.map +1 -1
  15. package/build/drawer/Drawer.mjs +2 -0
  16. package/build/drawer/Drawer.mjs.map +1 -1
  17. package/build/modal/Modal.js +3 -0
  18. package/build/modal/Modal.js.map +1 -1
  19. package/build/modal/Modal.mjs +3 -0
  20. package/build/modal/Modal.mjs.map +1 -1
  21. package/build/popover/Popover.js +6 -1
  22. package/build/popover/Popover.js.map +1 -1
  23. package/build/popover/Popover.mjs +7 -2
  24. package/build/popover/Popover.mjs.map +1 -1
  25. package/build/types/common/bottomSheet/BottomSheet.d.ts +1 -1
  26. package/build/types/common/bottomSheet/BottomSheet.d.ts.map +1 -1
  27. package/build/types/common/responsivePanel/ResponsivePanel.d.ts.map +1 -1
  28. package/build/types/dimmer/Dimmer.d.ts +2 -1
  29. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  30. package/build/types/drawer/Drawer.d.ts +2 -1
  31. package/build/types/drawer/Drawer.d.ts.map +1 -1
  32. package/build/types/modal/Modal.d.ts +2 -1
  33. package/build/types/modal/Modal.d.ts.map +1 -1
  34. package/build/types/popover/Popover.d.ts +6 -4
  35. package/build/types/popover/Popover.d.ts.map +1 -1
  36. package/build/types/uploadInput/UploadInput.d.ts +9 -0
  37. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  38. package/build/types/uploadInput/uploadItem/UploadItem.d.ts +16 -1
  39. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  40. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
  41. package/build/uploadInput/UploadInput.js +71 -66
  42. package/build/uploadInput/UploadInput.js.map +1 -1
  43. package/build/uploadInput/UploadInput.mjs +72 -67
  44. package/build/uploadInput/UploadInput.mjs.map +1 -1
  45. package/build/uploadInput/uploadItem/UploadItem.js +13 -4
  46. package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
  47. package/build/uploadInput/uploadItem/UploadItem.mjs +13 -4
  48. package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
  49. package/build/uploadInput/uploadItem/UploadItemLink.js +1 -0
  50. package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
  51. package/build/uploadInput/uploadItem/UploadItemLink.mjs +1 -0
  52. package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
  53. package/package.json +3 -3
  54. package/src/common/bottomSheet/BottomSheet.tsx +4 -2
  55. package/src/common/responsivePanel/ResponsivePanel.tsx +13 -3
  56. package/src/dimmer/Dimmer.spec.js +8 -0
  57. package/src/dimmer/Dimmer.tsx +4 -0
  58. package/src/drawer/Drawer.spec.js +25 -6
  59. package/src/drawer/Drawer.tsx +3 -1
  60. package/src/modal/Modal.spec.js +19 -1
  61. package/src/modal/Modal.tsx +4 -0
  62. package/src/popover/Popover.spec.tsx +64 -21
  63. package/src/popover/Popover.story.tsx +54 -42
  64. package/src/popover/Popover.tsx +12 -5
  65. package/src/popover/__snapshots__/Popover.spec.tsx.snap +2 -0
  66. package/src/uploadInput/UploadInput.spec.tsx +121 -9
  67. package/src/uploadInput/UploadInput.tests.story.tsx +207 -140
  68. package/src/uploadInput/UploadInput.tsx +110 -77
  69. package/src/uploadInput/uploadItem/UploadItem.spec.tsx +1 -0
  70. package/src/uploadInput/uploadItem/UploadItem.tsx +30 -6
  71. package/src/uploadInput/uploadItem/UploadItemLink.tsx +9 -1
@@ -1 +1 @@
1
- {"version":3,"file":"UploadItem.mjs","sources":["../../../src/uploadInput/uploadItem/UploadItem.tsx"],"sourcesContent":["import { Bin, CheckCircleFill, CrossCircleFill } from '@transferwise/icons';\nimport { clsx } from 'clsx';\nimport { forwardRef, useImperativeHandle, useRef } from 'react';\nimport { useIntl } from 'react-intl';\n\nimport Body from '../../body';\nimport { Size, Status, Typography, Sentiment } from '../../common';\nimport ProcessIndicator from '../../processIndicator/ProcessIndicator';\nimport StatusIcon from '../../statusIcon/StatusIcon';\nimport { UploadedFile, UploadError } from '../types';\n\nimport MESSAGES from './UploadItem.messages';\nimport { UploadItemLink } from './UploadItemLink';\n\nexport type UploadItemProps = React.JSX.IntrinsicAttributes & {\n file: UploadedFile;\n /**\n * Is this Item part of a multiple- or single-file UploadInput\n */\n singleFileUpload: boolean;\n canDelete: boolean;\n onDelete: () => void;\n\n /**\n * Callback to be called when the file link is clicked.\n * When provided, you need to manually trigger actions to load/download the file.\n *\n * @param file\n */\n onDownload?: (file: UploadedFile) => void;\n ref?: React.Ref<UploadItemRef>;\n};\n\ninterface UploadItemRef {\n focus: () => void;\n}\n\nexport enum TEST_IDS {\n uploadItem = 'uploadItem',\n mediaBody = 'mediaBody',\n}\n\nconst UploadItem = forwardRef<UploadItemRef, UploadItemProps>(\n ({ file, canDelete, onDelete, onDownload, singleFileUpload }, ref) => {\n const { formatMessage } = useIntl();\n const { status, filename, error, errors, url } = file;\n const linkRef = useRef<HTMLAnchorElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n useImperativeHandle<UploadItemRef, UploadItemRef>(ref, () => ({\n focus: (): void => {\n if (url) {\n linkRef.current?.focus();\n } else {\n buttonRef.current?.focus();\n }\n },\n }));\n\n const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;\n\n /**\n * We're temporarily reverting to the regular icon components,\n * until the StatusIcon receives 24px sizing. Some misalignment\n * to be expected.\n */\n const getIcon = () => {\n if (error || errors?.length || status === Status.FAILED) {\n return <CrossCircleFill size={24} className=\"emphasis--negative\" />;\n }\n\n let processIndicator: React.ReactNode;\n\n switch (status) {\n case Status.PROCESSING:\n case Status.PENDING:\n processIndicator = (\n <ProcessIndicator size={Size.EXTRA_SMALL} status={Status.PROCESSING} />\n );\n break;\n case Status.SUCCEEDED:\n case Status.DONE:\n default:\n processIndicator = <CheckCircleFill size={24} className=\"emphasis--positive\" />;\n }\n\n return processIndicator;\n };\n\n const getErrorMessage = (error?: UploadError) =>\n typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);\n\n const getMultipleErrors = (errors?: UploadError[]) => {\n if (!errors?.length) {\n return null;\n }\n\n if (errors?.length === 1) {\n return getErrorMessage(errors[0]);\n }\n\n return (\n <ul className=\"np-upload-input-errors m-b-0\">\n {errors.map((error, index) => {\n // eslint-disable-next-line react/no-array-index-key\n return <li key={index}>{getErrorMessage(error)}</li>;\n })}\n </ul>\n );\n };\n\n const getDescription = () => {\n if (error || errors?.length || status === Status.FAILED) {\n return (\n <Body type={Typography.BODY_DEFAULT_BOLD} className=\"np-upload-input__text text-negative\">\n {errors?.length ? getMultipleErrors(errors) : getErrorMessage(error)}\n </Body>\n );\n }\n\n switch (status) {\n case Status.PENDING:\n return (\n <Body type={Typography.BODY_DEFAULT} className=\"np-upload-input__text\">\n {formatMessage(MESSAGES.uploading)}\n </Body>\n );\n case Status.PROCESSING:\n return <Body className=\"np-upload-input__text\">{formatMessage(MESSAGES.deleting)}</Body>;\n case Status.SUCCEEDED:\n case Status.DONE:\n default:\n return (\n <Body type={Typography.BODY_DEFAULT_BOLD} className=\"np-upload-input__text\">\n {formatMessage(MESSAGES.uploaded)}\n </Body>\n );\n }\n };\n\n const getTitle = () => {\n return filename || formatMessage(MESSAGES.uploadedFile);\n };\n\n const onDownloadFile = (event: React.MouseEvent): void => {\n if (onDownload) {\n event.preventDefault();\n onDownload(file);\n }\n };\n\n return (\n <div\n className={clsx('np-upload-input__item', { 'is-interactive': isSucceeded && url })}\n data-testid={TEST_IDS.uploadItem}\n >\n <UploadItemLink\n ref={linkRef}\n url={isSucceeded ? url : undefined}\n singleFileUpload={singleFileUpload}\n onDownload={onDownloadFile}\n >\n <span className=\"np-upload-input__icon\">{getIcon()}</span>\n <div className=\"np-upload-input__item-content\" data-testid={TEST_IDS.mediaBody}>\n <Body type={Typography.BODY_LARGE} className=\"np-upload-input__title text-word-break\">\n {getTitle()}\n </Body>\n {getDescription()}\n </div>\n </UploadItemLink>\n {canDelete && (\n <div className=\"np-upload-input__item-action\">\n <button\n ref={buttonRef}\n aria-label={formatMessage(MESSAGES.removeFile, { filename })}\n className=\"np-upload-input__item-button\"\n type=\"button\"\n onClick={() => onDelete()}\n >\n <Bin size={16} />\n </button>\n </div>\n )}\n </div>\n );\n },\n);\n\nUploadItem.displayName = 'UploadItem';\n\nexport default UploadItem;\n"],"names":["TEST_IDS","UploadItem","forwardRef","file","canDelete","onDelete","onDownload","singleFileUpload","ref","formatMessage","useIntl","status","filename","error","errors","url","linkRef","useRef","buttonRef","useImperativeHandle","focus","current","isSucceeded","Status","SUCCEEDED","undefined","includes","getIcon","length","FAILED","_jsx","CrossCircleFill","size","className","processIndicator","PROCESSING","PENDING","ProcessIndicator","Size","EXTRA_SMALL","DONE","CheckCircleFill","getErrorMessage","message","MESSAGES","uploadingFailed","getMultipleErrors","children","map","index","getDescription","Body","type","Typography","BODY_DEFAULT_BOLD","BODY_DEFAULT","uploading","deleting","uploaded","getTitle","uploadedFile","onDownloadFile","event","preventDefault","_jsxs","clsx","uploadItem","UploadItemLink","mediaBody","BODY_LARGE","removeFile","onClick","Bin","displayName"],"mappings":"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"}
1
+ {"version":3,"file":"UploadItem.mjs","sources":["../../../src/uploadInput/uploadItem/UploadItem.tsx"],"sourcesContent":["import { Bin, CheckCircleFill, CrossCircleFill } from '@transferwise/icons';\nimport { clsx } from 'clsx';\nimport { forwardRef, useImperativeHandle, useRef } from 'react';\nimport { useIntl } from 'react-intl';\n\nimport Body from '../../body';\nimport { Size, Status, Typography } from '../../common';\nimport ProcessIndicator from '../../processIndicator/ProcessIndicator';\nimport { UploadedFile, UploadError } from '../types';\n\nimport MESSAGES from './UploadItem.messages';\nimport { UploadItemLink } from './UploadItemLink';\n\nexport type UploadItemProps = React.JSX.IntrinsicAttributes & {\n file: UploadedFile;\n /**\n * Is this Item part of a multiple- or single-file UploadInput\n */\n singleFileUpload: boolean;\n canDelete: boolean;\n onDelete: () => void;\n onFocus: () => void;\n\n /**\n * Callback to be called when the file link is clicked.\n * When provided, you need to manually trigger actions to load/download the file.\n *\n * @param file\n */\n onDownload?: (file: UploadedFile) => void;\n ref?: React.Ref<UploadItemRef>;\n};\ninterface UploadItemRef {\n /**\n * A method to set focus on the upload item.\n * @returns {void}\n */\n focus: () => void;\n\n /**\n * A required unique identifier for the upload item.\n */\n id: string | number;\n\n /**\n * An optional status of the upload item.\n */\n status?: string;\n}\n\nexport enum TEST_IDS {\n uploadItem = 'uploadItem',\n mediaBody = 'mediaBody',\n link = 'link',\n action = 'action',\n}\n\nconst UploadItem = forwardRef<UploadItemRef, UploadItemProps>(\n ({ file, canDelete, onDelete, onDownload, singleFileUpload, onFocus: handleFocus }, ref) => {\n const { formatMessage } = useIntl();\n const { status, filename, error, errors, url } = file;\n const linkRef = useRef<HTMLAnchorElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n useImperativeHandle<UploadItemRef, UploadItemRef>(ref, () => ({\n focus: (): void => {\n if (url) {\n linkRef.current?.focus();\n } else {\n buttonRef.current?.focus();\n }\n },\n id: file.id,\n status: file.status,\n }));\n\n const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;\n\n /**\n * We're temporarily reverting to the regular icon components,\n * until the StatusIcon receives 24px sizing. Some misalignment\n * to be expected.\n */\n const getIcon = () => {\n if (error || errors?.length || status === Status.FAILED) {\n return <CrossCircleFill size={24} className=\"emphasis--negative\" />;\n }\n\n let processIndicator: React.ReactNode;\n\n switch (status) {\n case Status.PROCESSING:\n case Status.PENDING:\n processIndicator = (\n <ProcessIndicator size={Size.EXTRA_SMALL} status={Status.PROCESSING} />\n );\n break;\n case Status.SUCCEEDED:\n case Status.DONE:\n default:\n processIndicator = <CheckCircleFill size={24} className=\"emphasis--positive\" />;\n }\n\n return processIndicator;\n };\n\n const getErrorMessage = (error?: UploadError) =>\n typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);\n\n const getMultipleErrors = (errors?: UploadError[]) => {\n if (!errors?.length) {\n return null;\n }\n\n if (errors?.length === 1) {\n return getErrorMessage(errors[0]);\n }\n\n return (\n <ul className=\"np-upload-input-errors m-b-0\">\n {errors.map((error, index) => {\n // eslint-disable-next-line react/no-array-index-key\n return <li key={index}>{getErrorMessage(error)}</li>;\n })}\n </ul>\n );\n };\n\n const getDescription = () => {\n if (error || errors?.length || status === Status.FAILED) {\n return (\n <Body type={Typography.BODY_DEFAULT_BOLD} className=\"np-upload-input__text text-negative\">\n {errors?.length ? getMultipleErrors(errors) : getErrorMessage(error)}\n </Body>\n );\n }\n\n switch (status) {\n case Status.PENDING:\n return (\n <Body type={Typography.BODY_DEFAULT} className=\"np-upload-input__text\">\n {formatMessage(MESSAGES.uploading)}\n </Body>\n );\n case Status.PROCESSING:\n return <Body className=\"np-upload-input__text\">{formatMessage(MESSAGES.deleting)}</Body>;\n case Status.SUCCEEDED:\n case Status.DONE:\n default:\n return (\n <Body type={Typography.BODY_DEFAULT_BOLD} className=\"np-upload-input__text\">\n {formatMessage(MESSAGES.uploaded)}\n </Body>\n );\n }\n };\n\n const getTitle = () => {\n return filename || formatMessage(MESSAGES.uploadedFile);\n };\n\n const onDownloadFile = (event: React.MouseEvent): void => {\n if (onDownload) {\n event.preventDefault();\n onDownload(file);\n }\n };\n\n return (\n <div\n className={clsx('np-upload-input__item', { 'is-interactive': isSucceeded && url })}\n data-testid={`${file.id}-${TEST_IDS.uploadItem}`}\n >\n <UploadItemLink\n ref={linkRef}\n url={isSucceeded ? url : undefined}\n singleFileUpload={singleFileUpload}\n data-testid={`${file.id}-${TEST_IDS.link}`}\n onDownload={onDownloadFile}\n >\n <span className=\"np-upload-input__icon\">{getIcon()}</span>\n <div\n className=\"np-upload-input__item-content\"\n data-testid={`${file.id}-${TEST_IDS.mediaBody}`}\n >\n <Body type={Typography.BODY_LARGE} className=\"np-upload-input__title text-word-break\">\n {getTitle()}\n </Body>\n {getDescription()}\n </div>\n </UploadItemLink>\n {canDelete && (\n <div className=\"np-upload-input__item-action\">\n <button\n ref={buttonRef}\n aria-label={formatMessage(MESSAGES.removeFile, { filename })}\n className=\"np-upload-input__item-button\"\n type=\"button\"\n tabIndex={0}\n data-testid={`${file.id}-${TEST_IDS.action}`}\n onClick={() => onDelete()}\n onFocus={handleFocus}\n >\n <Bin size={16} />\n </button>\n </div>\n )}\n </div>\n );\n },\n);\n\nUploadItem.displayName = 'UploadItem';\n\nexport default UploadItem;\n"],"names":["TEST_IDS","UploadItem","forwardRef","file","canDelete","onDelete","onDownload","singleFileUpload","onFocus","handleFocus","ref","formatMessage","useIntl","status","filename","error","errors","url","linkRef","useRef","buttonRef","useImperativeHandle","focus","current","id","isSucceeded","Status","SUCCEEDED","undefined","includes","getIcon","length","FAILED","_jsx","CrossCircleFill","size","className","processIndicator","PROCESSING","PENDING","ProcessIndicator","Size","EXTRA_SMALL","DONE","CheckCircleFill","getErrorMessage","message","MESSAGES","uploadingFailed","getMultipleErrors","children","map","index","getDescription","Body","type","Typography","BODY_DEFAULT_BOLD","BODY_DEFAULT","uploading","deleting","uploaded","getTitle","uploadedFile","onDownloadFile","event","preventDefault","_jsxs","clsx","uploadItem","UploadItemLink","link","mediaBody","BODY_LARGE","removeFile","tabIndex","action","onClick","Bin","displayName"],"mappings":"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"}
@@ -23,6 +23,7 @@ const UploadItemLink = /*#__PURE__*/React.forwardRef(({
23
23
  target: "_blank",
24
24
  rel: "noopener noreferrer",
25
25
  className: clsx.clsx('np-upload-input__item-link', singleFileUpload ? 'np-upload-input__item-link--single-file' : ''),
26
+ tabIndex: 0,
26
27
  onClick: onDownload,
27
28
  children: children
28
29
  });
@@ -1 +1 @@
1
- {"version":3,"file":"UploadItemLink.js","sources":["../../../src/uploadInput/uploadItem/UploadItemLink.tsx"],"sourcesContent":["import { PropsWithChildren, MouseEvent, forwardRef } from 'react';\nimport { clsx } from 'clsx';\n\ntype UploadItemLinkProps = PropsWithChildren<{\n url?: string;\n onDownload?: (event: MouseEvent) => void;\n singleFileUpload: boolean;\n}>;\n\nexport const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, UploadItemLinkProps>(\n ({ children, url, onDownload, singleFileUpload }, ref) => {\n if (!url) {\n return <div ref={ref as React.RefObject<HTMLDivElement>} className={clsx('np-upload-input__item-container')}>{children}</div>;\n }\n\n return (\n <a\n ref={ref as React.RefObject<HTMLAnchorElement>}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={clsx(\n 'np-upload-input__item-link',\n singleFileUpload ? 'np-upload-input__item-link--single-file' : '',\n )}\n onClick={onDownload}\n >\n {children}\n </a>\n );\n },\n);\n"],"names":["UploadItemLink","forwardRef","children","url","onDownload","singleFileUpload","ref","_jsx","className","clsx","href","target","rel","onClick"],"mappings":";;;;;;AASaA,MAAAA,cAAc,gBAAGC,gBAAU,CACtC,CAAC;EAAEC,QAAQ;EAAEC,GAAG;EAAEC,UAAU;AAAEC,EAAAA,gBAAAA;CAAkB,EAAEC,GAAG,KAAI;EACvD,IAAI,CAACH,GAAG,EAAE;AACR,IAAA,oBAAOI,cAAA,CAAA,KAAA,EAAA;AAAKD,MAAAA,GAAG,EAAEA,GAAuC;AAACE,MAAAA,SAAS,EAAEC,SAAI,CAAC,iCAAiC,CAAE;AAAAP,MAAAA,QAAA,EAAEA,QAAAA;AAAQ,KAAM,CAAC,CAAA;AAC/H,GAAA;AAEA,EAAA,oBACEK,cAAA,CAAA,GAAA,EAAA;AACED,IAAAA,GAAG,EAAEA,GAA0C;AAC/CI,IAAAA,IAAI,EAAEP,GAAI;AACVQ,IAAAA,MAAM,EAAC,QAAQ;AACfC,IAAAA,GAAG,EAAC,qBAAqB;IACzBJ,SAAS,EAAEC,SAAI,CACb,4BAA4B,EAC5BJ,gBAAgB,GAAG,yCAAyC,GAAG,EAAE,CACjE;AACFQ,IAAAA,OAAO,EAAET,UAAW;AAAAF,IAAAA,QAAA,EAEnBA,QAAAA;AAAQ,GACR,CAAC,CAAA;AAER,CAAC;;;;"}
1
+ {"version":3,"file":"UploadItemLink.js","sources":["../../../src/uploadInput/uploadItem/UploadItemLink.tsx"],"sourcesContent":["import { PropsWithChildren, MouseEvent, forwardRef } from 'react';\nimport { clsx } from 'clsx';\n\ntype UploadItemLinkProps = PropsWithChildren<{\n url?: string;\n onDownload?: (event: MouseEvent) => void;\n singleFileUpload: boolean;\n}>;\n\nexport const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, UploadItemLinkProps>(\n ({ children, url, onDownload, singleFileUpload }, ref) => {\n if (!url) {\n return (\n <div\n ref={ref as React.RefObject<HTMLDivElement>}\n className={clsx('np-upload-input__item-container')}\n >\n {children}\n </div>\n );\n }\n\n return (\n <a\n ref={ref as React.RefObject<HTMLAnchorElement>}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={clsx(\n 'np-upload-input__item-link',\n singleFileUpload ? 'np-upload-input__item-link--single-file' : '',\n )}\n tabIndex={0}\n onClick={onDownload}\n >\n {children}\n </a>\n );\n },\n);\n"],"names":["UploadItemLink","forwardRef","children","url","onDownload","singleFileUpload","ref","_jsx","className","clsx","href","target","rel","tabIndex","onClick"],"mappings":";;;;;;AASaA,MAAAA,cAAc,gBAAGC,gBAAU,CACtC,CAAC;EAAEC,QAAQ;EAAEC,GAAG;EAAEC,UAAU;AAAEC,EAAAA,gBAAAA;CAAkB,EAAEC,GAAG,KAAI;EACvD,IAAI,CAACH,GAAG,EAAE;AACR,IAAA,oBACEI,cAAA,CAAA,KAAA,EAAA;AACED,MAAAA,GAAG,EAAEA,GAAuC;AAC5CE,MAAAA,SAAS,EAAEC,SAAI,CAAC,iCAAiC,CAAE;AAAAP,MAAAA,QAAA,EAElDA,QAAAA;AAAQ,KACN,CAAC,CAAA;AAEV,GAAA;AAEA,EAAA,oBACEK,cAAA,CAAA,GAAA,EAAA;AACED,IAAAA,GAAG,EAAEA,GAA0C;AAC/CI,IAAAA,IAAI,EAAEP,GAAI;AACVQ,IAAAA,MAAM,EAAC,QAAQ;AACfC,IAAAA,GAAG,EAAC,qBAAqB;IACzBJ,SAAS,EAAEC,SAAI,CACb,4BAA4B,EAC5BJ,gBAAgB,GAAG,yCAAyC,GAAG,EAAE,CACjE;AACFQ,IAAAA,QAAQ,EAAE,CAAE;AACZC,IAAAA,OAAO,EAAEV,UAAW;AAAAF,IAAAA,QAAA,EAEnBA,QAAAA;AAAQ,GACR,CAAC,CAAA;AAER,CAAC;;;;"}
@@ -21,6 +21,7 @@ const UploadItemLink = /*#__PURE__*/forwardRef(({
21
21
  target: "_blank",
22
22
  rel: "noopener noreferrer",
23
23
  className: clsx('np-upload-input__item-link', singleFileUpload ? 'np-upload-input__item-link--single-file' : ''),
24
+ tabIndex: 0,
24
25
  onClick: onDownload,
25
26
  children: children
26
27
  });
@@ -1 +1 @@
1
- {"version":3,"file":"UploadItemLink.mjs","sources":["../../../src/uploadInput/uploadItem/UploadItemLink.tsx"],"sourcesContent":["import { PropsWithChildren, MouseEvent, forwardRef } from 'react';\nimport { clsx } from 'clsx';\n\ntype UploadItemLinkProps = PropsWithChildren<{\n url?: string;\n onDownload?: (event: MouseEvent) => void;\n singleFileUpload: boolean;\n}>;\n\nexport const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, UploadItemLinkProps>(\n ({ children, url, onDownload, singleFileUpload }, ref) => {\n if (!url) {\n return <div ref={ref as React.RefObject<HTMLDivElement>} className={clsx('np-upload-input__item-container')}>{children}</div>;\n }\n\n return (\n <a\n ref={ref as React.RefObject<HTMLAnchorElement>}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={clsx(\n 'np-upload-input__item-link',\n singleFileUpload ? 'np-upload-input__item-link--single-file' : '',\n )}\n onClick={onDownload}\n >\n {children}\n </a>\n );\n },\n);\n"],"names":["UploadItemLink","forwardRef","children","url","onDownload","singleFileUpload","ref","_jsx","className","clsx","href","target","rel","onClick"],"mappings":";;;;AASaA,MAAAA,cAAc,gBAAGC,UAAU,CACtC,CAAC;EAAEC,QAAQ;EAAEC,GAAG;EAAEC,UAAU;AAAEC,EAAAA,gBAAAA;CAAkB,EAAEC,GAAG,KAAI;EACvD,IAAI,CAACH,GAAG,EAAE;AACR,IAAA,oBAAOI,GAAA,CAAA,KAAA,EAAA;AAAKD,MAAAA,GAAG,EAAEA,GAAuC;AAACE,MAAAA,SAAS,EAAEC,IAAI,CAAC,iCAAiC,CAAE;AAAAP,MAAAA,QAAA,EAAEA,QAAAA;AAAQ,KAAM,CAAC,CAAA;AAC/H,GAAA;AAEA,EAAA,oBACEK,GAAA,CAAA,GAAA,EAAA;AACED,IAAAA,GAAG,EAAEA,GAA0C;AAC/CI,IAAAA,IAAI,EAAEP,GAAI;AACVQ,IAAAA,MAAM,EAAC,QAAQ;AACfC,IAAAA,GAAG,EAAC,qBAAqB;IACzBJ,SAAS,EAAEC,IAAI,CACb,4BAA4B,EAC5BJ,gBAAgB,GAAG,yCAAyC,GAAG,EAAE,CACjE;AACFQ,IAAAA,OAAO,EAAET,UAAW;AAAAF,IAAAA,QAAA,EAEnBA,QAAAA;AAAQ,GACR,CAAC,CAAA;AAER,CAAC;;;;"}
1
+ {"version":3,"file":"UploadItemLink.mjs","sources":["../../../src/uploadInput/uploadItem/UploadItemLink.tsx"],"sourcesContent":["import { PropsWithChildren, MouseEvent, forwardRef } from 'react';\nimport { clsx } from 'clsx';\n\ntype UploadItemLinkProps = PropsWithChildren<{\n url?: string;\n onDownload?: (event: MouseEvent) => void;\n singleFileUpload: boolean;\n}>;\n\nexport const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, UploadItemLinkProps>(\n ({ children, url, onDownload, singleFileUpload }, ref) => {\n if (!url) {\n return (\n <div\n ref={ref as React.RefObject<HTMLDivElement>}\n className={clsx('np-upload-input__item-container')}\n >\n {children}\n </div>\n );\n }\n\n return (\n <a\n ref={ref as React.RefObject<HTMLAnchorElement>}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={clsx(\n 'np-upload-input__item-link',\n singleFileUpload ? 'np-upload-input__item-link--single-file' : '',\n )}\n tabIndex={0}\n onClick={onDownload}\n >\n {children}\n </a>\n );\n },\n);\n"],"names":["UploadItemLink","forwardRef","children","url","onDownload","singleFileUpload","ref","_jsx","className","clsx","href","target","rel","tabIndex","onClick"],"mappings":";;;;AASaA,MAAAA,cAAc,gBAAGC,UAAU,CACtC,CAAC;EAAEC,QAAQ;EAAEC,GAAG;EAAEC,UAAU;AAAEC,EAAAA,gBAAAA;CAAkB,EAAEC,GAAG,KAAI;EACvD,IAAI,CAACH,GAAG,EAAE;AACR,IAAA,oBACEI,GAAA,CAAA,KAAA,EAAA;AACED,MAAAA,GAAG,EAAEA,GAAuC;AAC5CE,MAAAA,SAAS,EAAEC,IAAI,CAAC,iCAAiC,CAAE;AAAAP,MAAAA,QAAA,EAElDA,QAAAA;AAAQ,KACN,CAAC,CAAA;AAEV,GAAA;AAEA,EAAA,oBACEK,GAAA,CAAA,GAAA,EAAA;AACED,IAAAA,GAAG,EAAEA,GAA0C;AAC/CI,IAAAA,IAAI,EAAEP,GAAI;AACVQ,IAAAA,MAAM,EAAC,QAAQ;AACfC,IAAAA,GAAG,EAAC,qBAAqB;IACzBJ,SAAS,EAAEC,IAAI,CACb,4BAA4B,EAC5BJ,gBAAgB,GAAG,yCAAyC,GAAG,EAAE,CACjE;AACFQ,IAAAA,QAAQ,EAAE,CAAE;AACZC,IAAAA,OAAO,EAAEV,UAAW;AAAAF,IAAAA,QAAA,EAEnBA,QAAAA;AAAQ,GACR,CAAC,CAAA;AAER,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transferwise/components",
3
- "version": "46.73.0",
3
+ "version": "46.74.1",
4
4
  "description": "Neptune React components",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -92,8 +92,8 @@
92
92
  "rollup-preserve-directives": "^1.1.1",
93
93
  "storybook": "^8.2.2",
94
94
  "@transferwise/neptune-css": "14.19.1",
95
- "@wise/components-theming": "1.6.1",
96
- "@transferwise/less-config": "3.1.0"
95
+ "@transferwise/less-config": "3.1.0",
96
+ "@wise/components-theming": "1.6.1"
97
97
  },
98
98
  "peerDependencies": {
99
99
  "@transferwise/icons": "^3.13.1",
@@ -32,7 +32,7 @@ export type BottomSheetProps = PropsWithChildren<
32
32
  onClose?: (event: Event | SyntheticEvent) => void;
33
33
  open: boolean;
34
34
  } & CommonProps &
35
- Pick<HTMLAttributes<HTMLDivElement>, 'role' | 'aria-labelledby'>
35
+ Pick<HTMLAttributes<HTMLDivElement>, 'role' | 'aria-labelledby' | 'aria-label'>
36
36
  >;
37
37
 
38
38
  /**
@@ -185,6 +185,7 @@ const BottomSheet = ({ role = 'dialog', ...props }: BottomSheetProps) => {
185
185
  return is400Zoom ? (
186
186
  <Drawer
187
187
  aria-labelledby={props['aria-labelledby']}
188
+ aria-label={props['aria-label']}
188
189
  role={role}
189
190
  open={props.open}
190
191
  className={props.className}
@@ -203,7 +204,8 @@ const BottomSheet = ({ role = 'dialog', ...props }: BottomSheetProps) => {
203
204
  {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
204
205
  <div
205
206
  id={overlayId}
206
- aria-labelledby={props['aria-labelledby']}
207
+ aria-labelledby={props['aria-labelledby'] || undefined}
208
+ aria-label={props['aria-label'] || undefined}
207
209
  role={role}
208
210
  aria-modal
209
211
  onTouchStart={onSwipeStart}
@@ -3,8 +3,7 @@ import { forwardRef } from 'react';
3
3
  import { Position } from '..';
4
4
  import BottomSheet from '../bottomSheet';
5
5
  import { useLayout } from '../hooks';
6
- import Panel from '../panel';
7
- import { PanelProps } from '../panel/Panel';
6
+ import Panel, { type PanelProps } from '../panel';
8
7
 
9
8
  const ResponsivePanel = forwardRef<HTMLDivElement, PanelProps>(function ResponsivePanel(
10
9
  {
@@ -17,13 +16,22 @@ const ResponsivePanel = forwardRef<HTMLDivElement, PanelProps>(function Responsi
17
16
  open = false,
18
17
  position = Position.BOTTOM,
19
18
  anchorWidth = false,
19
+ 'aria-label': ariaLabel,
20
+ 'aria-labelledby': ariaLabelledBy,
20
21
  }: PanelProps,
21
22
  reference,
22
23
  ) {
23
24
  const { isMobile } = useLayout();
24
25
  if (isMobile) {
25
26
  return (
26
- <BottomSheet key="bottomSheet" open={open} className={className} onClose={onClose}>
27
+ <BottomSheet
28
+ key="bottomSheet"
29
+ aria-label={ariaLabel}
30
+ aria-labelledby={ariaLabelledBy}
31
+ open={open}
32
+ className={className}
33
+ onClose={onClose}
34
+ >
27
35
  {children}
28
36
  </BottomSheet>
29
37
  );
@@ -38,6 +46,8 @@ const ResponsivePanel = forwardRef<HTMLDivElement, PanelProps>(function Responsi
38
46
  position={position}
39
47
  anchorWidth={anchorWidth}
40
48
  anchorRef={anchorRef}
49
+ aria-label={ariaLabel}
50
+ aria-labelledby={ariaLabelledBy}
41
51
  className={className}
42
52
  onClose={onClose}
43
53
  >
@@ -15,6 +15,7 @@ describe('Dimmer', () => {
15
15
  children: <div />,
16
16
  className: undefined,
17
17
  onClose: undefined,
18
+ onExited: jest.fn(),
18
19
  };
19
20
 
20
21
  beforeEach(() => {
@@ -76,4 +77,11 @@ describe('Dimmer', () => {
76
77
  exit: 'dimmer--exit dimmer--exit-fade',
77
78
  });
78
79
  });
80
+
81
+ it('calls onExited when the exit animation is finished', () => {
82
+ component.setProps({ fadeContentOnExit: true });
83
+ const transition = component.find('CSSTransition');
84
+ transition.prop('onExited')();
85
+ expect(props.onExited).toHaveBeenCalled();
86
+ });
79
87
  });
@@ -31,6 +31,7 @@ export type DimmerProps = CommonProps & {
31
31
  scrollable?: boolean;
32
32
  transparent?: boolean;
33
33
  onClose?: (event: KeyboardEvent | MouseEvent) => void;
34
+ onExited?: () => void;
34
35
  };
35
36
 
36
37
  export const handleTouchMove = (event: Event) => {
@@ -56,6 +57,7 @@ const Dimmer = ({
56
57
  scrollable = false,
57
58
  transparent = false,
58
59
  onClose,
60
+ onExited: handleExited,
59
61
  }: DimmerProps) => {
60
62
  const [hasNotExited, setHasNotExited] = useState(false);
61
63
  const dimmerReference = useRef<HTMLDivElement>(null);
@@ -101,6 +103,8 @@ const Dimmer = ({
101
103
  if (dimmerReference.current) {
102
104
  dimmerManager.remove(dimmerReference.current);
103
105
  }
106
+
107
+ handleExited?.();
104
108
  };
105
109
 
106
110
  useEffect(() => {
@@ -1,4 +1,4 @@
1
- import { shallow } from 'enzyme';
1
+ import { mount } from 'enzyme';
2
2
 
3
3
  import { mockMatchMedia } from '../test-utils';
4
4
  import Title from '../title/Title';
@@ -12,7 +12,7 @@ jest.useFakeTimers();
12
12
  jest.mock(
13
13
  '../dimmer',
14
14
  () =>
15
- function ({ open, children }) {
15
+ function Dimmer({ open, children }) {
16
16
  return open ? <div className="dimmer">{children}</div> : null;
17
17
  },
18
18
  );
@@ -20,7 +20,7 @@ jest.mock(
20
20
  jest.mock(
21
21
  '../slidingPanel',
22
22
  () =>
23
- function ({ open, children }) {
23
+ function SlidingPanel({ open, children }) {
24
24
  return open ? <div className="sliding-panel">{children}</div> : null;
25
25
  },
26
26
  );
@@ -29,10 +29,10 @@ const defaultLocale = 'en-GB';
29
29
 
30
30
  jest.mock('react-intl', () => ({
31
31
  injectIntl: (Component) =>
32
- function (props) {
32
+ function InjectedComponent(props) {
33
33
  return <Component {...props} intl={{ locale: defaultLocale }} />;
34
34
  },
35
- useIntl: () => ({ locale: defaultLocale, formatMessage: (id) => `${id}` }),
35
+ useIntl: () => ({ locale: defaultLocale, formatMessage: (id) => String(id) }),
36
36
  defineMessages: (translations) => translations,
37
37
  }));
38
38
 
@@ -48,11 +48,12 @@ describe('Drawer', () => {
48
48
  };
49
49
 
50
50
  beforeEach(() => {
51
- component = shallow(<Drawer {...props} />);
51
+ component = mount(<Drawer {...props} />);
52
52
  });
53
53
 
54
54
  afterEach(() => {
55
55
  jest.clearAllMocks();
56
+ component.unmount();
56
57
  });
57
58
 
58
59
  it('renders drawer header if title is provided', () => {
@@ -79,4 +80,22 @@ describe('Drawer', () => {
79
80
  component.setProps({ footerContent: 'SomeContent' });
80
81
  expect(component.find('.np-drawer-footer')).toHaveLength(1);
81
82
  });
83
+
84
+ it('passes onUnmount to Dimmer onExited prop', () => {
85
+ const onUnmount = jest.fn();
86
+ component.setProps({ onUnmount });
87
+ component.setProps({ open: true });
88
+ expect(component.find('Dimmer').prop('onExited')).toBe(onUnmount);
89
+ });
90
+
91
+ it('calls onUnmount when the component unmounts', () => {
92
+ const onUnmount = jest.fn();
93
+ component.setProps({ onUnmount });
94
+ component.setProps({ open: true });
95
+ expect(onUnmount).not.toHaveBeenCalled();
96
+ jest.runAllTimers();
97
+ component.setProps({ open: false });
98
+ component.find('Dimmer').prop('onExited')();
99
+ expect(onUnmount).toHaveBeenCalledTimes(1);
100
+ });
82
101
  });
@@ -24,6 +24,7 @@ export type DrawerProps = {
24
24
  position?: `${Position.LEFT | Position.RIGHT | Position.BOTTOM}`;
25
25
  /** The action to perform on close click. */
26
26
  onClose?: (event: KeyboardEvent | React.MouseEvent) => void;
27
+ onUnmount?: () => void;
27
28
  } & Pick<HTMLAttributes<HTMLDivElement>, 'role' | 'aria-labelledby'>;
28
29
 
29
30
  export default function Drawer({
@@ -32,6 +33,7 @@ export default function Drawer({
32
33
  footerContent,
33
34
  headerTitle,
34
35
  onClose,
36
+ onUnmount,
35
37
  open = false,
36
38
  position = 'right',
37
39
  role = 'dialog',
@@ -48,7 +50,7 @@ export default function Drawer({
48
50
  const overlayId = useContext(OverlayIdContext);
49
51
 
50
52
  return (
51
- <Dimmer open={open} onClose={onClose}>
53
+ <Dimmer open={open} onClose={onClose} onExited={onUnmount}>
52
54
  <SlidingPanel open={open} position={isMobile ? Position.BOTTOM : position}>
53
55
  <div
54
56
  id={overlayId}
@@ -16,7 +16,7 @@ jest.mock('react-intl', () => ({
16
16
  function (props) {
17
17
  return <Component {...props} intl={{ locale: defaultLocale }} />;
18
18
  },
19
- useIntl: () => ({ locale: defaultLocale, formatMessage: (id) => `${id}` }),
19
+ useIntl: () => ({ locale: defaultLocale, formatMessage: (id) => String(id) }),
20
20
  defineMessages: (translations) => translations,
21
21
  }));
22
22
 
@@ -161,6 +161,24 @@ describe('Modal', () => {
161
161
  expect(onClose).toHaveBeenCalledTimes(1);
162
162
  });
163
163
 
164
+ it('passes onUnmount to Dimmer onExited prop', () => {
165
+ const onUnmount = jest.fn();
166
+ component.setProps({ onUnmount });
167
+ component.setProps({ open: true });
168
+ expect(component.find('Dimmer').prop('onExited')).toBe(onUnmount);
169
+ });
170
+
171
+ it('calls onUnmount when the component unmounts', () => {
172
+ const onUnmount = jest.fn();
173
+ component.setProps({ onUnmount });
174
+ component.setProps({ open: true });
175
+ expect(onUnmount).not.toHaveBeenCalled();
176
+ jest.runAllTimers();
177
+ component.setProps({ open: false });
178
+ component.find('Dimmer').prop('onExited')();
179
+ expect(onUnmount).toHaveBeenCalledTimes(1);
180
+ });
181
+
164
182
  const clickCloseButton = () => {
165
183
  component.find('.close').simulate('click');
166
184
  };
@@ -32,6 +32,7 @@ export type ModalProps = CommonProps & {
32
32
  footer?: ReactNode;
33
33
  size?: SizeSmall | SizeMedium | SizeLarge | SizeExtraLarge;
34
34
  onClose: () => void;
35
+ onUnmount?: () => void;
35
36
  open: boolean;
36
37
  scroll?: ScrollContent | ScrollViewport;
37
38
  position?: PositionTop | PositionCenter;
@@ -43,6 +44,7 @@ const Modal = ({
43
44
  body,
44
45
  footer = null,
45
46
  onClose,
47
+ onUnmount,
46
48
  className,
47
49
  open,
48
50
  size = Size.MEDIUM,
@@ -71,6 +73,7 @@ const Modal = ({
71
73
  footerContent={footer}
72
74
  position={Position.BOTTOM}
73
75
  onClose={onClose}
76
+ onUnmount={onUnmount}
74
77
  >
75
78
  {body}
76
79
  </Drawer>
@@ -81,6 +84,7 @@ const Modal = ({
81
84
  contentPosition={position}
82
85
  disableClickToClose={disableDimmerClickToClose}
83
86
  onClose={onClose}
87
+ onExited={onUnmount}
84
88
  >
85
89
  <CSSTransition
86
90
  nodeRef={contentReference}
@@ -35,27 +35,70 @@ describe('Popover', () => {
35
35
  expect(getPanel()).toMatchSnapshot();
36
36
  });
37
37
 
38
- it('renders title', async () => {
39
- ({ container, rerender } = render(
40
- <Popover {...props}>
41
- <button type="button">Open</button>
42
- </Popover>,
43
- ));
44
-
45
- await userEvent.click(screen.getByText('Open'));
46
- await waitForPanel();
47
-
48
- expect(getTitle()).toBeInTheDocument();
49
-
50
- rerender(
51
- <Popover {...props} title={undefined}>
52
- <button type="button">Open</button>
53
- </Popover>,
54
- );
55
-
56
- await userEvent.click(screen.getByText('Open'));
57
-
58
- expect(getTitle()).not.toBeInTheDocument();
38
+ describe('title', () => {
39
+ it('renders title', async () => {
40
+ ({ container, rerender } = render(
41
+ <Popover {...props}>
42
+ <button type="button">Open</button>
43
+ </Popover>,
44
+ ));
45
+
46
+ await userEvent.click(screen.getByText('Open'));
47
+ await waitForPanel();
48
+
49
+ expect(getTitle()).toBeInTheDocument();
50
+
51
+ rerender(
52
+ <Popover {...props} title={undefined}>
53
+ <button type="button">Open</button>
54
+ </Popover>,
55
+ );
56
+
57
+ await userEvent.click(screen.getByText('Open'));
58
+
59
+ expect(getTitle()).not.toBeInTheDocument();
60
+ });
61
+
62
+ it('should uses `title` as the accessible name', async () => {
63
+ render(
64
+ <Popover {...props}>
65
+ <button type="button">Open</button>
66
+ </Popover>,
67
+ );
68
+
69
+ await userEvent.click(screen.getByText('Open'));
70
+ await waitForPanel();
71
+ });
72
+
73
+ describe('accessible name', () => {
74
+ const ACCESSIBLE_NAME = 'Accessible name';
75
+
76
+ it('should use `aria-label` as the accessible name', async () => {
77
+ render(
78
+ <Popover {...props} title={undefined} aria-label={ACCESSIBLE_NAME}>
79
+ <button type="button">Open</button>
80
+ </Popover>,
81
+ );
82
+
83
+ await userEvent.click(screen.getByText('Open'));
84
+ await waitForPanel();
85
+
86
+ expect(screen.getByRole('dialog', { name: ACCESSIBLE_NAME })).toBeInTheDocument();
87
+ });
88
+
89
+ it('should prioritise `aria-label` over `title` as the accessible name', async () => {
90
+ render(
91
+ <Popover {...props} aria-label={ACCESSIBLE_NAME}>
92
+ <button type="button">Open</button>
93
+ </Popover>,
94
+ );
95
+
96
+ await userEvent.click(screen.getByText('Open'));
97
+ await waitForPanel();
98
+
99
+ expect(screen.getByRole('dialog', { name: ACCESSIBLE_NAME })).toBeInTheDocument();
100
+ });
101
+ });
59
102
  });
60
103
 
61
104
  it('renders Panel onClick', async () => {
@@ -1,54 +1,66 @@
1
1
  import { action } from '@storybook/addon-actions';
2
- import { select } from '@storybook/addon-knobs';
3
2
 
4
3
  import Button from '../button';
5
4
  import { Position } from '../common';
6
5
 
7
6
  import Popover from './Popover';
7
+ import { Meta, StoryObj } from '@storybook/react';
8
+
9
+ type Story = StoryObj<typeof Popover>;
10
+
11
+ const Content = () => (
12
+ <>
13
+ You’ll get this rate as long as we receive your 10 EUR within the next 51 hours.
14
+ <div>
15
+ <a href="test1">Test 1</a>
16
+ </div>
17
+ <div>
18
+ <a href="test1">Test 2</a>
19
+ </div>
20
+ <div>
21
+ <a href="test1">Test 3</a>
22
+ </div>
23
+ </>
24
+ );
8
25
 
9
26
  export default {
10
27
  component: Popover,
11
28
  title: 'Dialogs/Popover',
12
- };
29
+ tags: ['autodocs'],
30
+ args: {
31
+ preferredPlacement: Position.BOTTOM,
32
+ title: 'Guaranteed rate',
33
+ content: <Content />,
34
+ children: <Button onClick={action(`I'm also triggered`)}>Click here to Open Popover!</Button>,
35
+ },
36
+ argTypes: {
37
+ preferredPlacement: {
38
+ control: 'select',
39
+ options: [Position.TOP, Position.RIGHT, Position.BOTTOM, Position.LEFT],
40
+ },
41
+ },
42
+ } satisfies Meta<typeof Popover>;
13
43
 
14
- export const Basic = () => {
15
- const preferredPlacement = select(
16
- 'preferredPlacement',
17
- [
18
- Position.TOP,
19
- Position.RIGHT,
20
- Position.BOTTOM,
21
- Position.LEFT,
22
- Position.LEFT_TOP,
23
- Position.RIGHT_TOP,
24
- Position.BOTTOM_RIGHT,
25
- Position.BOTTOM_LEFT,
26
- ],
27
- Position.TOP,
28
- );
44
+ export const Basic: Story = {};
29
45
 
30
- return (
31
- <div className="text-xs-center">
32
- <Popover
33
- content={
34
- <>
35
- You’ll get this rate as long as we receive your 10 EUR within the next 51 hours.
36
- <div>
37
- <a href="test1">Test 1</a>
38
- </div>
39
- <div>
40
- <a href="test1">Test 2</a>
41
- </div>
42
- <div>
43
- <a href="test1">Test 3</a>
44
- </div>
45
- </>
46
- }
47
- preferredPlacement={preferredPlacement}
48
- title="Guaranteed rate"
49
- >
50
- <Button onClick={action(`I'm also triggered`)}>Click here to Open Popover!</Button>
51
- </Popover>
52
- </div>
53
- );
54
- };
46
+ /**
47
+ * While it might be easier for sighted users to associate the content
48
+ * of a `Popover` with the surrounding and trigger content, it's likely
49
+ * much harder for people relying on the assistive-tech.
50
+ *
51
+ * For that reason, the `Popover` must always have an accessible name
52
+ * if `title` prop is set, the component will use it automatically,
53
+ * otherwise `aria-label` must be provided instead.
54
+ *
55
+ * **NB:** If both props are provided, then screen readers will prioritise
56
+ * `aria-label` over `title`.
57
+ */
58
+ export const WithoutVisibleTitle: Story = {
59
+ args: {
60
+ preferredPlacement: Position.BOTTOM,
61
+ title: undefined,
62
+ 'aria-label': 'Guaranteed rate',
63
+ content: <Content />,
64
+ children: <Button onClick={action(`I'm also triggered`)}>Click here to Open Popover!</Button>,
65
+ },
66
+ } satisfies Meta<typeof Popover>;