@frontify/guideline-blocks-settings 0.29.17 → 0.30.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/Attachments/AttachmentItem.es.js +73 -79
  3. package/dist/components/Attachments/AttachmentItem.es.js.map +1 -1
  4. package/dist/components/Attachments/Attachments.es.js +103 -96
  5. package/dist/components/Attachments/Attachments.es.js.map +1 -1
  6. package/dist/components/Attachments/AttachmentsButtonTrigger.es.js +21 -0
  7. package/dist/components/Attachments/AttachmentsButtonTrigger.es.js.map +1 -0
  8. package/dist/components/BlockItemWrapper/BlockItemWrapper.es.js +47 -43
  9. package/dist/components/BlockItemWrapper/BlockItemWrapper.es.js.map +1 -1
  10. package/dist/components/BlockItemWrapper/Toolbar/Toolbar.es.js +123 -0
  11. package/dist/components/BlockItemWrapper/Toolbar/Toolbar.es.js.map +1 -0
  12. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachments.es.js +27 -0
  13. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachments.es.js.map +1 -0
  14. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.es.js +12 -0
  15. package/dist/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.es.js.map +1 -0
  16. package/dist/components/BlockItemWrapper/Toolbar/ToolbarSegment.es.js +6 -0
  17. package/dist/components/BlockItemWrapper/Toolbar/ToolbarSegment.es.js.map +1 -0
  18. package/dist/components/BlockItemWrapper/Toolbar/helpers.es.js +26 -0
  19. package/dist/components/BlockItemWrapper/Toolbar/helpers.es.js.map +1 -0
  20. package/dist/hooks/useAttachments.es.js +43 -20
  21. package/dist/hooks/useAttachments.es.js.map +1 -1
  22. package/dist/index.cjs.js +3 -3
  23. package/dist/index.cjs.js.map +1 -1
  24. package/dist/index.d.ts +84 -15
  25. package/dist/index.es.js +103 -100
  26. package/dist/index.umd.js +3 -3
  27. package/dist/index.umd.js.map +1 -1
  28. package/dist/styles.css +1 -1
  29. package/package.json +1 -1
  30. package/src/components/Attachments/AttachmentItem.tsx +2 -13
  31. package/src/components/Attachments/Attachments.tsx +30 -15
  32. package/src/components/Attachments/AttachmentsButtonTrigger.tsx +22 -0
  33. package/src/components/Attachments/types.ts +10 -2
  34. package/src/components/BlockItemWrapper/BlockItemWrapper.tsx +23 -20
  35. package/src/components/BlockItemWrapper/Toolbar/Toolbar.spec.tsx +127 -0
  36. package/src/components/BlockItemWrapper/Toolbar/Toolbar.tsx +133 -0
  37. package/src/components/BlockItemWrapper/Toolbar/ToolbarAttachments.tsx +29 -0
  38. package/src/components/BlockItemWrapper/Toolbar/ToolbarAttachmentsTrigger.tsx +14 -0
  39. package/src/components/BlockItemWrapper/Toolbar/ToolbarSegment.tsx +9 -0
  40. package/src/components/BlockItemWrapper/Toolbar/helpers.ts +33 -0
  41. package/src/components/BlockItemWrapper/Toolbar/index.ts +4 -0
  42. package/src/components/BlockItemWrapper/Toolbar/types.ts +38 -0
  43. package/src/components/BlockItemWrapper/types.ts +11 -34
  44. package/src/hooks/{useAttachments.spec.ts → useAttachments.spec.tsx} +55 -4
  45. package/src/hooks/useAttachments.tsx +95 -0
  46. package/dist/components/BlockItemWrapper/Toolbar.es.js +0 -117
  47. package/dist/components/BlockItemWrapper/Toolbar.es.js.map +0 -1
  48. package/src/components/BlockItemWrapper/Toolbar.tsx +0 -133
  49. package/src/hooks/useAttachments.ts +0 -46
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(147 197 253 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(147 197 253 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.tw-pointer-events-none{pointer-events:none}.tw-pointer-events-auto{pointer-events:auto}.tw-absolute{position:absolute}.tw-relative{position:relative}.tw-bottom-\[calc\(100\%-4px\)\]{bottom:calc(100% - 4px)}.tw-left-0{left:0}.tw-right-\[-3px\]{right:-3px}.tw-top-full{top:100%}.tw-isolate{isolation:isolate}.tw-z-20{z-index:20}.tw-z-\[2\]{z-index:2}.tw-z-\[60\]{z-index:60}.tw-m-0{margin:0}.tw-my-4{margin-top:1rem;margin-bottom:1rem}.-tw-mr-2{margin-right:-.5rem}.tw-mr-2{margin-right:.5rem}.tw-mt-1{margin-top:.25rem}.tw-mt-3{margin-top:.75rem}.tw-mt-5{margin-top:1.25rem}.tw-mt-8{margin-top:2rem}.tw-flex{display:flex}.tw-inline-flex{display:inline-flex}.tw-h-0{height:0px}.tw-h-10{height:2.5rem}.tw-h-6{height:1.5rem}.tw-h-7{height:1.75rem}.tw-h-8{height:2rem}.tw-h-9{height:2.25rem}.tw-h-\[72px\]{height:72px}.tw-h-full{height:100%}.tw-min-h-\[10px\]{min-height:10px}.tw-w-0{width:0px}.tw-w-6{width:1.5rem}.tw-w-8{width:2rem}.tw-w-9{width:2.25rem}.tw-w-\[300px\]{width:300px}.tw-w-fit{width:-moz-fit-content;width:fit-content}.tw-w-full{width:100%}.tw-min-w-0{min-width:0px}.tw-min-w-\[400px\]{min-width:400px}.tw-flex-1{flex:1 1 0%}.tw-flex-auto{flex:1 1 auto}.tw-flex-shrink-0{flex-shrink:0}.tw-rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes tw-pulse{50%{opacity:.5}}.tw-animate-pulse{animation:tw-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.\!tw-cursor-not-allowed{cursor:not-allowed!important}.tw-cursor-grab{cursor:grab}.tw-cursor-grabbing{cursor:grabbing}.tw-cursor-pointer{cursor:pointer}.tw-flex-col{flex-direction:column}.tw-items-start{align-items:flex-start}.tw-items-center{align-items:center}.tw-justify-end{justify-content:flex-end}.tw-justify-center{justify-content:center}.tw-justify-between{justify-content:space-between}.tw-gap-0{gap:0px}.tw-gap-0\.5{gap:.125rem}.tw-gap-1{gap:.25rem}.tw-gap-2{gap:.5rem}.tw-gap-3{gap:.75rem}.tw-gap-\[2px\]{gap:2px}.tw-gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.tw-space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.tw-space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.tw-self-start{align-self:flex-start}.tw-overflow-hidden{overflow:hidden}.tw-overflow-y-auto{overflow-y:auto}.tw-text-ellipsis{text-overflow:ellipsis}.tw-whitespace-nowrap{white-space:nowrap}.tw-whitespace-pre-wrap{white-space:pre-wrap}.tw-rounded{border-radius:var(--radius)}.tw-rounded-full{border-radius:9999px}.tw-rounded-sm{border-radius:.125rem}.tw-border{border-width:var(--line-width)}.tw-border-b{border-bottom-width:var(--line-width)}.tw-border-b-4{border-bottom-width:4px}.tw-border-l-4{border-left-width:4px}.tw-border-t{border-top-width:var(--line-width)}.tw-border-t-4{border-top-width:4px}.tw-border-solid{border-style:solid}.tw-border-dashed{border-style:dashed}.\!tw-border-red-50{--tw-border-opacity: 1 !important;border-color:rgb(255 128 102 / var(--tw-border-opacity))!important}.tw-border-blank-state-line{--tw-border-opacity: 1;border-color:rgb(163 165 165 / var(--tw-border-opacity))}.tw-border-blank-state-line-hover{--tw-border-opacity: 1;border-color:rgb(26 28 28 / var(--tw-border-opacity))}.tw-border-box-selected-inverse{border-color:var(--box-selected-inverse-color)}.tw-border-button-border{border-color:var(--button-border-color)}.tw-border-b-line{border-bottom-color:var(--line-color)}.tw-border-b-transparent{border-bottom-color:transparent}.tw-border-t-black-10{--tw-border-opacity: 1;border-top-color:rgb(234 235 235 / var(--tw-border-opacity))}.tw-border-t-transparent{border-top-color:transparent}.tw-bg-base{background-color:var(--base-color)}.tw-bg-base-alt{background-color:var(--base-color-alt)}.tw-bg-blank-state-pressed-inverse{--tw-bg-opacity: 1;background-color:rgb(241 241 241 / var(--tw-bg-opacity))}.tw-bg-blank-state-shaded-inverse{--tw-bg-opacity: 1;background-color:rgb(250 250 250 / var(--tw-bg-opacity))}.tw-bg-blank-state-weak-inverse{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.tw-bg-box-neutral-hover{background-color:var(--box-neutral-color-hover)}.tw-bg-box-neutral-strong-inverse{background-color:var(--box-neutral-strong-inverse-color)}.tw-bg-box-selected-pressed{background-color:var(--box-selected-color-pressed)}.tw-bg-box-selected-strong{background-color:var(--box-selected-strong-color)}.tw-bg-button-background{background-color:var(--button-background-color)}.tw-bg-button-background-pressed{background-color:var(--button-background-color-pressed)}.tw-bg-red-50{--tw-bg-opacity: 1;background-color:rgb(255 128 102 / var(--tw-bg-opacity))}.tw-bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.tw-p-1{padding:.25rem}.tw-p-1\.5{padding:.375rem}.tw-p-2{padding:.5rem}.tw-p-4{padding:1rem}.tw-p-7{padding:1.75rem}.tw-p-\[6px\]{padding:6px}.tw-px-0{padding-left:0;padding-right:0}.tw-px-2{padding-left:.5rem;padding-right:.5rem}.tw-px-2\.5{padding-left:.625rem;padding-right:.625rem}.tw-px-5{padding-left:1.25rem;padding-right:1.25rem}.tw-px-\[1px\]{padding-left:1px;padding-right:1px}.tw-py-0{padding-top:0;padding-bottom:0}.tw-py-2{padding-top:.5rem;padding-bottom:.5rem}.tw-py-3{padding-top:.75rem;padding-bottom:.75rem}.tw-pb-1{padding-bottom:.25rem}.tw-pb-1\.5{padding-bottom:.375rem}.tw-pl-12{padding-left:3rem}.tw-pl-14{padding-left:3.5rem}.tw-pl-3{padding-left:.75rem}.tw-pl-3\.5{padding-left:.875rem}.tw-pl-7{padding-left:1.75rem}.tw-pr-2{padding-right:.5rem}.tw-pr-2\.5{padding-right:.625rem}.tw-pr-3{padding-right:.75rem}.tw-pr-3\.5{padding-right:.875rem}.tw-pt-1{padding-top:.25rem}.tw-pt-1\.5{padding-top:.375rem}.tw-pt-5{padding-top:1.25rem}.tw-text-left{text-align:left}.tw-text-right{text-align:right}.tw-font-body{font-family:var(--body-family)}.tw-font-sans{font-family:Space Grotesk Frontify,Arial,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol}.tw-text-\[13px\]{font-size:13px}.tw-text-body-small{font-size:var(--body-size-small);line-height:var(--body-size-small-line-height)}.tw-text-s{font-size:.875rem;line-height:1rem}.tw-text-sm{font-size:.875rem;line-height:1.25rem}.tw-text-xs{font-size:.813rem;line-height:1rem}.tw-font-bold{font-weight:700}.tw-font-medium{font-weight:500}.tw-font-normal{font-weight:400}.tw-leading-4{line-height:1rem}.tw-leading-5{line-height:1.25rem}.tw-text-blank-state-shaded{--tw-text-opacity: 1;color:rgb(114 116 116 / var(--tw-text-opacity))}.tw-text-box-neutral-strong{color:var(--box-neutral-strong-color)}.tw-text-box-selected-inverse{color:var(--box-selected-inverse-color)}.tw-text-box-selected-strong-inverse{color:var(--box-selected-strong-inverse-color)}.tw-text-red-60{--tw-text-opacity: 1;color:rgb(255 55 90 / var(--tw-text-opacity))}.tw-text-text{color:var(--text-color)}.tw-text-text-negative{color:var(--text-color-negative)}.tw-text-text-weak{color:var(--text-color-weak)}.tw-opacity-0{opacity:0}.tw-opacity-100{opacity:1}.tw-shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.tw-outline-none{outline:2px solid transparent;outline-offset:2px}.tw-outline{outline-style:solid}.tw-outline-1{outline-width:1px}.tw-outline-offset-1{outline-offset:1px}.tw-outline-offset-2{outline-offset:2px}.tw-outline-offset-\[1px\]{outline-offset:1px}.tw-outline-box-selected-inverse{outline-color:var(--box-selected-inverse-color)}.tw-outline-line{outline-color:var(--line-color)}.tw-outline-violet-60{outline-color:#825fff}.tw-transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.tw-transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.first\:tw-rounded-bl:first-child{border-bottom-left-radius:var(--radius)}.first\:tw-rounded-tl:first-child{border-top-left-radius:var(--radius)}.first\:tw-rounded-tr:first-child{border-top-right-radius:var(--radius)}.last\:tw-rounded-bl:last-child{border-bottom-left-radius:var(--radius)}.last\:tw-rounded-br:last-child{border-bottom-right-radius:var(--radius)}.last\:tw-rounded-tr:last-child{border-top-right-radius:var(--radius)}.focus-within\:tw-opacity-100:focus-within{opacity:1}.focus-within\:tw-outline:focus-within{outline-style:solid}.hover\:tw-border-blank-state-line-hover:hover{--tw-border-opacity: 1;border-color:rgb(26 28 28 / var(--tw-border-opacity))}.hover\:tw-bg-black-10:hover{--tw-bg-opacity: 1;background-color:rgb(234 235 235 / var(--tw-bg-opacity))}.hover\:tw-bg-blank-state-hover-inverse:hover{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.hover\:tw-bg-box-neutral-hover:hover{background-color:var(--box-neutral-color-hover)}.hover\:tw-bg-box-neutral-strong-inverse-hover:hover{background-color:var(--box-neutral-strong-inverse-color-hover)}.hover\:tw-bg-box-selected-hover:hover{background-color:var(--box-selected-color-hover)}.hover\:tw-bg-button-background-hover:hover{background-color:var(--button-background-color-hover)}.hover\:tw-bg-button-background-pressed:hover{background-color:var(--button-background-color-pressed)}.hover\:tw-text-blank-state-hover:hover{--tw-text-opacity: 1;color:rgb(26 28 28 / var(--tw-text-opacity))}.hover\:tw-text-box-neutral-inverse-hover:hover{color:var(--box-neutral-inverse-color-hover)}.hover\:tw-outline:hover{outline-style:solid}.focus-visible\:tw-z-10:focus-visible{z-index:10}.focus-visible\:tw-opacity-100:focus-visible{opacity:1}.focus-visible\:tw-ring-blue:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(94 158 214 / var(--tw-ring-opacity)) }.active\:tw-border-blank-state-line-hover:active{--tw-border-opacity: 1;border-color:rgb(26 28 28 / var(--tw-border-opacity))}.active\:tw-bg-blank-state-pressed-inverse:active{--tw-bg-opacity: 1;background-color:rgb(241 241 241 / var(--tw-bg-opacity))}.active\:tw-bg-box-neutral-strong-inverse-pressed:active{background-color:var(--box-neutral-strong-inverse-color-pressed)}.active\:tw-bg-box-selected-pressed:active{background-color:var(--box-selected-color-pressed)}.active\:tw-bg-button-background-pressed:active{background-color:var(--button-background-color-pressed)}.active\:tw-text-blank-state-pressed:active{--tw-text-opacity: 1;color:rgb(8 8 8 / var(--tw-text-opacity))}.tw-group:hover .group-hover\:tw-text-box-neutral-inverse-hover{color:var(--box-neutral-inverse-color-hover)}.tw-group:hover .group-hover\:tw-opacity-100{opacity:1}.tw-group:focus .group-focus\:tw-opacity-100{opacity:1}.\[\&\:not\(\:first-child\)\]\:tw-border-l-0:not(:first-child){border-left-width:0px}.\[\&\:not\(\:first-child\)\]\:tw-border-t-0:not(:first-child){border-top-width:0px}.\[\&\>\*\]\:tw-pointer-events-none>*{pointer-events:none}
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(147 197 253 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(147 197 253 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.tw-pointer-events-none{pointer-events:none}.tw-pointer-events-auto{pointer-events:auto}.tw-absolute{position:absolute}.tw-relative{position:relative}.tw-bottom-\[calc\(100\%-4px\)\]{bottom:calc(100% - 4px)}.tw-left-0{left:0}.tw-right-\[-3px\]{right:-3px}.tw-top-full{top:100%}.tw-isolate{isolation:isolate}.tw-z-20{z-index:20}.tw-z-\[2\]{z-index:2}.tw-z-\[60\]{z-index:60}.tw-m-0{margin:0}.tw-my-4{margin-top:1rem;margin-bottom:1rem}.-tw-mr-2{margin-right:-.5rem}.tw-mr-2{margin-right:.5rem}.tw-mt-1{margin-top:.25rem}.tw-mt-3{margin-top:.75rem}.tw-mt-5{margin-top:1.25rem}.tw-mt-8{margin-top:2rem}.tw-flex{display:flex}.tw-inline-flex{display:inline-flex}.tw-h-0{height:0px}.tw-h-10{height:2.5rem}.tw-h-6{height:1.5rem}.tw-h-8{height:2rem}.tw-h-9{height:2.25rem}.tw-h-\[26px\]{height:26px}.tw-h-\[72px\]{height:72px}.tw-h-full{height:100%}.tw-min-h-\[10px\]{min-height:10px}.tw-w-0{width:0px}.tw-w-8{width:2rem}.tw-w-9{width:2.25rem}.tw-w-\[300px\]{width:300px}.tw-w-fit{width:-moz-fit-content;width:fit-content}.tw-w-full{width:100%}.tw-min-w-0{min-width:0px}.tw-min-w-\[400px\]{min-width:400px}.tw-flex-1{flex:1 1 0%}.tw-flex-auto{flex:1 1 auto}.tw-flex-none{flex:none}.tw-flex-shrink-0{flex-shrink:0}.tw-rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes tw-pulse{50%{opacity:.5}}.tw-animate-pulse{animation:tw-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.\!tw-cursor-not-allowed{cursor:not-allowed!important}.tw-cursor-grab{cursor:grab}.tw-cursor-grabbing{cursor:grabbing}.tw-cursor-pointer{cursor:pointer}.tw-flex-col{flex-direction:column}.tw-items-start{align-items:flex-start}.tw-items-center{align-items:center}.tw-justify-end{justify-content:flex-end}.tw-justify-center{justify-content:center}.tw-justify-between{justify-content:space-between}.tw-gap-0{gap:0px}.tw-gap-0\.5{gap:.125rem}.tw-gap-1{gap:.25rem}.tw-gap-2{gap:.5rem}.tw-gap-3{gap:.75rem}.tw-gap-px{gap:1px}.tw-gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.tw-space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.tw-space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.tw-divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(var(--line-width) * var(--tw-divide-x-reverse));border-left-width:calc(var(--line-width) * calc(1 - var(--tw-divide-x-reverse)))}.tw-divide-line-strong>:not([hidden])~:not([hidden]){border-color:var(--line-color-strong)}.tw-self-start{align-self:flex-start}.tw-overflow-hidden{overflow:hidden}.tw-overflow-y-auto{overflow-y:auto}.tw-text-ellipsis{text-overflow:ellipsis}.tw-whitespace-nowrap{white-space:nowrap}.tw-whitespace-pre-wrap{white-space:pre-wrap}.tw-rounded{border-radius:var(--radius)}.tw-rounded-full{border-radius:9999px}.tw-rounded-md{border-radius:.375rem}.tw-rounded-sm{border-radius:.125rem}.tw-border{border-width:var(--line-width)}.tw-border-b{border-bottom-width:var(--line-width)}.tw-border-b-4{border-bottom-width:4px}.tw-border-l-4{border-left-width:4px}.tw-border-t{border-top-width:var(--line-width)}.tw-border-t-4{border-top-width:4px}.tw-border-solid{border-style:solid}.tw-border-dashed{border-style:dashed}.\!tw-border-red-50{--tw-border-opacity: 1 !important;border-color:rgb(255 128 102 / var(--tw-border-opacity))!important}.tw-border-blank-state-line{--tw-border-opacity: 1;border-color:rgb(163 165 165 / var(--tw-border-opacity))}.tw-border-blank-state-line-hover{--tw-border-opacity: 1;border-color:rgb(26 28 28 / var(--tw-border-opacity))}.tw-border-button-border{border-color:var(--button-border-color)}.tw-border-line-strong{border-color:var(--line-color-strong)}.tw-border-b-line{border-bottom-color:var(--line-color)}.tw-border-b-transparent{border-bottom-color:transparent}.tw-border-t-black-10{--tw-border-opacity: 1;border-top-color:rgb(234 235 235 / var(--tw-border-opacity))}.tw-border-t-transparent{border-top-color:transparent}.tw-bg-base{background-color:var(--base-color)}.tw-bg-base-alt{background-color:var(--base-color-alt)}.tw-bg-blank-state-pressed-inverse{--tw-bg-opacity: 1;background-color:rgb(241 241 241 / var(--tw-bg-opacity))}.tw-bg-blank-state-shaded-inverse{--tw-bg-opacity: 1;background-color:rgb(250 250 250 / var(--tw-bg-opacity))}.tw-bg-blank-state-weak-inverse{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.tw-bg-box-neutral-hover{background-color:var(--box-neutral-color-hover)}.tw-bg-box-neutral-pressed{background-color:var(--box-neutral-color-pressed)}.tw-bg-box-neutral-strong-inverse{background-color:var(--box-neutral-strong-inverse-color)}.tw-bg-box-selected-strong{background-color:var(--box-selected-strong-color)}.tw-bg-button-background{background-color:var(--button-background-color)}.tw-bg-button-background-pressed{background-color:var(--button-background-color-pressed)}.tw-bg-red-50{--tw-bg-opacity: 1;background-color:rgb(255 128 102 / var(--tw-bg-opacity))}.tw-bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.tw-p-1{padding:.25rem}.tw-p-1\.5{padding:.375rem}.tw-p-2{padding:.5rem}.tw-p-4{padding:1rem}.tw-p-7{padding:1.75rem}.tw-px-0{padding-left:0;padding-right:0}.tw-px-2{padding-left:.5rem;padding-right:.5rem}.tw-px-2\.5{padding-left:.625rem;padding-right:.625rem}.tw-px-5{padding-left:1.25rem;padding-right:1.25rem}.tw-px-px{padding-left:1px;padding-right:1px}.tw-py-0{padding-top:0;padding-bottom:0}.tw-py-2{padding-top:.5rem;padding-bottom:.5rem}.tw-py-3{padding-top:.75rem;padding-bottom:.75rem}.tw-pb-1{padding-bottom:.25rem}.tw-pb-1\.5{padding-bottom:.375rem}.tw-pl-12{padding-left:3rem}.tw-pl-14{padding-left:3.5rem}.tw-pl-3{padding-left:.75rem}.tw-pl-3\.5{padding-left:.875rem}.tw-pl-7{padding-left:1.75rem}.tw-pr-2{padding-right:.5rem}.tw-pr-2\.5{padding-right:.625rem}.tw-pr-3{padding-right:.75rem}.tw-pr-3\.5{padding-right:.875rem}.tw-pt-1{padding-top:.25rem}.tw-pt-1\.5{padding-top:.375rem}.tw-pt-5{padding-top:1.25rem}.tw-text-left{text-align:left}.tw-text-right{text-align:right}.tw-font-body{font-family:var(--body-family)}.tw-font-sans{font-family:Space Grotesk Frontify,Arial,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol}.tw-text-\[13px\]{font-size:13px}.tw-text-body-small{font-size:var(--body-size-small);line-height:var(--body-size-small-line-height)}.tw-text-s{font-size:.875rem;line-height:1rem}.tw-text-sm{font-size:.875rem;line-height:1.25rem}.tw-text-xs{font-size:.813rem;line-height:1rem}.tw-font-bold{font-weight:700}.tw-font-medium{font-weight:500}.tw-font-normal{font-weight:400}.tw-leading-4{line-height:1rem}.tw-leading-5{line-height:1.25rem}.tw-text-blank-state-shaded{--tw-text-opacity: 1;color:rgb(114 116 116 / var(--tw-text-opacity))}.tw-text-box-neutral-inverse{color:var(--box-neutral-inverse-color)}.tw-text-box-neutral-inverse-pressed{color:var(--box-neutral-inverse-color-pressed)}.tw-text-box-neutral-strong{color:var(--box-neutral-strong-color)}.tw-text-box-selected-strong-inverse{color:var(--box-selected-strong-inverse-color)}.tw-text-red-60{--tw-text-opacity: 1;color:rgb(255 55 90 / var(--tw-text-opacity))}.tw-text-text{color:var(--text-color)}.tw-text-text-negative{color:var(--text-color-negative)}.tw-text-text-weak{color:var(--text-color-weak)}.tw-opacity-0{opacity:0}.tw-opacity-100{opacity:1}.tw-shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.tw-shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.tw-outline-none{outline:2px solid transparent;outline-offset:2px}.tw-outline{outline-style:solid}.tw-outline-1{outline-width:1px}.tw-outline-offset-1{outline-offset:1px}.tw-outline-offset-2{outline-offset:2px}.tw-outline-box-selected-inverse{outline-color:var(--box-selected-inverse-color)}.tw-outline-line{outline-color:var(--line-color)}.tw-outline-violet-60{outline-color:#825fff}.tw-transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.tw-transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.first\:tw-rounded-bl:first-child{border-bottom-left-radius:var(--radius)}.first\:tw-rounded-tl:first-child{border-top-left-radius:var(--radius)}.first\:tw-rounded-tr:first-child{border-top-right-radius:var(--radius)}.last\:tw-rounded-bl:last-child{border-bottom-left-radius:var(--radius)}.last\:tw-rounded-br:last-child{border-bottom-right-radius:var(--radius)}.last\:tw-rounded-tr:last-child{border-top-right-radius:var(--radius)}.focus-within\:tw-opacity-100:focus-within{opacity:1}.focus-within\:tw-outline:focus-within{outline-style:solid}.hover\:tw-border-blank-state-line-hover:hover{--tw-border-opacity: 1;border-color:rgb(26 28 28 / var(--tw-border-opacity))}.hover\:tw-bg-black-10:hover{--tw-bg-opacity: 1;background-color:rgb(234 235 235 / var(--tw-bg-opacity))}.hover\:tw-bg-blank-state-hover-inverse:hover{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.hover\:tw-bg-box-neutral-hover:hover{background-color:var(--box-neutral-color-hover)}.hover\:tw-bg-box-neutral-strong-inverse-hover:hover{background-color:var(--box-neutral-strong-inverse-color-hover)}.hover\:tw-bg-button-background-hover:hover{background-color:var(--button-background-color-hover)}.hover\:tw-bg-button-background-pressed:hover{background-color:var(--button-background-color-pressed)}.hover\:tw-text-blank-state-hover:hover{--tw-text-opacity: 1;color:rgb(26 28 28 / var(--tw-text-opacity))}.hover\:tw-text-box-neutral-inverse-hover:hover{color:var(--box-neutral-inverse-color-hover)}.hover\:tw-outline:hover{outline-style:solid}.focus-visible\:tw-z-10:focus-visible{z-index:10}.focus-visible\:tw-opacity-100:focus-visible{opacity:1}.focus-visible\:tw-ring-blue:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(94 158 214 / var(--tw-ring-opacity)) }.active\:tw-cursor-grabbing:active{cursor:grabbing}.active\:tw-border-blank-state-line-hover:active{--tw-border-opacity: 1;border-color:rgb(26 28 28 / var(--tw-border-opacity))}.active\:tw-bg-blank-state-pressed-inverse:active{--tw-bg-opacity: 1;background-color:rgb(241 241 241 / var(--tw-bg-opacity))}.active\:tw-bg-box-neutral-pressed:active{background-color:var(--box-neutral-color-pressed)}.active\:tw-bg-box-neutral-strong-inverse-pressed:active{background-color:var(--box-neutral-strong-inverse-color-pressed)}.active\:tw-bg-button-background-pressed:active{background-color:var(--button-background-color-pressed)}.active\:tw-text-blank-state-pressed:active{--tw-text-opacity: 1;color:rgb(8 8 8 / var(--tw-text-opacity))}.active\:tw-text-box-neutral-inverse-pressed:active{color:var(--box-neutral-inverse-color-pressed)}.tw-group:hover .group-hover\:tw-text-box-neutral-inverse-hover{color:var(--box-neutral-inverse-color-hover)}.tw-group:hover .group-hover\:tw-opacity-100{opacity:1}.tw-group:focus .group-focus\:tw-opacity-100{opacity:1}.\[\&\:not\(\:first-child\)\]\:tw-border-l-0:not(:first-child){border-left-width:0px}.\[\&\:not\(\:first-child\)\]\:tw-border-t-0:not(:first-child){border-top-width:0px}.\[\&\>\*\]\:tw-pointer-events-none>*{pointer-events:none}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontify/guideline-blocks-settings",
3
- "version": "0.29.17",
3
+ "version": "0.30.0",
4
4
  "description": "Provides types and helpers for the guideline block development",
5
5
  "sideEffects": false,
6
6
  "main": "dist/index.umd.js",
@@ -54,6 +54,7 @@ export const AttachmentItem = forwardRef<HTMLButtonElement, AttachmentItemProps>
54
54
  onDelete,
55
55
  onReplaceWithBrowse,
56
56
  onReplaceWithUpload,
57
+ onDownload,
57
58
  },
58
59
  ref,
59
60
  ) => {
@@ -76,25 +77,13 @@ export const AttachmentItem = forwardRef<HTMLButtonElement, AttachmentItemProps>
76
77
  // eslint-disable-next-line react-hooks/exhaustive-deps
77
78
  }, [doneAll, uploadResults]);
78
79
 
79
- const download = (url: string, filename: string) => {
80
- fetch(url).then((response) => {
81
- response.blob().then((blob) => {
82
- const url = URL.createObjectURL(blob);
83
- const a = document.createElement('a');
84
- a.href = url;
85
- a.download = filename;
86
- a.click();
87
- });
88
- });
89
- };
90
-
91
80
  const showLoadingCircle = isLoading || (selectedFiles && !doneAll);
92
81
 
93
82
  return (
94
83
  <button
95
84
  aria-label="Download attachment"
96
85
  data-test-id="attachments-item"
97
- onClick={() => download(item.genericUrl, item.fileName)}
86
+ onClick={() => onDownload?.()}
98
87
  ref={ref}
99
88
  style={{
100
89
  ...transformStyle,
@@ -12,6 +12,7 @@ import {
12
12
  useSensor,
13
13
  useSensors,
14
14
  } from '@dnd-kit/core';
15
+ import { restrictToWindowEdges } from '@dnd-kit/modifiers';
15
16
  import { SortableContext, arrayMove, rectSortingStrategy } from '@dnd-kit/sortable';
16
17
  import { Asset, useAssetUpload, useEditorState } from '@frontify/app-bridge';
17
18
  import {
@@ -19,14 +20,13 @@ import {
19
20
  AssetInputSize,
20
21
  Flyout,
21
22
  FlyoutPlacement,
22
- IconCaretDown12,
23
- IconPaperclip16,
24
23
  LegacyTooltip as Tooltip,
25
24
  TooltipPosition,
26
25
  } from '@frontify/fondue';
26
+
27
27
  import { AttachmentItem, SortableAttachmentItem } from './AttachmentItem';
28
- import { AttachmentsProps } from './types';
29
- import { restrictToWindowEdges } from '@dnd-kit/modifiers';
28
+ import { type AttachmentsProps } from './types';
29
+ import { AttachmentsButtonTrigger } from './AttachmentsButtonTrigger';
30
30
 
31
31
  export const Attachments = ({
32
32
  items = [],
@@ -37,15 +37,20 @@ export const Attachments = ({
37
37
  onUpload,
38
38
  onSorted,
39
39
  appBridge,
40
+ triggerComponent: TriggerComponent = AttachmentsButtonTrigger,
41
+ isOpen,
42
+ onOpenChange,
40
43
  }: AttachmentsProps) => {
41
44
  const [internalItems, setInternalItems] = useState<Asset[]>(items);
42
- const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
45
+ const [isFlyoutOpenInternal, setIsFlyoutOpenInternal] = useState(false);
43
46
  const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor));
44
47
  const [draggedAssetId, setDraggedAssetId] = useState<number | undefined>(undefined);
45
48
  const [isUploadLoading, setIsUploadLoading] = useState(false);
46
49
  const [assetIdsLoading, setAssetIdsLoading] = useState<number[]>([]);
47
50
  const [selectedFiles, setSelectedFiles] = useState<FileList | null>(null);
48
51
  const isEditing = useEditorState(appBridge);
52
+ const isControllingStateExternally = isOpen !== undefined;
53
+ const isFlyoutOpen = isControllingStateExternally ? isOpen : isFlyoutOpenInternal;
49
54
 
50
55
  const draggedItem = internalItems?.find((item) => item.id === draggedAssetId);
51
56
 
@@ -53,6 +58,12 @@ export const Attachments = ({
53
58
  onUploadProgress: () => !isUploadLoading && setIsUploadLoading(true),
54
59
  });
55
60
 
61
+ const handleFlyoutOpenChange = (isOpen: boolean) => {
62
+ const stateSetter = isControllingStateExternally ? onOpenChange : setIsFlyoutOpenInternal;
63
+
64
+ stateSetter?.(isOpen);
65
+ };
66
+
56
67
  useEffect(() => {
57
68
  setInternalItems(items);
58
69
  }, [items]);
@@ -77,12 +88,12 @@ export const Attachments = ({
77
88
  }, [doneAll, uploadResults]);
78
89
 
79
90
  const onOpenAssetChooser = () => {
80
- setIsFlyoutOpen(false);
91
+ handleFlyoutOpenChange(false);
81
92
  appBridge.openAssetChooser(
82
93
  (result: Asset[]) => {
83
94
  onBrowse(result);
84
95
  appBridge.closeAssetChooser();
85
- setIsFlyoutOpen(true);
96
+ handleFlyoutOpenChange(true);
86
97
  },
87
98
  {
88
99
  multiSelection: true,
@@ -92,10 +103,10 @@ export const Attachments = ({
92
103
  };
93
104
 
94
105
  const onReplaceItemWithBrowse = (toReplace: Asset) => {
95
- setIsFlyoutOpen(false);
106
+ handleFlyoutOpenChange(false);
96
107
  appBridge.openAssetChooser(
97
108
  async (result: Asset[]) => {
98
- setIsFlyoutOpen(true);
109
+ handleFlyoutOpenChange(true);
99
110
  appBridge.closeAssetChooser();
100
111
  setAssetIdsLoading([...assetIdsLoading, toReplace.id]);
101
112
  await onReplaceWithBrowse(toReplace, result[0]);
@@ -142,20 +153,18 @@ export const Attachments = ({
142
153
  <div data-test-id="attachments-flyout-button">
143
154
  <Flyout
144
155
  placement={FlyoutPlacement.BottomRight}
145
- onOpenChange={(isOpen) => setIsFlyoutOpen(!!draggedItem ? true : isOpen)}
156
+ onOpenChange={(isOpen) => handleFlyoutOpenChange(!!draggedItem ? true : isOpen)}
146
157
  isOpen={isFlyoutOpen}
147
158
  hug={false}
148
159
  fitContent
149
160
  legacyFooter={false}
150
161
  trigger={
151
- <div className="tw-flex tw-text-[13px] tw-font-body tw-items-center tw-gap-1 tw-rounded-full tw-bg-box-neutral-strong-inverse hover:tw-bg-box-neutral-strong-inverse-hover active:tw-bg-box-neutral-strong-inverse-pressed tw-text-box-neutral-strong tw-outline tw-outline-1 tw-outline-offset-[1px] tw-p-[6px] tw-outline-line">
152
- <IconPaperclip16 />
162
+ <TriggerComponent isFlyoutOpen={isFlyoutOpen}>
153
163
  <div>{items.length > 0 ? items.length : 'Add'}</div>
154
- <IconCaretDown12 />
155
- </div>
164
+ </TriggerComponent>
156
165
  }
157
166
  >
158
- <div className="tw-w-[300px]">
167
+ <div className="tw-w-[300px]" data-test-id="attachments-flyout-content">
159
168
  {internalItems.length > 0 && (
160
169
  <DndContext
161
170
  sensors={sensors}
@@ -177,6 +186,12 @@ export const Attachments = ({
177
186
  onReplaceWithUpload={(uploadedAsset: Asset) =>
178
187
  onReplaceItemWithUpload(item, uploadedAsset)
179
188
  }
189
+ onDownload={() =>
190
+ appBridge.dispatch({
191
+ name: 'downloadAsset',
192
+ payload: item,
193
+ })
194
+ }
180
195
  />
181
196
  ))}
182
197
  </div>
@@ -0,0 +1,22 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { IconCaretDown12, IconPaperclip16 } from '@frontify/fondue';
4
+
5
+ import { joinClassNames } from '../../utilities';
6
+
7
+ import { type AttachmentsTriggerProps } from './types';
8
+
9
+ export const AttachmentsButtonTrigger = ({ children, isFlyoutOpen }: AttachmentsTriggerProps) => (
10
+ <div
11
+ className={joinClassNames([
12
+ 'tw-flex tw-text-[13px] tw-font-body tw-items-center tw-gap-1 tw-rounded-full tw-outline tw-outline-1 tw-outline-offset-1 tw-p-1.5 tw-outline-line',
13
+ isFlyoutOpen
14
+ ? 'tw-bg-box-neutral-pressed tw-text-box-neutral-inverse-pressed'
15
+ : 'tw-bg-base hover:tw-bg-box-neutral-hover active:tw-bg-box-neutral-pressed tw-text-box-neutral-inverse hover:tw-text-box-neutral-inverse-hover active:tw-text-box-neutral-inverse-pressed',
16
+ ])}
17
+ >
18
+ <IconPaperclip16 />
19
+ {children}
20
+ <IconCaretDown12 />
21
+ </div>
22
+ );
@@ -1,6 +1,12 @@
1
1
  /* (c) Copyright Frontify Ltd., all rights reserved. */
2
2
 
3
- import { AppBridgeBlock, Asset } from '@frontify/app-bridge';
3
+ import { type AppBridgeBlock, type Asset } from '@frontify/app-bridge';
4
+ import { type ReactNode } from 'react';
5
+
6
+ export type AttachmentsTriggerProps = {
7
+ children: ReactNode;
8
+ isFlyoutOpen: boolean;
9
+ };
4
10
 
5
11
  export type AttachmentsProps = {
6
12
  items?: Asset[];
@@ -11,7 +17,8 @@ export type AttachmentsProps = {
11
17
  onUpload: (uploadedAttachments: Asset[]) => Promise<void>;
12
18
  onBrowse: (browserAttachments: Asset[]) => void;
13
19
  onSorted: (sortedAttachments: Asset[]) => void;
14
- };
20
+ triggerComponent?: (props: AttachmentsTriggerProps) => JSX.Element;
21
+ } & ({ isOpen?: never; onOpenChange?: never } | { isOpen: boolean; onOpenChange: (isOpen: boolean) => void });
15
22
 
16
23
  export type AttachmentItemProps = SortableAttachmentItemProps & {
17
24
  isDragging?: boolean;
@@ -27,4 +34,5 @@ export type SortableAttachmentItemProps = {
27
34
  isLoading?: boolean;
28
35
  onReplaceWithBrowse: () => void;
29
36
  onReplaceWithUpload: (uploadedAsset: Asset) => void;
37
+ onDownload?: () => Promise<void>;
30
38
  };
@@ -1,9 +1,11 @@
1
1
  /* (c) Copyright Frontify Ltd., all rights reserved. */
2
2
 
3
- import { ReactElement, useEffect, useRef, useState } from 'react';
3
+ import { type ReactElement, useRef, useState } from 'react';
4
+
4
5
  import { joinClassNames } from '../../utilities';
5
- import { Toolbar } from './Toolbar';
6
- import { BlockItemWrapperProps, ToolbarItem } from './types';
6
+
7
+ import { Toolbar, type ToolbarItem } from './Toolbar';
8
+ import { type BlockItemWrapperProps } from './types';
7
9
 
8
10
  export const BlockItemWrapper = ({
9
11
  children,
@@ -15,18 +17,12 @@ export const BlockItemWrapper = ({
15
17
  shouldFillContainer,
16
18
  outlineOffset = 2,
17
19
  shouldBeShown = false,
20
+ showAttachments = false,
18
21
  }: BlockItemWrapperProps): ReactElement => {
19
- const [isFlyoutOpen, setIsFlyoutOpen] = useState(shouldBeShown);
20
- const [isFlyoutDisabled, setIsFlyoutDisabled] = useState(false);
22
+ const [isMenuFlyoutOpen, setIsMenuFlyoutOpen] = useState(shouldBeShown);
23
+ const [isAttachmentFlyoutOpen, setIsAttachmentFlyoutOpen] = useState(false);
21
24
  const wrapperRef = useRef<HTMLDivElement>(null);
22
25
 
23
- useEffect(() => {
24
- if (!isFlyoutOpen) {
25
- // This prevents automatic refocusing of the trigger element
26
- setIsFlyoutDisabled(true);
27
- }
28
- }, [isFlyoutOpen]);
29
-
30
26
  if (shouldHideWrapper) {
31
27
  // eslint-disable-next-line react/jsx-no-useless-fragment
32
28
  return <>{children}</>;
@@ -34,11 +30,11 @@ export const BlockItemWrapper = ({
34
30
 
35
31
  const items = toolbarItems?.filter((item): item is ToolbarItem => item !== undefined);
36
32
 
33
+ const shouldToolbarBeVisible = isMenuFlyoutOpen || isAttachmentFlyoutOpen || shouldBeShown;
34
+
37
35
  return (
38
36
  <div
39
37
  ref={wrapperRef}
40
- onFocus={() => setIsFlyoutDisabled(false)}
41
- onPointerEnter={() => setIsFlyoutDisabled(false)}
42
38
  data-test-id="block-item-wrapper"
43
39
  style={{
44
40
  outlineOffset,
@@ -47,7 +43,7 @@ export const BlockItemWrapper = ({
47
43
  'tw-relative tw-group tw-outline-1 tw-outline-box-selected-inverse',
48
44
  shouldFillContainer && 'tw-flex-1 tw-h-full tw-w-full',
49
45
  'hover:tw-outline focus-within:tw-outline',
50
- (isFlyoutOpen || shouldBeShown) && 'tw-outline',
46
+ shouldToolbarBeVisible && 'tw-outline',
51
47
  shouldHideComponent && 'tw-opacity-0',
52
48
  ])}
53
49
  >
@@ -59,14 +55,21 @@ export const BlockItemWrapper = ({
59
55
  className={joinClassNames([
60
56
  'tw-pointer-events-none tw-absolute tw-bottom-[calc(100%-4px)] tw-right-[-3px] tw-w-full tw-opacity-0 tw-z-[60]',
61
57
  'group-hover:tw-opacity-100 group-focus:tw-opacity-100 focus-within:tw-opacity-100',
62
- (isFlyoutOpen || shouldBeShown) && 'tw-opacity-100',
58
+ 'tw-flex tw-justify-end',
59
+ shouldToolbarBeVisible && 'tw-opacity-100',
63
60
  ])}
64
61
  >
65
62
  <Toolbar
66
- isFlyoutOpen={isFlyoutOpen}
67
- isFlyoutDisabled={isFlyoutDisabled}
68
- setIsFlyoutOpen={setIsFlyoutOpen}
69
- flyoutItems={toolbarFlyoutItems}
63
+ flyoutMenu={{
64
+ items: toolbarFlyoutItems,
65
+ isOpen: isMenuFlyoutOpen,
66
+ onOpenChange: setIsMenuFlyoutOpen,
67
+ }}
68
+ attachments={{
69
+ isEnabled: showAttachments,
70
+ isOpen: isAttachmentFlyoutOpen,
71
+ onOpenChange: setIsAttachmentFlyoutOpen,
72
+ }}
70
73
  items={items}
71
74
  isDragging={isDragging}
72
75
  />
@@ -0,0 +1,127 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { getAppBridgeBlockStub } from '@frontify/app-bridge';
4
+ import { render } from '@testing-library/react';
5
+ import { beforeAll, describe, expect, it, vi } from 'vitest';
6
+
7
+ import { AttachmentsProvider } from '../../../hooks/useAttachments';
8
+
9
+ import { Toolbar } from './Toolbar';
10
+
11
+ /**
12
+ * @vitest-environment happy-dom
13
+ */
14
+
15
+ const ATTACHMENTS_FLYOUT_ID = 'attachments-flyout-content';
16
+ const MENU_FLYOUT_ID = 'menu-item';
17
+
18
+ const MOCK_ASSET_FIELD_ID = 'attachment';
19
+
20
+ describe('Toolbar', () => {
21
+ beforeAll(() => {
22
+ vi.stubGlobal(
23
+ 'Worker',
24
+ class Worker {
25
+ constructor() {}
26
+ addEventListener() {}
27
+ terminate() {}
28
+ },
29
+ );
30
+ });
31
+
32
+ it('should not throw error if toolbar does not have attachments enabled', () => {
33
+ expect(() =>
34
+ render(
35
+ <Toolbar
36
+ items={[]}
37
+ flyoutMenu={{ items: [], isOpen: false, onOpenChange: vi.fn() }}
38
+ attachments={{ isEnabled: false, isOpen: false, onOpenChange: vi.fn() }}
39
+ />,
40
+ ),
41
+ ).not.toThrowError();
42
+ });
43
+
44
+ it('should throw error if toolbar does have attachments enabled without provider', () => {
45
+ expect(() =>
46
+ render(
47
+ <Toolbar
48
+ items={[]}
49
+ flyoutMenu={{ items: [], isOpen: false, onOpenChange: vi.fn() }}
50
+ attachments={{ isEnabled: true, isOpen: false, onOpenChange: vi.fn() }}
51
+ />,
52
+ ),
53
+ ).toThrowError();
54
+ });
55
+
56
+ it('should open flyouts if not dragging', async () => {
57
+ const STUB_WITH_NO_ASSETS = getAppBridgeBlockStub({
58
+ blockId: 1,
59
+ blockAssets: { [MOCK_ASSET_FIELD_ID]: [] },
60
+ editorState: true,
61
+ });
62
+
63
+ const ToolbarWithAttachments = () => (
64
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
65
+ <Toolbar
66
+ items={[]}
67
+ flyoutMenu={{
68
+ items: [
69
+ [
70
+ {
71
+ title: 'Replace with upload',
72
+ icon: <div></div>,
73
+ onClick: vi.fn(),
74
+ },
75
+ ],
76
+ ],
77
+ isOpen: true,
78
+ onOpenChange: vi.fn(),
79
+ }}
80
+ attachments={{ isEnabled: true, isOpen: true, onOpenChange: vi.fn() }}
81
+ isDragging={false}
82
+ />
83
+ </AttachmentsProvider>
84
+ );
85
+
86
+ const { baseElement } = render(<ToolbarWithAttachments />, { container: document.body });
87
+
88
+ expect(baseElement.querySelector(`[data-test-id=${ATTACHMENTS_FLYOUT_ID}]`)).not.toBeNull();
89
+ expect(baseElement.querySelector(`[data-test-id=${MENU_FLYOUT_ID}]`)).not.toBeNull();
90
+ });
91
+
92
+ it('should keep flyouts closed if dragging', async () => {
93
+ const MOCK_ASSET_FIELD_ID = 'attachment';
94
+ const STUB_WITH_NO_ASSETS = getAppBridgeBlockStub({
95
+ blockId: 1,
96
+ blockAssets: { [MOCK_ASSET_FIELD_ID]: [] },
97
+ editorState: true,
98
+ });
99
+
100
+ const ToolbarWithAttachments = () => (
101
+ <AttachmentsProvider appBridge={STUB_WITH_NO_ASSETS} assetId={MOCK_ASSET_FIELD_ID}>
102
+ <Toolbar
103
+ items={[]}
104
+ flyoutMenu={{
105
+ items: [
106
+ [
107
+ {
108
+ title: 'Replace with upload',
109
+ icon: <div></div>,
110
+ onClick: vi.fn(),
111
+ },
112
+ ],
113
+ ],
114
+ isOpen: true,
115
+ onOpenChange: vi.fn(),
116
+ }}
117
+ attachments={{ isEnabled: true, isOpen: true, onOpenChange: vi.fn() }}
118
+ isDragging
119
+ />
120
+ </AttachmentsProvider>
121
+ );
122
+
123
+ const { baseElement } = render(<ToolbarWithAttachments />, { container: document.body });
124
+ expect(baseElement.querySelector(`[data-test-id=${ATTACHMENTS_FLYOUT_ID}]`)).toBeNull();
125
+ expect(baseElement.querySelector(`[data-test-id=${MENU_FLYOUT_ID}]`)).toBeNull();
126
+ });
127
+ });
@@ -0,0 +1,133 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import {
4
+ ActionMenu,
5
+ Flyout,
6
+ IconDotsHorizontal16,
7
+ MenuItemContentSize,
8
+ LegacyTooltip as Tooltip,
9
+ TooltipPosition,
10
+ } from '@frontify/fondue';
11
+
12
+ import { DEFAULT_DRAGGING_TOOLTIP, DEFAULT_DRAG_TOOLTIP } from '../constants';
13
+
14
+ import { ToolbarSegment } from './ToolbarSegment';
15
+ import { ToolbarAttachments } from './ToolbarAttachments';
16
+ import { getToolbarButtonClassNames } from './helpers';
17
+ import { type ToolbarProps } from './types';
18
+
19
+ export const Toolbar = ({ items, flyoutMenu, attachments, isDragging }: ToolbarProps) => {
20
+ return (
21
+ <div
22
+ data-test-id="block-item-wrapper-toolbar"
23
+ className="tw-rounded-md tw-bg-base tw-border tw-border-line-strong tw-divide-x tw-divide-line-strong tw-shadow-lg tw-flex tw-flex-none tw-items-center tw-isolate"
24
+ >
25
+ {attachments.isEnabled && (
26
+ <ToolbarSegment>
27
+ <ToolbarAttachments
28
+ isOpen={attachments.isOpen && !isDragging}
29
+ onOpenChange={attachments.onOpenChange}
30
+ />
31
+ </ToolbarSegment>
32
+ )}
33
+ <ToolbarSegment>
34
+ {items.map((item, i) =>
35
+ 'draggableProps' in item ? (
36
+ <Tooltip
37
+ key={i}
38
+ withArrow
39
+ hoverDelay={0}
40
+ enterDelay={300}
41
+ open={isDragging}
42
+ position={TooltipPosition.Top}
43
+ content={
44
+ <div>
45
+ {isDragging ? DEFAULT_DRAGGING_TOOLTIP : item.tooltip ?? DEFAULT_DRAG_TOOLTIP}
46
+ </div>
47
+ }
48
+ triggerElement={
49
+ <button
50
+ ref={item.setActivatorNodeRef}
51
+ data-test-id="block-item-wrapper-toolbar-btn"
52
+ {...item.draggableProps}
53
+ className={getToolbarButtonClassNames('grab', isDragging)}
54
+ >
55
+ {item.icon}
56
+ </button>
57
+ }
58
+ />
59
+ ) : (
60
+ <Tooltip
61
+ key={i}
62
+ withArrow
63
+ enterDelay={300}
64
+ hoverDelay={0}
65
+ disabled={isDragging}
66
+ position={TooltipPosition.Top}
67
+ content={<div>{item.tooltip ?? ''}</div>}
68
+ triggerElement={
69
+ <button
70
+ data-test-id="block-item-wrapper-toolbar-btn"
71
+ onClick={item.onClick}
72
+ className={getToolbarButtonClassNames('pointer')}
73
+ >
74
+ {item.icon}
75
+ </button>
76
+ }
77
+ />
78
+ ),
79
+ )}
80
+ {flyoutMenu.items.length > 0 && (
81
+ <Tooltip
82
+ withArrow
83
+ hoverDelay={0}
84
+ enterDelay={300}
85
+ disabled={isDragging || flyoutMenu.isOpen}
86
+ position={TooltipPosition.Top}
87
+ content={<div>Options</div>}
88
+ triggerElement={
89
+ <div className="tw-flex tw-flex-shrink-0 tw-flex-1 tw-h-6 tw-relative">
90
+ <Flyout
91
+ isOpen={flyoutMenu.isOpen && !isDragging}
92
+ legacyFooter={false}
93
+ fitContent
94
+ hug={false}
95
+ onOpenChange={flyoutMenu.onOpenChange}
96
+ trigger={
97
+ <div
98
+ data-test-id="block-item-wrapper-toolbar-flyout"
99
+ className={getToolbarButtonClassNames(
100
+ 'pointer',
101
+ flyoutMenu.isOpen && !isDragging,
102
+ )}
103
+ >
104
+ <IconDotsHorizontal16 />
105
+ </div>
106
+ }
107
+ >
108
+ <ActionMenu
109
+ menuBlocks={flyoutMenu.items.map((block, blockIndex) => ({
110
+ id: blockIndex.toString(),
111
+ menuItems: block.map((item, itemIndex) => ({
112
+ id: blockIndex.toString() + itemIndex.toString(),
113
+ size: MenuItemContentSize.XSmall,
114
+ title: item.title,
115
+ style: item.style,
116
+ onClick: () => {
117
+ flyoutMenu.onOpenChange(false);
118
+ item.onClick();
119
+ },
120
+ initialValue: true,
121
+ decorator: <div className="tw-mr-2">{item.icon}</div>,
122
+ })),
123
+ }))}
124
+ />
125
+ </Flyout>
126
+ </div>
127
+ }
128
+ />
129
+ )}
130
+ </ToolbarSegment>
131
+ </div>
132
+ );
133
+ };