@leaflink/stash 52.0.2 → 53.0.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.
- package/README.md +50 -49
- package/dist/Accordion.js +16 -16
- package/dist/Accordion.js.map +1 -1
- package/dist/AccordionGroup.js +7 -7
- package/dist/AccordionGroup.js.map +1 -1
- package/dist/ActionsDropdown.js +16 -16
- package/dist/ActionsDropdown.js.map +1 -1
- package/dist/AddressSelect.js.map +1 -1
- package/dist/AddressSelect.vue.d.ts +1 -1
- package/dist/Alert.js +34 -34
- package/dist/Alert.js.map +1 -1
- package/dist/AppNavigationItem.js +31 -31
- package/dist/AppNavigationItem.js.map +1 -1
- package/dist/AppSidebar.js +19 -19
- package/dist/AppSidebar.js.map +1 -1
- package/dist/AppTopbar.js +32 -32
- package/dist/AppTopbar.js.map +1 -1
- package/dist/Avatar.js +18 -18
- package/dist/Avatar.js.map +1 -1
- package/dist/Backdrop.js +5 -5
- package/dist/Backdrop.js.map +1 -1
- package/dist/Badge.js +38 -38
- package/dist/Badge.js.map +1 -1
- package/dist/Box.js +1 -1
- package/dist/{Box.vue_vue_type_script_setup_true_lang-rFnvwLVY.js → Box.vue_vue_type_script_setup_true_lang-dFFZN40_.js} +6 -6
- package/dist/{Box.vue_vue_type_script_setup_true_lang-rFnvwLVY.js.map → Box.vue_vue_type_script_setup_true_lang-dFFZN40_.js.map} +1 -1
- package/dist/Button.js +21 -21
- package/dist/Button.js.map +1 -1
- package/dist/ButtonGroup.js +26 -26
- package/dist/ButtonGroup.js.map +1 -1
- package/dist/Card.js +14 -14
- package/dist/Card.js.map +1 -1
- package/dist/CardContent.js +1 -1
- package/dist/CardContent.js.map +1 -1
- package/dist/CardFooter.js +1 -1
- package/dist/CardFooter.js.map +1 -1
- package/dist/CardHeader.js +4 -4
- package/dist/CardHeader.js.map +1 -1
- package/dist/CardMedia.js +20 -20
- package/dist/CardMedia.js.map +1 -1
- package/dist/Carousel.js +88 -93
- package/dist/Carousel.js.map +1 -1
- package/dist/Checkbox.js +30 -30
- package/dist/Checkbox.js.map +1 -1
- package/dist/Checkbox.vue.d.ts +0 -3
- package/dist/Chip.js +33 -33
- package/dist/Chip.js.map +1 -1
- package/dist/ConfirmationCodeInput.js +72 -72
- package/dist/ConfirmationCodeInput.js.map +1 -1
- package/dist/ContextSwitcher.js +27 -27
- package/dist/ContextSwitcher.js.map +1 -1
- package/dist/Copy.js +47 -48
- package/dist/Copy.js.map +1 -1
- package/dist/CurrencyInput.js +1 -1
- package/dist/CurrencyInput.js.map +1 -1
- package/dist/CurrencyInput.vue.d.ts +5 -5
- package/dist/DataView.js +23 -23
- package/dist/DataView.js.map +1 -1
- package/dist/DataViewFilters.js +26 -26
- package/dist/DataViewFilters.js.map +1 -1
- package/dist/DataViewSortButton.js +22 -22
- package/dist/DataViewSortButton.js.map +1 -1
- package/dist/DataViewToolbar.js +52 -52
- package/dist/DataViewToolbar.js.map +1 -1
- package/dist/DatePicker.js +10 -10
- package/dist/DatePicker.js.map +1 -1
- package/dist/DescriptionList.js +2 -2
- package/dist/DescriptionList.js.map +1 -1
- package/dist/DescriptionListDetail.js +2 -2
- package/dist/DescriptionListDetail.js.map +1 -1
- package/dist/DescriptionListGroup.js +9 -9
- package/dist/DescriptionListGroup.js.map +1 -1
- package/dist/DescriptionListTerm.js +8 -8
- package/dist/DescriptionListTerm.js.map +1 -1
- package/dist/Dialog.js +47 -47
- package/dist/Dialog.js.map +1 -1
- package/dist/Divider.js +6 -6
- package/dist/Divider.js.map +1 -1
- package/dist/Dropdown.js +20 -20
- package/dist/Dropdown.js.map +1 -1
- package/dist/EmptyState.js +26 -26
- package/dist/EmptyState.js.map +1 -1
- package/dist/Field.js +1 -1
- package/dist/{Field.vue_vue_type_script_setup_true_lang-DI6z3AE9.js → Field.vue_vue_type_script_setup_true_lang-dAGKfjf5.js} +17 -17
- package/dist/Field.vue_vue_type_script_setup_true_lang-dAGKfjf5.js.map +1 -0
- package/dist/FileUpload.js +47 -49
- package/dist/FileUpload.js.map +1 -1
- package/dist/FilterChip.js +20 -20
- package/dist/FilterChip.js.map +1 -1
- package/dist/FilterDrawerItem.js +13 -13
- package/dist/FilterDrawerItem.js.map +1 -1
- package/dist/FilterDropdown.js +27 -27
- package/dist/FilterDropdown.js.map +1 -1
- package/dist/FilterSelect.js +33 -33
- package/dist/FilterSelect.js.map +1 -1
- package/dist/Filters.js +29 -29
- package/dist/Filters.js.map +1 -1
- package/dist/Filters.vue.d.ts +2 -8
- package/dist/HttpError.js +29 -29
- package/dist/HttpError.js.map +1 -1
- package/dist/HttpError.vue.d.ts +0 -3
- package/dist/Icon.js +12 -12
- package/dist/Icon.js.map +1 -1
- package/dist/IconLabel.js +19 -19
- package/dist/IconLabel.js.map +1 -1
- package/dist/IconLabel.vue.d.ts +1 -1
- package/dist/Illustration.js +2 -2
- package/dist/{Illustration.vue_vue_type_script_setup_true_lang-BrqEF8xe.js → Illustration.vue_vue_type_script_setup_true_lang-C1bPkWZZ.js} +4 -4
- package/dist/{Illustration.vue_vue_type_script_setup_true_lang-BrqEF8xe.js.map → Illustration.vue_vue_type_script_setup_true_lang-C1bPkWZZ.js.map} +1 -1
- package/dist/Image.js +2 -2
- package/dist/Image.vue.d.ts +0 -3
- package/dist/{Image.vue_vue_type_script_setup_true_lang-D5u4av0_.js → Image.vue_vue_type_script_setup_true_lang-CAj0FH9h.js} +11 -11
- package/dist/Image.vue_vue_type_script_setup_true_lang-CAj0FH9h.js.map +1 -0
- package/dist/InlineEdit.js +8 -8
- package/dist/InlineEdit.js.map +1 -1
- package/dist/Input.js +29 -29
- package/dist/Input.js.map +1 -1
- package/dist/InputOptions.js +87 -84
- package/dist/InputOptions.js.map +1 -1
- package/dist/InputOptions.vue.d.ts +2 -2
- package/dist/IntegrationIcon.js +11 -11
- package/dist/IntegrationIcon.js.map +1 -1
- package/dist/Label.js +1 -1
- package/dist/{Label.vue_vue_type_script_setup_true_lang-CNquF3AP.js → Label.vue_vue_type_script_setup_true_lang-xwY3X-iV.js} +16 -16
- package/dist/{Label.vue_vue_type_script_setup_true_lang-CNquF3AP.js.map → Label.vue_vue_type_script_setup_true_lang-xwY3X-iV.js.map} +1 -1
- package/dist/ListItem.js +14 -14
- package/dist/ListItem.js.map +1 -1
- package/dist/ListItem.vue.d.ts +0 -6
- package/dist/ListItemCell.js +9 -9
- package/dist/ListItemCell.js.map +1 -1
- package/dist/ListView.js +138 -141
- package/dist/ListView.js.map +1 -1
- package/dist/ListView.vue.d.ts +2 -26
- package/dist/Loading.js +8 -8
- package/dist/Loading.js.map +1 -1
- package/dist/Logo.js +1 -1
- package/dist/{Logo.vue_vue_type_script_setup_true_lang-Dz8c98sc.js → Logo.vue_vue_type_script_setup_true_lang-DghNC_k6.js} +3 -3
- package/dist/{Logo.vue_vue_type_script_setup_true_lang-Dz8c98sc.js.map → Logo.vue_vue_type_script_setup_true_lang-DghNC_k6.js.map} +1 -1
- package/dist/Menu.js +5 -5
- package/dist/Menu.js.map +1 -1
- package/dist/MenuItem.js +12 -12
- package/dist/MenuItem.js.map +1 -1
- package/dist/Metric.js +24 -24
- package/dist/Metric.js.map +1 -1
- package/dist/Modal.js +60 -60
- package/dist/Modal.js.map +1 -1
- package/dist/Modals.js +1 -1
- package/dist/Modals.js.map +1 -1
- package/dist/Module.js +6 -6
- package/dist/Module.js.map +1 -1
- package/dist/ModuleContent.js +16 -16
- package/dist/ModuleContent.js.map +1 -1
- package/dist/ModuleFooter.js +13 -13
- package/dist/ModuleFooter.js.map +1 -1
- package/dist/ModuleHeader.js +29 -29
- package/dist/ModuleHeader.js.map +1 -1
- package/dist/MoreActions.js +81 -84
- package/dist/MoreActions.js.map +1 -1
- package/dist/ObfuscateText.js +4 -4
- package/dist/ObfuscateText.js.map +1 -1
- package/dist/PageContent.js +13 -13
- package/dist/PageContent.js.map +1 -1
- package/dist/PageHeader.js +28 -28
- package/dist/PageHeader.js.map +1 -1
- package/dist/PageNavigation.js +1 -1
- package/dist/Paginate.js +45 -45
- package/dist/Paginate.js.map +1 -1
- package/dist/QuickAction.js +18 -18
- package/dist/QuickAction.js.map +1 -1
- package/dist/Radio.js +17 -17
- package/dist/Radio.js.map +1 -1
- package/dist/RadioGroup.js +158 -154
- package/dist/RadioGroup.js.map +1 -1
- package/dist/RadioNew.js +80 -80
- package/dist/RadioNew.js.map +1 -1
- package/dist/RadioNew.vue.d.ts +0 -3
- package/dist/RangeInput.js +2 -2
- package/dist/RangeInput.js.map +1 -1
- package/dist/SearchBar.js +9 -9
- package/dist/SearchBar.js.map +1 -1
- package/dist/SectionHeader.js +14 -14
- package/dist/SectionHeader.js.map +1 -1
- package/dist/Select.js +369 -366
- package/dist/Select.js.map +1 -1
- package/dist/SelectStatus.js +26 -27
- package/dist/SelectStatus.js.map +1 -1
- package/dist/Skeleton.js +20 -20
- package/dist/Skeleton.js.map +1 -1
- package/dist/Step.js +37 -40
- package/dist/Step.js.map +1 -1
- package/dist/Stepper.js +17 -17
- package/dist/Stepper.js.map +1 -1
- package/dist/Switch.js +57 -57
- package/dist/Switch.js.map +1 -1
- package/dist/Tab.js +17 -19
- package/dist/Tab.js.map +1 -1
- package/dist/TabPanel.js +1 -1
- package/dist/TabPanel.js.map +1 -1
- package/dist/Table.js +22 -22
- package/dist/Table.js.map +1 -1
- package/dist/TableCell.js +32 -32
- package/dist/TableCell.js.map +1 -1
- package/dist/TableHeaderCell.js +35 -35
- package/dist/TableHeaderCell.js.map +1 -1
- package/dist/TableHeaderRow.js +10 -10
- package/dist/TableHeaderRow.js.map +1 -1
- package/dist/TableRow.js +51 -51
- package/dist/TableRow.js.map +1 -1
- package/dist/Tabs.js +2 -2
- package/dist/{Tabs.vue_vue_type_script_setup_true_lang-B3Irnlcd.js → Tabs.vue_vue_type_script_setup_true_lang-BVTCcK6M.js} +33 -33
- package/dist/Tabs.vue_vue_type_script_setup_true_lang-BVTCcK6M.js.map +1 -0
- package/dist/TextEditor.js +8 -8
- package/dist/TextEditor.js.map +1 -1
- package/dist/Textarea.js +15 -15
- package/dist/Textarea.js.map +1 -1
- package/dist/Thumbnail.js +41 -41
- package/dist/Thumbnail.js.map +1 -1
- package/dist/ThumbnailEmpty.js +3 -3
- package/dist/ThumbnailEmpty.js.map +1 -1
- package/dist/ThumbnailGroup.js +22 -22
- package/dist/ThumbnailGroup.js.map +1 -1
- package/dist/Timeline.js +3 -3
- package/dist/Timeline.js.map +1 -1
- package/dist/TimelineItem.js +22 -22
- package/dist/TimelineItem.js.map +1 -1
- package/dist/Toast.js +29 -29
- package/dist/Toast.js.map +1 -1
- package/dist/Toast.vue.d.ts +3 -0
- package/dist/Toasts.js +11 -11
- package/dist/Toasts.js.map +1 -1
- package/dist/Tooltip.js +2 -81
- package/dist/Tooltip.js.map +1 -1
- package/dist/Tooltip.vue.d.ts +1 -1
- package/dist/Tooltip.vue_vue_type_script_setup_true_lang-mzBLSXy3.js +84 -0
- package/dist/Tooltip.vue_vue_type_script_setup_true_lang-mzBLSXy3.js.map +1 -0
- package/dist/components.css +2 -2
- package/dist/constants.d.ts +9 -9
- package/dist/constants.js +17 -17
- package/dist/constants.js.map +1 -1
- package/dist/directives/tooltip.js +2 -2
- package/dist/directives/tooltip.js.map +1 -1
- package/dist/{index-C14LhAwV.js → index-DBV9Uz0C.js} +3 -3
- package/dist/{index-C14LhAwV.js.map → index-DBV9Uz0C.js.map} +1 -1
- package/dist/tailwind-base.js.d.ts +12 -0
- package/dist/tailwind-base.js.map +1 -1
- package/dist/useSortable.js +1 -1
- package/dist/utils/helpers.js +15 -15
- package/dist/utils/helpers.js.map +1 -1
- package/package.json +15 -15
- package/styles/backwards-compat.css +373 -2851
- package/styles/main.css +8 -0
- package/styles/sofia-font.css +23 -27
- package/styles/theme.css +1033 -0
- package/dist/Field.vue_vue_type_script_setup_true_lang-DI6z3AE9.js.map +0 -1
- package/dist/Image.vue_vue_type_script_setup_true_lang-D5u4av0_.js.map +0 -1
- package/dist/Tabs.vue_vue_type_script_setup_true_lang-B3Irnlcd.js.map +0 -1
- package/dist/tailwind-base.d.ts +0 -333
- package/styles/base.css +0 -902
- package/tailwind-base.ts +0 -455
package/dist/Dropdown.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.js","sources":["../src/components/Dropdown/Dropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, nextTick, onBeforeUnmount, onMounted, ref, useCssModule, watch } from 'vue';\n\n import { KEY_CODES } from '../../constants';\n import vClickoutside from '../../directives/clickoutside/clickoutside';\n import calculateElementOverflow from '../../utils/calculateElementOverflow';\n import { getMountPoint } from '../../utils/helpers';\n import Icon from '../Icon/Icon.vue';\n\n export type DropdownOffset = {\n x?: number;\n y?: number;\n };\n\n export interface DropdownProps {\n /**\n * Used to position the dropdown relative to target\n * Options: left, right\n */\n align?: 'left' | 'right';\n /**\n * When passed, text with caret is displayed instead of default kebab menu.\n */\n label?: string;\n /**\n * If true, dropdown will remain open when clicking on its content. Otherwise, it'll close automatically on click.\n */\n closeManually?: boolean;\n /**\n * Used to horizontally and vertically offset the dropdown in pixels\n * Keep in mind, `x` value is relative to `align` value.\n */\n offset?: DropdownOffset;\n /**\n * If `reattach` is true, the contents will be mounted in the mount point container,\n * so as to circumvent clipping issues from a parent's overflow property.\n */\n reattach?: boolean;\n /**\n * Custom class to apply to the default dropdown content element\n */\n contentClass?: string | string[];\n /**\n * Removes content container's max width\n */\n fluidContent?: boolean;\n }\n\n type DropdownStyles = {\n left?: string;\n right?: string;\n top?: string;\n };\n\n defineOptions({\n name: 'll-dropdown',\n });\n\n const props = withDefaults(defineProps<DropdownProps>(), {\n align: 'right',\n label: '',\n closeManually: false,\n offset: () => ({}),\n reattach: true,\n contentClass: '',\n fluidContent: false,\n });\n\n const emit = defineEmits<{\n toggle: [isActive: boolean];\n dismiss: [];\n }>();\n\n const contentRef = ref<HTMLElement | null>(null);\n const dropdownRef = ref<HTMLDivElement | null>(null);\n\n const DEFAULT_ITEMS_LIST = [] as unknown as NodeListOf<Element>;\n\n const items = ref<NodeListOf<Element>>(DEFAULT_ITEMS_LIST);\n const itemIndex = ref(-1);\n const isActive = ref(false);\n\n const styles = ref<DropdownStyles>({});\n const classes = useCssModule();\n\n watch(isActive, (val) => {\n if (!val) {\n itemIndex.value = -1;\n }\n\n emit('toggle', val);\n });\n\n watch(itemIndex, (next, prev) => {\n if (next in items.value) {\n items.value[next].classList.add('tw-bg-ice-200');\n }\n if (prev in items.value) {\n items.value[prev].classList.remove('tw-bg-ice-200');\n }\n });\n\n onMounted(() => {\n if (props.reattach) {\n const target = getMountPoint();\n\n if (contentRef.value) {\n target.appendChild(contentRef.value);\n }\n }\n\n // if stash-menu exists, use it's items\n if (contentRef.value?.querySelector('.stash-menu')) {\n items.value = contentRef.value?.querySelectorAll('.stash-menu-item') ?? DEFAULT_ITEMS_LIST;\n } else {\n items.value = contentRef.value?.querySelectorAll('.dropdown__item') ?? DEFAULT_ITEMS_LIST;\n }\n\n // avoid calling dismiss with ResizeEvent because it's target is the Window element and it's not a valid Node element to be passed to dismiss\n window.addEventListener('resize', () => dismiss());\n });\n\n onBeforeUnmount(() => {\n window.removeEventListener('resize', () => dismiss());\n contentRef?.value?.parentNode?.removeChild(contentRef.value);\n });\n\n /**\n * Dismisses the Dropdown.\n */\n function dismiss(event?: Event) {\n if (\n props.closeManually &&\n (contentRef.value?.contains(event?.target as HTMLElement) || contentRef.value === event?.target)\n ) {\n return;\n }\n\n const wasOpen = isActive.value;\n\n isActive.value = false;\n\n if (wasOpen) {\n emit('dismiss');\n }\n }\n\n /**\n * Opens or closes the Dropdown. If opening, positions and focuses its content.\n */\n async function toggle() {\n if (isActive.value) {\n isActive.value = false;\n } else {\n isActive.value = true;\n await calculateOffset();\n contentRef.value?.focus();\n }\n }\n\n /**\n * Handles all user keyboard input on the Dropdown.\n * @param {KeyboardEvent} e The native keydown event.\n */\n function onKeyDown(e: KeyboardEvent) {\n if (e.keyCode === KEY_CODES.ESCAPE) {\n dismiss();\n } else if (e.keyCode === KEY_CODES.DOWN && itemIndex.value < items.value.length - 1 && isActive.value) {\n itemIndex.value++;\n } else if (e.keyCode === KEY_CODES.UP && itemIndex.value > -1 && isActive.value) {\n itemIndex.value--;\n } else if (e.keyCode === KEY_CODES.ENTER && itemIndex.value !== -1 && isActive.value) {\n items[itemIndex.value].click();\n } else {\n return;\n }\n\n e.preventDefault(); // if keyDown did something / we didn't return\n }\n\n /**\n * Reset any page overflow offset\n */\n function resetOffset() {\n contentRef.value?.style.setProperty('--offset', '0');\n }\n\n /**\n * Calculate the position for the Dropdown content element,\n * relative to the viewport.\n * Repositions the dropdown content element (& arrow)\n * if it overflows the page (including gutter).\n */\n async function calculateOffset() {\n await nextTick();\n\n const toggleBCR = dropdownRef.value?.getBoundingClientRect() || ({} as DOMRect);\n const top = toggleBCR.top + toggleBCR.height;\n const offset = {\n x: 0,\n y: 0,\n ...props.offset,\n };\n\n if (props.reattach) {\n styles.value = {\n left: props.align === 'left' ? `${Math.round(toggleBCR.left) + offset.x}px` : undefined,\n right:\n props.align === 'right'\n ? `${document.documentElement.clientWidth - Math.round(toggleBCR.right) + offset.x}px`\n : undefined,\n top: `${window.scrollY + top + offset.y}px`,\n };\n await nextTick(); // wait for style updates to sync in the DOM\n }\n\n const overflow = calculateElementOverflow(contentRef?.value);\n\n if (overflow) {\n const direction = props.align === 'left' ? '-' : '';\n contentRef.value?.style.setProperty('--offset', `${direction}${overflow.value}`);\n }\n }\n\n defineExpose({\n isActive: computed(() => isActive.value),\n toggle,\n dismiss,\n });\n</script>\n\n<template>\n <div\n ref=\"dropdownRef\"\n v-clickoutside=\"dismiss\"\n class=\"tw-relative tw-inline-block\"\n data-test=\"ll-dropdown\"\n @keydown=\"onKeyDown\"\n >\n <!-- @slot Toggle component slot, receives `toggle` method to trigger Dropdown display -->\n <slot name=\"toggle\" :is-active=\"isActive\" :toggle=\"toggle\">\n <button\n data-test=\"button|toggle\"\n :aria-expanded=\"isActive\"\n :class=\"[label ? 'tw-min-w-auto tw-outline-none' : 'tw-rounded tw-p-3']\"\n @click=\"toggle\"\n >\n <span v-if=\"label\" class=\"tw-flex tw-items-center tw-font-medium tw-text-blue-500 hover:tw-text-blue-700\">\n {{ label }}\n <Icon name=\"caret-down\" />\n </span>\n <Icon v-else name=\"ellipsis\" />\n </button>\n </slot>\n\n <transition name=\"fade\" @after-leave=\"resetOffset\">\n <div\n v-show=\"isActive\"\n ref=\"contentRef\"\n data-test=\"ll-dropdown-content\"\n :class=\"[\n classes.content,\n contentClass,\n {\n 'tw-left-0 after:tw-left-6': props.align === 'left',\n 'tw-right-0': props.align === 'right',\n 'tw-max-w-[360px]': !props.fluidContent,\n },\n ]\"\n :style=\"styles\"\n >\n <!-- @slot Dropdown content slot. Receives `dismiss` method, to trigger manual close. You must call dismiss with parentheses. Receives `is-active` prop. -->\n <slot :dismiss=\"dismiss\" :is-active=\"isActive\"></slot>\n </div>\n </transition>\n </div>\n</template>\n\n<style module>\n .content {\n --offset: 0;\n\n @apply tw-shadow-2xl\n tw-bg-white\n tw-border\n tw-border-solid\n tw-border-blue-500\n tw-rounded\n tw-absolute\n tw-z-[301]\n tw-min-w-[200px];\n\n transform: translateX(calc(var(--offset) * 1px));\n }\n\n /**\n * TODO: these global styles should be componentized in https://leaflink.atlassian.net/browse/STASH-566\n */\n :global(.dropdown__list) {\n @apply tw-bg-white tw-list-none tw-m-1.5 tw-p-0;\n }\n\n :global(.dropdown__item) {\n @apply tw-block tw-mx-0 tw-mt-0 tw-mb-1.5 tw-w-full first:tw-mt-0 last:tw-mb-0;\n }\n\n :global(.dropdown__item) button {\n @apply tw-border-none tw-text-left tw-p-1.5;\n }\n\n :global(.dropdown__item) :where(a, button) {\n @apply tw-rounded\n tw-text-ice-700\n tw-block\n tw-py-[10px]\n tw-px-1.5\n tw-w-full\n disabled:tw-text-ice-500\n disabled:tw-cursor-default\n hover:tw-bg-ice-200\n hover:tw-text-ice-700\n hover:tw-no-underline\n tw-transition-all\n tw-duration-fast\n tw-ease-swing;\n }\n</style>\n"],"names":["props","__props","emit","__emit","contentRef","ref","dropdownRef","DEFAULT_ITEMS_LIST","items","itemIndex","isActive","styles","classes","useCssModule","watch","val","next","prev","onMounted","target","getMountPoint","_a","_b","_c","dismiss","onBeforeUnmount","event","wasOpen","toggle","calculateOffset","onKeyDown","KEY_CODES","resetOffset","nextTick","toggleBCR","top","offset","overflow","calculateElementOverflow","direction","__expose","computed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Dropdown.js","sources":["../src/components/Dropdown/Dropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, nextTick, onBeforeUnmount, onMounted, ref, useCssModule, watch } from 'vue';\n\n import { KEY_CODES } from '../../constants';\n import vClickoutside from '../../directives/clickoutside/clickoutside';\n import calculateElementOverflow from '../../utils/calculateElementOverflow';\n import { getMountPoint } from '../../utils/helpers';\n import Icon from '../Icon/Icon.vue';\n\n export type DropdownOffset = {\n x?: number;\n y?: number;\n };\n\n export interface DropdownProps {\n /**\n * Used to position the dropdown relative to target\n * Options: left, right\n */\n align?: 'left' | 'right';\n /**\n * When passed, text with caret is displayed instead of default kebab menu.\n */\n label?: string;\n /**\n * If true, dropdown will remain open when clicking on its content. Otherwise, it'll close automatically on click.\n */\n closeManually?: boolean;\n /**\n * Used to horizontally and vertically offset the dropdown in pixels\n * Keep in mind, `x` value is relative to `align` value.\n */\n offset?: DropdownOffset;\n /**\n * If `reattach` is true, the contents will be mounted in the mount point container,\n * so as to circumvent clipping issues from a parent's overflow property.\n */\n reattach?: boolean;\n /**\n * Custom class to apply to the default dropdown content element\n */\n contentClass?: string | string[];\n /**\n * Removes content container's max width\n */\n fluidContent?: boolean;\n }\n\n type DropdownStyles = {\n left?: string;\n right?: string;\n top?: string;\n };\n\n defineOptions({\n name: 'll-dropdown',\n });\n\n const props = withDefaults(defineProps<DropdownProps>(), {\n align: 'right',\n label: '',\n closeManually: false,\n offset: () => ({}),\n reattach: true,\n contentClass: '',\n fluidContent: false,\n });\n\n const emit = defineEmits<{\n toggle: [isActive: boolean];\n dismiss: [];\n }>();\n\n const contentRef = ref<HTMLElement | null>(null);\n const dropdownRef = ref<HTMLDivElement | null>(null);\n\n const DEFAULT_ITEMS_LIST = [] as unknown as NodeListOf<Element>;\n\n const items = ref<NodeListOf<Element>>(DEFAULT_ITEMS_LIST);\n const itemIndex = ref(-1);\n const isActive = ref(false);\n\n const styles = ref<DropdownStyles>({});\n const classes = useCssModule();\n\n watch(isActive, (val) => {\n if (!val) {\n itemIndex.value = -1;\n }\n\n emit('toggle', val);\n });\n\n watch(itemIndex, (next, prev) => {\n if (next in items.value) {\n items.value[next].classList.add('bg-ice-200');\n }\n if (prev in items.value) {\n items.value[prev].classList.remove('bg-ice-200');\n }\n });\n\n onMounted(() => {\n if (props.reattach) {\n const target = getMountPoint();\n\n if (contentRef.value) {\n target.appendChild(contentRef.value);\n }\n }\n\n // if stash-menu exists, use it's items\n if (contentRef.value?.querySelector('.stash-menu')) {\n items.value = contentRef.value?.querySelectorAll('.stash-menu-item') ?? DEFAULT_ITEMS_LIST;\n } else {\n items.value = contentRef.value?.querySelectorAll('.dropdown__item') ?? DEFAULT_ITEMS_LIST;\n }\n\n // avoid calling dismiss with ResizeEvent because it's target is the Window element and it's not a valid Node element to be passed to dismiss\n window.addEventListener('resize', () => dismiss());\n });\n\n onBeforeUnmount(() => {\n window.removeEventListener('resize', () => dismiss());\n contentRef?.value?.parentNode?.removeChild(contentRef.value);\n });\n\n /**\n * Dismisses the Dropdown.\n */\n function dismiss(event?: Event) {\n if (\n props.closeManually &&\n (contentRef.value?.contains(event?.target as HTMLElement) || contentRef.value === event?.target)\n ) {\n return;\n }\n\n const wasOpen = isActive.value;\n\n isActive.value = false;\n\n if (wasOpen) {\n emit('dismiss');\n }\n }\n\n /**\n * Opens or closes the Dropdown. If opening, positions and focuses its content.\n */\n async function toggle() {\n if (isActive.value) {\n isActive.value = false;\n } else {\n isActive.value = true;\n await calculateOffset();\n contentRef.value?.focus();\n }\n }\n\n /**\n * Handles all user keyboard input on the Dropdown.\n * @param {KeyboardEvent} e The native keydown event.\n */\n function onKeyDown(e: KeyboardEvent) {\n if (e.keyCode === KEY_CODES.ESCAPE) {\n dismiss();\n } else if (e.keyCode === KEY_CODES.DOWN && itemIndex.value < items.value.length - 1 && isActive.value) {\n itemIndex.value++;\n } else if (e.keyCode === KEY_CODES.UP && itemIndex.value > -1 && isActive.value) {\n itemIndex.value--;\n } else if (e.keyCode === KEY_CODES.ENTER && itemIndex.value !== -1 && isActive.value) {\n items[itemIndex.value].click();\n } else {\n return;\n }\n\n e.preventDefault(); // if keyDown did something / we didn't return\n }\n\n /**\n * Reset any page overflow offset\n */\n function resetOffset() {\n contentRef.value?.style.setProperty('--offset', '0');\n }\n\n /**\n * Calculate the position for the Dropdown content element,\n * relative to the viewport.\n * Repositions the dropdown content element (& arrow)\n * if it overflows the page (including gutter).\n */\n async function calculateOffset() {\n await nextTick();\n\n const toggleBCR = dropdownRef.value?.getBoundingClientRect() || ({} as DOMRect);\n const top = toggleBCR.top + toggleBCR.height;\n const offset = {\n x: 0,\n y: 0,\n ...props.offset,\n };\n\n if (props.reattach) {\n styles.value = {\n left: props.align === 'left' ? `${Math.round(toggleBCR.left) + offset.x}px` : undefined,\n right:\n props.align === 'right'\n ? `${document.documentElement.clientWidth - Math.round(toggleBCR.right) + offset.x}px`\n : undefined,\n top: `${window.scrollY + top + offset.y}px`,\n };\n await nextTick(); // wait for style updates to sync in the DOM\n }\n\n const overflow = calculateElementOverflow(contentRef?.value);\n\n if (overflow) {\n const direction = props.align === 'left' ? '-' : '';\n contentRef.value?.style.setProperty('--offset', `${direction}${overflow.value}`);\n }\n }\n\n defineExpose({\n isActive: computed(() => isActive.value),\n toggle,\n dismiss,\n });\n</script>\n\n<template>\n <div\n ref=\"dropdownRef\"\n v-clickoutside=\"dismiss\"\n class=\"relative inline-block\"\n data-test=\"ll-dropdown\"\n @keydown=\"onKeyDown\"\n >\n <!-- @slot Toggle component slot, receives `toggle` method to trigger Dropdown display -->\n <slot name=\"toggle\" :is-active=\"isActive\" :toggle=\"toggle\">\n <button\n data-test=\"button|toggle\"\n :aria-expanded=\"isActive\"\n :class=\"[label ? 'outline-hidden min-w-auto' : 'rounded p-3']\"\n @click=\"toggle\"\n >\n <span v-if=\"label\" class=\"flex items-center font-medium text-blue-500 hover:text-blue-700\">\n {{ label }}\n <Icon name=\"caret-down\" />\n </span>\n <Icon v-else name=\"ellipsis\" />\n </button>\n </slot>\n\n <transition name=\"fade\" @after-leave=\"resetOffset\">\n <div\n v-show=\"isActive\"\n ref=\"contentRef\"\n data-test=\"ll-dropdown-content\"\n :class=\"[\n classes.content,\n contentClass,\n {\n 'left-0 after:left-6': props.align === 'left',\n 'right-0': props.align === 'right',\n 'max-w-[360px]': !props.fluidContent,\n },\n ]\"\n :style=\"styles\"\n >\n <!-- @slot Dropdown content slot. Receives `dismiss` method, to trigger manual close. You must call dismiss with parentheses. Receives `is-active` prop. -->\n <slot :dismiss=\"dismiss\" :is-active=\"isActive\"></slot>\n </div>\n </transition>\n </div>\n</template>\n\n<style module>\n @reference \"../../../styles/main.css\";\n\n @layer utilities {\n .content {\n --offset: 0;\n\n @apply shadow-2xl\n bg-white\n border\n border-solid\n border-blue-500\n rounded\n absolute\n z-[301]\n min-w-[200px];\n\n transform: translateX(calc(var(--offset) * 1px));\n }\n\n /**\n * TODO: these global styles should be componentized in https://leaflink.atlassian.net/browse/STASH-566\n */\n :global(.dropdown__list) {\n @apply bg-white list-none m-1.5 p-0;\n }\n\n :global(.dropdown__item) {\n @apply block mx-0 mt-0 mb-1.5 w-full first:mt-0 last:mb-0;\n }\n\n :global(.dropdown__item) button {\n @apply border-none text-left p-1.5;\n }\n\n :global(.dropdown__item) :where(a, button) {\n @apply rounded\n text-ice-700\n block\n py-[10px]\n px-1.5\n w-full\n disabled:text-ice-500\n disabled:cursor-default\n hover:bg-ice-200\n hover:text-ice-700\n hover:no-underline\n transition-all\n duration-150\n ease-swing;\n }\n }\n</style>\n"],"names":["props","__props","emit","__emit","contentRef","ref","dropdownRef","DEFAULT_ITEMS_LIST","items","itemIndex","isActive","styles","classes","useCssModule","watch","val","next","prev","onMounted","target","getMountPoint","_a","_b","_c","dismiss","onBeforeUnmount","event","wasOpen","toggle","calculateOffset","onKeyDown","KEY_CODES","resetOffset","nextTick","toggleBCR","top","offset","overflow","calculateElementOverflow","direction","__expose","computed"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0DE,UAAMA,IAAQC,GAURC,IAAOC,GAKPC,IAAaC,EAAwB,IAAI,GACzCC,IAAcD,EAA2B,IAAI,GAE7CE,IAAqB,CAAA,GAErBC,IAAQH,EAAyBE,CAAkB,GACnDE,IAAYJ,EAAI,EAAE,GAClBK,IAAWL,EAAI,EAAK,GAEpBM,IAASN,EAAoB,EAAE,GAC/BO,IAAUC,EAAA;AAEhB,IAAAC,EAAMJ,GAAU,CAACK,MAAQ;AACvB,MAAKA,MACHN,EAAU,QAAQ,KAGpBP,EAAK,UAAUa,CAAG;AAAA,IACpB,CAAC,GAEDD,EAAML,GAAW,CAACO,GAAMC,MAAS;AAC/B,MAAID,KAAQR,EAAM,SAChBA,EAAM,MAAMQ,CAAI,EAAE,UAAU,IAAI,YAAY,GAE1CC,KAAQT,EAAM,SAChBA,EAAM,MAAMS,CAAI,EAAE,UAAU,OAAO,YAAY;AAAA,IAEnD,CAAC,GAEDC,EAAU,MAAM;;AACd,UAAIlB,EAAM,UAAU;AAClB,cAAMmB,IAASC,EAAA;AAEf,QAAIhB,EAAW,SACbe,EAAO,YAAYf,EAAW,KAAK;AAAA,MAEvC;AAGA,OAAIiB,IAAAjB,EAAW,UAAX,QAAAiB,EAAkB,cAAc,iBAClCb,EAAM,UAAQc,IAAAlB,EAAW,UAAX,gBAAAkB,EAAkB,iBAAiB,wBAAuBf,IAExEC,EAAM,UAAQe,IAAAnB,EAAW,UAAX,gBAAAmB,EAAkB,iBAAiB,uBAAsBhB,GAIzE,OAAO,iBAAiB,UAAU,MAAMiB,EAAA,CAAS;AAAA,IACnD,CAAC,GAEDC,EAAgB,MAAM;;AACpB,aAAO,oBAAoB,UAAU,MAAMD,EAAA,CAAS,IACpDF,KAAAD,IAAAjB,KAAA,gBAAAA,EAAY,UAAZ,gBAAAiB,EAAmB,eAAnB,QAAAC,EAA+B,YAAYlB,EAAW;AAAA,IACxD,CAAC;AAKD,aAASoB,EAAQE,GAAe;;AAC9B,UACE1B,EAAM,mBACLqB,IAAAjB,EAAW,UAAX,QAAAiB,EAAkB,SAASK,KAAA,gBAAAA,EAAO,WAA0BtB,EAAW,WAAUsB,KAAA,gBAAAA,EAAO;AAEzF;AAGF,YAAMC,IAAUjB,EAAS;AAEzB,MAAAA,EAAS,QAAQ,IAEbiB,KACFzB,EAAK,SAAS;AAAA,IAElB;AAKA,mBAAe0B,IAAS;;AACtB,MAAIlB,EAAS,QACXA,EAAS,QAAQ,MAEjBA,EAAS,QAAQ,IACjB,MAAMmB,EAAA,IACNR,IAAAjB,EAAW,UAAX,QAAAiB,EAAkB;AAAA,IAEtB;AAMA,aAASS,EAAU,GAAkB;AACnC,UAAI,EAAE,YAAYC,EAAU;AAC1B,QAAAP,EAAA;AAAA,eACS,EAAE,YAAYO,EAAU,QAAQtB,EAAU,QAAQD,EAAM,MAAM,SAAS,KAAKE,EAAS;AAC9F,QAAAD,EAAU;AAAA,eACD,EAAE,YAAYsB,EAAU,MAAMtB,EAAU,QAAQ,MAAMC,EAAS;AACxE,QAAAD,EAAU;AAAA,eACD,EAAE,YAAYsB,EAAU,SAAStB,EAAU,UAAU,MAAMC,EAAS;AAC7E,QAAAF,EAAMC,EAAU,KAAK,EAAE,MAAA;AAAA;AAEvB;AAGF,QAAE,eAAA;AAAA,IACJ;AAKA,aAASuB,IAAc;;AACrB,OAAAX,IAAAjB,EAAW,UAAX,QAAAiB,EAAkB,MAAM,YAAY,YAAY;AAAA,IAClD;AAQA,mBAAeQ,IAAkB;;AAC/B,YAAMI,EAAA;AAEN,YAAMC,MAAYb,IAAAf,EAAY,UAAZ,gBAAAe,EAAmB,4BAA4B,CAAA,GAC3Dc,IAAMD,EAAU,MAAMA,EAAU,QAChCE,IAAS;AAAA,QACb,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAGpC,EAAM;AAAA,MAAA;AAGX,MAAIA,EAAM,aACRW,EAAO,QAAQ;AAAA,QACb,MAAMX,EAAM,UAAU,SAAS,GAAG,KAAK,MAAMkC,EAAU,IAAI,IAAIE,EAAO,CAAC,OAAO;AAAA,QAC9E,OACEpC,EAAM,UAAU,UACZ,GAAG,SAAS,gBAAgB,cAAc,KAAK,MAAMkC,EAAU,KAAK,IAAIE,EAAO,CAAC,OAChF;AAAA,QACN,KAAK,GAAG,OAAO,UAAUD,IAAMC,EAAO,CAAC;AAAA,MAAA,GAEzC,MAAMH,EAAA;AAGR,YAAMI,IAAWC,EAAyBlC,KAAA,gBAAAA,EAAY,KAAK;AAE3D,UAAIiC,GAAU;AACZ,cAAME,IAAYvC,EAAM,UAAU,SAAS,MAAM;AACjD,SAAAsB,IAAAlB,EAAW,UAAX,QAAAkB,EAAkB,MAAM,YAAY,YAAY,GAAGiB,CAAS,GAAGF,EAAS,KAAK;AAAA,MAC/E;AAAA,IACF;AAEA,WAAAG,EAAa;AAAA,MACX,UAAUC,EAAS,MAAM/B,EAAS,KAAK;AAAA,MACvC,QAAAkB;AAAA,MACA,SAAAJ;AAAA,IAAA,CACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/EmptyState.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { defineComponent as f, useCssModule as p, useSlots as
|
|
2
|
-
import { t as
|
|
3
|
-
import { _ as
|
|
4
|
-
import { _ as
|
|
5
|
-
const
|
|
1
|
+
import { defineComponent as f, useCssModule as p, useSlots as _, createElementBlock as o, openBlock as s, createBlock as y, createCommentVNode as i, createElementVNode as u, renderSlot as a, toDisplayString as n, normalizeClass as m, unref as r, createTextVNode as g } from "vue";
|
|
2
|
+
import { t as b } from "./locale.js";
|
|
3
|
+
import { _ as x } from "./Illustration.vue_vue_type_script_setup_true_lang-C1bPkWZZ.js";
|
|
4
|
+
import { _ as v } from "./_plugin-vue_export-helper-CHgC5LLL.js";
|
|
5
|
+
const h = {
|
|
6
6
|
key: "empty",
|
|
7
7
|
class: "stash-empty-state",
|
|
8
8
|
"data-test": "stash-empty-state"
|
|
9
|
-
},
|
|
9
|
+
}, k = {
|
|
10
10
|
key: 0,
|
|
11
|
-
class: "
|
|
12
|
-
},
|
|
11
|
+
class: "flex flex-col items-center text-center md:flex-row md:text-left"
|
|
12
|
+
}, S = { class: "mb-1.5" }, $ = ["alt", "src"], z = /* @__PURE__ */ f({
|
|
13
13
|
name: "ll-empty-state",
|
|
14
14
|
__name: "EmptyState",
|
|
15
15
|
props: {
|
|
@@ -25,58 +25,58 @@ const x = {
|
|
|
25
25
|
vignetteSize: { default: 300 }
|
|
26
26
|
},
|
|
27
27
|
setup(d) {
|
|
28
|
-
const t = d, c = p(), e =
|
|
29
|
-
return (l,
|
|
30
|
-
t.title || e.title || t.subtitle || e.subtitle || t.vignette ? (s(), o("div",
|
|
31
|
-
t.vignette ? (s(),
|
|
28
|
+
const t = d, c = p(), e = _();
|
|
29
|
+
return (l, N) => (s(), o("div", h, [
|
|
30
|
+
t.title || e.title || t.subtitle || e.subtitle || t.vignette ? (s(), o("div", k, [
|
|
31
|
+
t.vignette ? (s(), y(x, {
|
|
32
32
|
key: 0,
|
|
33
33
|
"data-test": "stash-empty-state|illustration",
|
|
34
34
|
type: "vignette",
|
|
35
|
-
class: "
|
|
35
|
+
class: "mx-3",
|
|
36
36
|
name: t.vignette,
|
|
37
37
|
size: t.vignetteSize
|
|
38
38
|
}, null, 8, ["name", "size"])) : i("", !0),
|
|
39
39
|
u("section", null, [
|
|
40
40
|
a(l.$slots, "title", {}, () => [
|
|
41
|
-
u("h3",
|
|
41
|
+
u("h3", S, n(t.title), 1)
|
|
42
42
|
]),
|
|
43
43
|
a(l.$slots, "subtitle", {}, () => [
|
|
44
44
|
t.subtitle || e.subtitle ? (s(), o("p", {
|
|
45
45
|
key: 0,
|
|
46
|
-
class: m({ "
|
|
46
|
+
class: m({ "mb-8": e.button, "mb-0": !e.button })
|
|
47
47
|
}, n(t.subtitle), 3)) : i("", !0)
|
|
48
48
|
]),
|
|
49
49
|
a(l.$slots, "button"),
|
|
50
50
|
a(l.$slots, "footnote", {}, () => [
|
|
51
51
|
t.footnote ? (s(), o("p", {
|
|
52
52
|
key: 0,
|
|
53
|
-
class: m(["
|
|
53
|
+
class: m(["mb-0 text-xs", { "mt-3": e.button }])
|
|
54
54
|
}, n(t.footnote), 3)) : i("", !0)
|
|
55
55
|
])
|
|
56
56
|
])
|
|
57
57
|
])) : (s(), o("div", {
|
|
58
58
|
key: 1,
|
|
59
|
-
class: m(["
|
|
59
|
+
class: m(["mx-auto my-0 flex items-center justify-center py-12", [r(c).root, { "text-center": !t.image.src && !e.image }]])
|
|
60
60
|
}, [
|
|
61
61
|
a(l.$slots, "image", {}, () => [
|
|
62
62
|
t.image.src ? (s(), o("img", {
|
|
63
63
|
key: 0,
|
|
64
|
-
class: m(["
|
|
64
|
+
class: m(["mr-6", r(c).image]),
|
|
65
65
|
alt: t.image.alt,
|
|
66
66
|
src: t.image.src,
|
|
67
67
|
"data-test": "stash-empty-state__img"
|
|
68
|
-
}, null, 10,
|
|
68
|
+
}, null, 10, $)) : i("", !0)
|
|
69
69
|
]),
|
|
70
|
-
|
|
70
|
+
g(" " + n(t.text || r(b)("ll.emptyState.noResults")), 1)
|
|
71
71
|
], 2))
|
|
72
72
|
]));
|
|
73
73
|
}
|
|
74
|
-
}),
|
|
75
|
-
root:
|
|
76
|
-
image:
|
|
77
|
-
},
|
|
78
|
-
$style:
|
|
79
|
-
}, R = /* @__PURE__ */
|
|
74
|
+
}), C = "_root_1ibnx_3", E = "_image_1ibnx_7", B = {
|
|
75
|
+
root: C,
|
|
76
|
+
image: E
|
|
77
|
+
}, M = {
|
|
78
|
+
$style: B
|
|
79
|
+
}, R = /* @__PURE__ */ v(z, [["__cssModules", M]]);
|
|
80
80
|
export {
|
|
81
81
|
R as default
|
|
82
82
|
};
|
package/dist/EmptyState.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","sources":["../src/components/EmptyState/EmptyState.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { useCssModule } from 'vue';\n\n import { t } from '../../locale';\n import { VignetteNames } from '../Illustration/Illustration.models';\n import Illustration from '../Illustration/Illustration.vue';\n\n export interface EmptyStateProps {\n /**\n * @deprecated Use illustration instead.\n */\n image?: {\n alt?: string;\n src: string;\n };\n\n /**\n * @deprecated Use title instead.\n */\n text?: string;\n\n /**\n * the title text of the empty state\n */\n title?: string;\n\n /**\n * the subtitle text of the empty state\n */\n subtitle?: string;\n\n /**\n * additional text to place at the bottom of the empty state\n */\n footnote?: string;\n\n /**\n * The name of the vignette illustration to render\n */\n vignette?: VignetteNames;\n\n /**\n * The size in pixels of the vignette illustration. Defaults to 300.\n */\n vignetteSize?: number;\n }\n\n export interface EmptyStateSlots {\n title?: () => unknown;\n subtitle?: () => unknown;\n footnote?: () => unknown;\n button?: () => unknown;\n image?: () => unknown;\n }\n\n defineOptions({\n name: 'll-empty-state',\n });\n\n const props = withDefaults(defineProps<EmptyStateProps>(), {\n /**\n * An optional image to render\n */\n image: () => ({\n alt: '',\n src: '',\n }),\n\n /**\n * The custom message to render\n */\n text: '',\n\n title: '',\n subtitle: '',\n footnote: '',\n vignette: undefined,\n vignetteSize: 300,\n });\n\n const classes = useCssModule();\n const slots = defineSlots<EmptyStateSlots>();\n</script>\n\n<template>\n <div key=\"empty\" class=\"stash-empty-state\" data-test=\"stash-empty-state\">\n <template v-if=\"props.title || slots.title || props.subtitle || slots.subtitle || props.vignette\">\n <div class=\"
|
|
1
|
+
{"version":3,"file":"EmptyState.js","sources":["../src/components/EmptyState/EmptyState.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { useCssModule } from 'vue';\n\n import { t } from '../../locale';\n import { VignetteNames } from '../Illustration/Illustration.models';\n import Illustration from '../Illustration/Illustration.vue';\n\n export interface EmptyStateProps {\n /**\n * @deprecated Use illustration instead.\n */\n image?: {\n alt?: string;\n src: string;\n };\n\n /**\n * @deprecated Use title instead.\n */\n text?: string;\n\n /**\n * the title text of the empty state\n */\n title?: string;\n\n /**\n * the subtitle text of the empty state\n */\n subtitle?: string;\n\n /**\n * additional text to place at the bottom of the empty state\n */\n footnote?: string;\n\n /**\n * The name of the vignette illustration to render\n */\n vignette?: VignetteNames;\n\n /**\n * The size in pixels of the vignette illustration. Defaults to 300.\n */\n vignetteSize?: number;\n }\n\n export interface EmptyStateSlots {\n title?: () => unknown;\n subtitle?: () => unknown;\n footnote?: () => unknown;\n button?: () => unknown;\n image?: () => unknown;\n }\n\n defineOptions({\n name: 'll-empty-state',\n });\n\n const props = withDefaults(defineProps<EmptyStateProps>(), {\n /**\n * An optional image to render\n */\n image: () => ({\n alt: '',\n src: '',\n }),\n\n /**\n * The custom message to render\n */\n text: '',\n\n title: '',\n subtitle: '',\n footnote: '',\n vignette: undefined,\n vignetteSize: 300,\n });\n\n const classes = useCssModule();\n const slots = defineSlots<EmptyStateSlots>();\n</script>\n\n<template>\n <div key=\"empty\" class=\"stash-empty-state\" data-test=\"stash-empty-state\">\n <template v-if=\"props.title || slots.title || props.subtitle || slots.subtitle || props.vignette\">\n <div class=\"flex flex-col items-center text-center md:flex-row md:text-left\">\n <Illustration\n v-if=\"props.vignette\"\n data-test=\"stash-empty-state|illustration\"\n type=\"vignette\"\n class=\"mx-3\"\n :name=\"props.vignette\"\n :size=\"props.vignetteSize\"\n />\n <section>\n <slot name=\"title\">\n <h3 class=\"mb-1.5\">\n {{ props.title }}\n </h3>\n </slot>\n\n <slot name=\"subtitle\">\n <p v-if=\"props.subtitle || slots.subtitle\" :class=\"{ 'mb-8': slots.button, 'mb-0': !slots.button }\">\n {{ props.subtitle }}\n </p>\n </slot>\n\n <slot name=\"button\"></slot>\n <slot name=\"footnote\">\n <p v-if=\"props.footnote\" class=\"mb-0 text-xs\" :class=\"{ 'mt-3': slots.button }\">\n {{ props.footnote }}\n </p>\n </slot>\n </section>\n </div>\n </template>\n\n <template v-else>\n <div\n class=\"mx-auto my-0 flex items-center justify-center py-12\"\n :class=\"[classes.root, { 'text-center': !props.image.src && !slots.image }]\"\n >\n <slot name=\"image\">\n <img\n v-if=\"props.image.src\"\n class=\"mr-6\"\n :alt=\"props.image.alt\"\n :class=\"classes.image\"\n :src=\"props.image.src\"\n data-test=\"stash-empty-state__img\"\n />\n </slot>\n\n {{ props.text || t('ll.emptyState.noResults') }}\n </div>\n </template>\n </div>\n</template>\n\n<style module>\n @layer utilities {\n .root {\n max-width: 600px;\n }\n\n .image {\n max-width: 60px;\n }\n }\n</style>\n"],"names":["props","__props","classes","useCssModule","slots","_useSlots"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DE,UAAMA,IAAQC,GAqBRC,IAAUC,EAAA,GACVC,IAAQC,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/Field.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { defineComponent as
|
|
1
|
+
import { defineComponent as I, useAttrs as O, useSlots as T, computed as t, createBlock as c, openBlock as l, resolveDynamicComponent as w, mergeProps as R, unref as d, withCtx as b, createCommentVNode as m, renderSlot as y, createElementBlock as o, createTextVNode as k, toDisplayString as r } from "vue";
|
|
2
2
|
import n from "lodash-es/uniqueId";
|
|
3
|
-
import { _ as q } from "./Label.vue_vue_type_script_setup_true_lang-
|
|
3
|
+
import { _ as q } from "./Label.vue_vue_type_script_setup_true_lang-xwY3X-iV.js";
|
|
4
4
|
const S = ["id"], D = {
|
|
5
5
|
key: 2,
|
|
6
|
-
class: "stash-field-hint
|
|
6
|
+
class: "stash-field-hint mt-1 block whitespace-pre-line text-xs",
|
|
7
7
|
"data-test": "stash-field-hint"
|
|
8
8
|
}, E = {
|
|
9
9
|
key: 3,
|
|
10
|
-
class: "stash-field-hint
|
|
10
|
+
class: "stash-field-hint mt-1 whitespace-pre-line text-xs",
|
|
11
11
|
"data-test": "stash-field-hint"
|
|
12
|
-
}, C = /* @__PURE__ */
|
|
12
|
+
}, C = /* @__PURE__ */ I({
|
|
13
13
|
inheritAttrs: !1,
|
|
14
14
|
__name: "Field",
|
|
15
15
|
props: {
|
|
@@ -27,23 +27,23 @@ const S = ["id"], D = {
|
|
|
27
27
|
isDisabled: { type: Boolean, default: !1 },
|
|
28
28
|
disabled: { type: Boolean, default: !1 }
|
|
29
29
|
},
|
|
30
|
-
setup(
|
|
31
|
-
const e =
|
|
32
|
-
const { placeholder: s, ...p } =
|
|
30
|
+
setup(v) {
|
|
31
|
+
const e = v, x = O(), a = T(), f = t(() => e.id || n("stash-field-")), h = t(() => e.errorId || n("stash-field-error-")), u = t(() => n("stash-field-label-")), i = t(() => !!e.errorText), B = t(() => e.fieldset ? "fieldset" : "div"), _ = t(() => {
|
|
32
|
+
const { placeholder: s, ...p } = x;
|
|
33
33
|
return p;
|
|
34
34
|
});
|
|
35
|
-
return (s, p) => (l(), c(
|
|
35
|
+
return (s, p) => (l(), c(w(B.value), R({
|
|
36
36
|
"data-test": "stash-field",
|
|
37
37
|
class: ["stash-field", [
|
|
38
|
-
{ "
|
|
39
|
-
{ "
|
|
40
|
-
{ "
|
|
38
|
+
{ "p-0": e.fieldset },
|
|
39
|
+
{ "mb-9": e.addBottomSpace && !e.errorText && !e.hintText && !d(a).hint },
|
|
40
|
+
{ "mb-4": e.addBottomSpace && (e.errorText || e.hintText || d(a).hint) },
|
|
41
41
|
{ "stash-field--disabled": e.isDisabled || e.disabled },
|
|
42
42
|
{ "stash-field--is-read-only": e.isReadOnly },
|
|
43
43
|
{ "stash-field--is-required": e.isRequired },
|
|
44
44
|
{ "stash-field--has-error": i.value }
|
|
45
45
|
]]
|
|
46
|
-
},
|
|
46
|
+
}, _.value), {
|
|
47
47
|
default: b(() => [
|
|
48
48
|
e.label ? (l(), c(q, {
|
|
49
49
|
key: 0,
|
|
@@ -61,7 +61,7 @@ const S = ["id"], D = {
|
|
|
61
61
|
]),
|
|
62
62
|
_: 1
|
|
63
63
|
}, 8, ["id", "for", "has-error", "is-required", "show-optional", "is-sr-only", "legend", "disabled"])) : m("", !0),
|
|
64
|
-
|
|
64
|
+
y(s.$slots, "default", {
|
|
65
65
|
fieldId: f.value,
|
|
66
66
|
fieldErrorId: h.value,
|
|
67
67
|
hasError: i.value,
|
|
@@ -74,10 +74,10 @@ const S = ["id"], D = {
|
|
|
74
74
|
e.errorText ? (l(), o("span", {
|
|
75
75
|
key: 1,
|
|
76
76
|
id: h.value,
|
|
77
|
-
class: "stash-field-error
|
|
77
|
+
class: "stash-field-error mt-1 block whitespace-pre-line text-xs text-red-500",
|
|
78
78
|
"data-test": "stash-field-error"
|
|
79
79
|
}, r(e.errorText), 9, S)) : e.hintText && !e.isReadOnly ? (l(), o("span", D, r(e.hintText), 1)) : d(a).hint && !e.isReadOnly ? (l(), o("div", E, [
|
|
80
|
-
|
|
80
|
+
y(s.$slots, "hint")
|
|
81
81
|
])) : m("", !0)
|
|
82
82
|
]),
|
|
83
83
|
_: 3
|
|
@@ -87,4 +87,4 @@ const S = ["id"], D = {
|
|
|
87
87
|
export {
|
|
88
88
|
C as _
|
|
89
89
|
};
|
|
90
|
-
//# sourceMappingURL=Field.vue_vue_type_script_setup_true_lang-
|
|
90
|
+
//# sourceMappingURL=Field.vue_vue_type_script_setup_true_lang-dAGKfjf5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Field.vue_vue_type_script_setup_true_lang-dAGKfjf5.js","sources":["../src/components/Field/Field.vue"],"sourcesContent":["<script lang=\"ts\">\n import { FieldProps } from './Field.types';\n\n export * from './Field.types';\n</script>\n\n<script setup lang=\"ts\">\n import uniqueId from 'lodash-es/uniqueId';\n import { computed, useAttrs, useSlots } from 'vue';\n\n import Label from '../Label/Label.vue';\n\n defineOptions({\n inheritAttrs: false,\n });\n\n const props = withDefaults(defineProps<FieldProps>(), {\n addBottomSpace: false,\n errorText: undefined,\n hintText: undefined,\n id: undefined,\n errorId: undefined,\n isRequired: false,\n label: undefined,\n showOptionalInLabel: false,\n isLabelSrOnly: false,\n fieldset: false,\n isDisabled: false,\n disabled: false,\n });\n const attrs = useAttrs();\n const slots = useSlots();\n const fieldId = computed(() => props.id || uniqueId('stash-field-'));\n const fieldErrorId = computed(() => props.errorId || uniqueId('stash-field-error-'));\n const labelId = computed(() => uniqueId('stash-field-label-'));\n const hasError = computed(() => !!props.errorText);\n const wrapperElement = computed(() => (props.fieldset ? 'fieldset' : 'div'));\n\n // Any attributes that are unique to form elements, you want to exclude from\n // being bound from the root element.\n const rootAttrs = computed(() => {\n const { placeholder, ...otherAttrs } = attrs;\n\n return otherAttrs;\n });\n</script>\n\n<template>\n <component\n :is=\"wrapperElement\"\n data-test=\"stash-field\"\n class=\"stash-field\"\n :class=\"[\n { 'p-0': props.fieldset },\n { 'mb-9': props.addBottomSpace && !props.errorText && !props.hintText && !slots.hint },\n { 'mb-4': props.addBottomSpace && (props.errorText || props.hintText || slots.hint) },\n { 'stash-field--disabled': props.isDisabled || props.disabled },\n { 'stash-field--is-read-only': props.isReadOnly },\n { 'stash-field--is-required': props.isRequired },\n { 'stash-field--has-error': hasError },\n ]\"\n v-bind=\"rootAttrs\"\n >\n <Label\n v-if=\"props.label\"\n :id=\"labelId\"\n :for=\"fieldId\"\n :has-error=\"hasError\"\n :is-required=\"isRequired\"\n :show-optional=\"props.showOptionalInLabel\"\n :is-sr-only=\"props.isLabelSrOnly\"\n :legend=\"props.fieldset\"\n :disabled=\"props.isDisabled || props.disabled\"\n >\n {{ props.label }}\n </Label>\n\n <!-- @slot for the form field; the Label can also be rendered here instead of using the label prop -->\n <slot\n :field-id=\"fieldId\"\n :field-error-id=\"fieldErrorId\"\n :has-error=\"hasError\"\n :is-required=\"isRequired\"\n :is-read-only=\"props.isReadOnly\"\n :disabled=\"props.isDisabled || props.disabled\"\n :label-id=\"labelId\"\n :show-optional-in-label=\"props.showOptionalInLabel\"\n ></slot>\n\n <span\n v-if=\"props.errorText\"\n :id=\"fieldErrorId\"\n class=\"stash-field-error mt-1 block whitespace-pre-line text-xs text-red-500\"\n data-test=\"stash-field-error\"\n >\n {{ props.errorText }}\n </span>\n\n <span\n v-else-if=\"props.hintText && !props.isReadOnly\"\n class=\"stash-field-hint mt-1 block whitespace-pre-line text-xs\"\n data-test=\"stash-field-hint\"\n >\n {{ props.hintText }}\n </span>\n\n <div\n v-else-if=\"slots.hint && !props.isReadOnly\"\n class=\"stash-field-hint mt-1 whitespace-pre-line text-xs\"\n data-test=\"stash-field-hint\"\n >\n <!-- @slot for displaying hint text below the field -->\n <slot name=\"hint\"></slot>\n </div>\n </component>\n</template>\n"],"names":["props","__props","attrs","useAttrs","slots","useSlots","fieldId","computed","uniqueId","fieldErrorId","labelId","hasError","wrapperElement","rootAttrs","placeholder","otherAttrs"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBE,UAAMA,IAAQC,GAcRC,IAAQC,EAAA,GACRC,IAAQC,EAAA,GACRC,IAAUC,EAAS,MAAMP,EAAM,MAAMQ,EAAS,cAAc,CAAC,GAC7DC,IAAeF,EAAS,MAAMP,EAAM,WAAWQ,EAAS,oBAAoB,CAAC,GAC7EE,IAAUH,EAAS,MAAMC,EAAS,oBAAoB,CAAC,GACvDG,IAAWJ,EAAS,MAAM,CAAC,CAACP,EAAM,SAAS,GAC3CY,IAAiBL,EAAS,MAAOP,EAAM,WAAW,aAAa,KAAM,GAIrEa,IAAYN,EAAS,MAAM;AAC/B,YAAM,EAAE,aAAAO,GAAa,GAAGC,EAAA,IAAeb;AAEvC,aAAOa;AAAA,IACT,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/FileUpload.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { defineComponent as
|
|
1
|
+
import { defineComponent as H, useCssModule as K, ref as U, inject as Q, useAttrs as W, computed as g, createElementBlock as r, openBlock as n, normalizeClass as c, unref as l, withDirectives as Z, createVNode as E, withModifiers as p, withCtx as S, renderSlot as N, createTextVNode as F, toDisplayString as u, createElementVNode as v, createCommentVNode as k, Fragment as R, createBlock as ee, renderList as te, mergeProps as le, vShow as se } from "vue";
|
|
2
2
|
import ne from "@leaflink/snitch";
|
|
3
3
|
import { I as oe } from "./index-D6bxWkZ1.js";
|
|
4
4
|
import { t as f } from "./locale.js";
|
|
5
|
-
import
|
|
5
|
+
import w from "./Button.js";
|
|
6
6
|
import ae from "./Icon.js";
|
|
7
7
|
import { _ as ie } from "./_plugin-vue_export-helper-CHgC5LLL.js";
|
|
8
8
|
const M = {
|
|
@@ -38,10 +38,10 @@ const M = {
|
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
var h = /* @__PURE__ */ ((m) => (m.Dense = "dense", m.Standard = "standard", m))(h || {});
|
|
41
|
-
const re = { key: 0 }, de = { class: "
|
|
41
|
+
const re = { key: 0 }, de = { class: "text-ice-900" }, ce = {
|
|
42
42
|
key: 0,
|
|
43
|
-
class: "
|
|
44
|
-
}, pe = ["disabled", "accept", "multiple"], ue = /* @__PURE__ */
|
|
43
|
+
class: "mt-6 text-center text-xs text-ice-700"
|
|
44
|
+
}, pe = ["disabled", "accept", "multiple"], ue = /* @__PURE__ */ H({
|
|
45
45
|
__name: "FileUpload",
|
|
46
46
|
props: {
|
|
47
47
|
files: { default: () => [] },
|
|
@@ -53,14 +53,14 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
53
53
|
},
|
|
54
54
|
emits: ["file-select", "file-delete", "file-error"],
|
|
55
55
|
setup(m, { emit: A }) {
|
|
56
|
-
const o = m, d = K(),
|
|
56
|
+
const o = m, d = K(), _ = A, y = U(!1), T = U(), b = Q("stashOptions"), x = W(), X = g(() => {
|
|
57
57
|
const e = { ...x };
|
|
58
58
|
return delete e["data-test"], delete e.class, delete e.type, delete e.accept, e;
|
|
59
59
|
});
|
|
60
60
|
function D(e, t) {
|
|
61
61
|
return e.concat(t);
|
|
62
62
|
}
|
|
63
|
-
const
|
|
63
|
+
const I = g(() => o.fileTypes.map((e) => M[e].MIME_TYPES).reduce(D)), O = g(() => o.fileTypes.map((e) => M[e].EXTENSION).reduce(D)), Y = g(() => `${b == null ? void 0 : b.staticPath}/illustrations/FileUpload/${M[o.fileTypes[0]].ILLUSTRATION}.svg`);
|
|
64
64
|
function L() {
|
|
65
65
|
T.value && (T.value.value = "", T.value.click());
|
|
66
66
|
}
|
|
@@ -74,12 +74,12 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
74
74
|
const t = f("ll.fileUpload.errors.incorrectFileType", {
|
|
75
75
|
fileTypes: O.value.join(", ")
|
|
76
76
|
});
|
|
77
|
-
|
|
77
|
+
_("file-error", t), ne.log(e);
|
|
78
78
|
}
|
|
79
|
-
async function
|
|
80
|
-
if (!
|
|
81
|
-
const t = await Promise.all(e.map((i) =>
|
|
82
|
-
if (!(!!t.length && t.every((i) =>
|
|
79
|
+
async function q(e) {
|
|
80
|
+
if (!I.value.length) return !0;
|
|
81
|
+
const t = await Promise.all(e.map((i) => J(i)));
|
|
82
|
+
if (!(!!t.length && t.every((i) => I.value.includes(i))))
|
|
83
83
|
throw new Error("One or more files contains an unacceptable mime type.");
|
|
84
84
|
if (!e.every((i) => {
|
|
85
85
|
const C = i.name.split(".").pop();
|
|
@@ -90,27 +90,27 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
90
90
|
}
|
|
91
91
|
async function P(e) {
|
|
92
92
|
try {
|
|
93
|
-
await
|
|
93
|
+
await q(e), _("file-select", { files: e });
|
|
94
94
|
} catch (t) {
|
|
95
95
|
B(t);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
function
|
|
98
|
+
function V(e) {
|
|
99
99
|
var s;
|
|
100
100
|
const t = [...((s = e.target) == null ? void 0 : s.files) || []];
|
|
101
101
|
P(t);
|
|
102
102
|
}
|
|
103
|
-
function
|
|
103
|
+
function z(e) {
|
|
104
104
|
var s;
|
|
105
105
|
if (o.disabled)
|
|
106
106
|
return;
|
|
107
107
|
const t = [...((s = e.dataTransfer) == null ? void 0 : s.files) || []];
|
|
108
108
|
return y.value = !1, P(t);
|
|
109
109
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
110
|
+
function G(e) {
|
|
111
|
+
_("file-delete", e);
|
|
112
112
|
}
|
|
113
|
-
function
|
|
113
|
+
function J(e) {
|
|
114
114
|
return new Promise((t, s) => {
|
|
115
115
|
if (e.type)
|
|
116
116
|
return t(e.type);
|
|
@@ -129,22 +129,22 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
129
129
|
"data-test": "stash-file-upload"
|
|
130
130
|
}, [
|
|
131
131
|
e.buttonOnly ? (n(), r("div", re, [
|
|
132
|
-
|
|
132
|
+
E(w, {
|
|
133
133
|
secondary: "",
|
|
134
134
|
type: "button",
|
|
135
135
|
disabled: o.disabled,
|
|
136
136
|
onClick: p(L, ["stop", "prevent"])
|
|
137
137
|
}, {
|
|
138
|
-
default:
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
default: S(() => [
|
|
139
|
+
N(e.$slots, "submitText", {}, () => [
|
|
140
|
+
F(u(l(f)("ll.fileUpload.uploadFile")), 1)
|
|
141
141
|
])
|
|
142
142
|
]),
|
|
143
143
|
_: 3
|
|
144
144
|
}, 8, ["disabled"])
|
|
145
145
|
])) : (n(), r("div", {
|
|
146
146
|
key: 1,
|
|
147
|
-
class: c(["
|
|
147
|
+
class: c(["rounded p-6", [
|
|
148
148
|
l(d)["file-dropbox"],
|
|
149
149
|
{
|
|
150
150
|
[l(d)["is-dragging"]]: y.value,
|
|
@@ -152,23 +152,23 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
152
152
|
}
|
|
153
153
|
]]),
|
|
154
154
|
onDragover: p($, ["prevent"]),
|
|
155
|
-
onDrop: p(
|
|
155
|
+
onDrop: p(z, ["prevent"]),
|
|
156
156
|
onDragleave: p(j, ["prevent"])
|
|
157
157
|
}, [
|
|
158
158
|
v("div", {
|
|
159
|
-
class: c(["
|
|
159
|
+
class: c(["flex flex-col items-center justify-center text-center", [{ "items-center md:flex-row": e.size === l(h).Dense }]])
|
|
160
160
|
}, [
|
|
161
161
|
e.files.length ? (n(!0), r(R, { key: 1 }, te(e.files, (s) => (n(), r("div", {
|
|
162
162
|
key: s.name
|
|
163
163
|
}, [
|
|
164
|
-
|
|
164
|
+
E(ae, { name: "file" }),
|
|
165
165
|
v("span", null, u(s.name), 1),
|
|
166
|
-
|
|
166
|
+
E(w, {
|
|
167
167
|
class: c([l(d)["remove-button"], l(d).button]),
|
|
168
|
-
onClick: p((a) =>
|
|
168
|
+
onClick: p((a) => G(s), ["stop", "prevent"])
|
|
169
169
|
}, {
|
|
170
|
-
default:
|
|
171
|
-
|
|
170
|
+
default: S(() => [
|
|
171
|
+
F(u(l(f)("ll.fileUpload.remove")), 1)
|
|
172
172
|
]),
|
|
173
173
|
_: 2
|
|
174
174
|
}, 1032, ["class", "onClick"])
|
|
@@ -182,20 +182,18 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
182
182
|
}, null, 8, ["src"])) : k("", !0),
|
|
183
183
|
v("span", de, u(l(f)("ll.fileUpload.dragDropFileHere")), 1),
|
|
184
184
|
v("span", {
|
|
185
|
-
class: c(
|
|
186
|
-
e.size === l(h).Dense ? "md:tw-ml-1.5 md:tw-mr-3 md:tw-my-0 tw-my-1.5 tw-text-ice-900" : "tw-mt-1.5 tw-my-1.5"
|
|
187
|
-
)
|
|
185
|
+
class: c(e.size === l(h).Dense ? "md:ml-1.5 md:mr-3 md:my-0 my-1.5 text-ice-900" : "mt-1.5 my-1.5")
|
|
188
186
|
}, u(l(f)("ll.fileUpload.or")), 3),
|
|
189
|
-
|
|
190
|
-
class: c(["
|
|
187
|
+
E(w, {
|
|
188
|
+
class: c(["mt-1.5", l(d)["file-select-button"]]),
|
|
191
189
|
secondary: "",
|
|
192
190
|
type: "button",
|
|
193
191
|
disabled: e.disabled,
|
|
194
192
|
onClick: p(L, ["stop", "prevent"])
|
|
195
193
|
}, {
|
|
196
|
-
default:
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
default: S(() => [
|
|
195
|
+
N(e.$slots, "submitText", {}, () => [
|
|
196
|
+
F(u(l(f)("ll.fileUpload.uploadFile")), 1)
|
|
199
197
|
])
|
|
200
198
|
]),
|
|
201
199
|
_: 3
|
|
@@ -203,7 +201,7 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
203
201
|
], 64))
|
|
204
202
|
], 2),
|
|
205
203
|
e.$slots.hint && !e.files.length ? (n(), r("div", ce, [
|
|
206
|
-
|
|
204
|
+
N(e.$slots, "hint")
|
|
207
205
|
])) : k("", !0)
|
|
208
206
|
], 34)),
|
|
209
207
|
Z(v("input", le(X.value, {
|
|
@@ -212,27 +210,27 @@ const re = { key: 0 }, de = { class: "tw-text-ice-900" }, ce = {
|
|
|
212
210
|
"data-test": "stash-file-upload|input",
|
|
213
211
|
type: "file",
|
|
214
212
|
disabled: e.disabled,
|
|
215
|
-
accept:
|
|
213
|
+
accept: I.value.join(","),
|
|
216
214
|
multiple: o.multiple,
|
|
217
|
-
onChange:
|
|
215
|
+
onChange: V
|
|
218
216
|
}), null, 16, pe), [
|
|
219
217
|
[se, !1]
|
|
220
218
|
])
|
|
221
219
|
], 2));
|
|
222
220
|
}
|
|
223
|
-
}), fe = "
|
|
224
|
-
"file-dropbox": "_file-
|
|
225
|
-
"is-dragging": "_is-
|
|
226
|
-
"is-disabled": "_is-
|
|
227
|
-
"upload-icon": "_upload-
|
|
228
|
-
"remove-button": "_remove-
|
|
221
|
+
}), fe = "_button_qf143_30", me = {
|
|
222
|
+
"file-dropbox": "_file-dropbox_qf143_3",
|
|
223
|
+
"is-dragging": "_is-dragging_qf143_11",
|
|
224
|
+
"is-disabled": "_is-disabled_qf143_20",
|
|
225
|
+
"upload-icon": "_upload-icon_qf143_25",
|
|
226
|
+
"remove-button": "_remove-button_qf143_30",
|
|
229
227
|
button: fe
|
|
230
228
|
}, ve = {
|
|
231
229
|
$style: me
|
|
232
|
-
},
|
|
230
|
+
}, Ie = /* @__PURE__ */ ie(ue, [["__cssModules", ve]]);
|
|
233
231
|
export {
|
|
234
232
|
M as FILE_TYPES,
|
|
235
233
|
h as FileUploadSizes,
|
|
236
|
-
|
|
234
|
+
Ie as default
|
|
237
235
|
};
|
|
238
236
|
//# sourceMappingURL=FileUpload.js.map
|
package/dist/FileUpload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUpload.js","sources":["../src/components/FileUpload/FileUpload.constants.ts","../src/components/FileUpload/FileUpload.vue"],"sourcesContent":["export const FILE_TYPES = {\n CSV: {\n EXTENSION: ['csv'],\n MIME_TYPES: ['text/csv', 'application/octet-stream', 'application/vnd.ms-excel'],\n ILLUSTRATION: 'csv',\n },\n PDF: {\n EXTENSION: ['pdf'],\n MIME_TYPES: ['application/pdf'],\n ILLUSTRATION: 'pdf',\n },\n PNG: {\n EXTENSION: ['png'],\n MIME_TYPES: ['image/png'],\n ILLUSTRATION: 'image',\n },\n JPEG: {\n EXTENSION: ['jpg', 'jpeg'],\n MIME_TYPES: ['image/jpeg'],\n ILLUSTRATION: 'image',\n },\n DOC: {\n EXTENSION: ['doc', 'docx'],\n MIME_TYPES: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n ILLUSTRATION: 'document',\n },\n XLS: {\n EXTENSION: ['xls', 'xlsx'],\n MIME_TYPES: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n ILLUSTRATION: 'csv',\n },\n};\n\nexport enum FileUploadSizes {\n Dense = 'dense',\n Standard = 'standard',\n}\n\nexport type FileUploadSize = `${FileUploadSizes}`;\n","<script lang=\"ts\">\n export * from './FileUpload.constants';\n</script>\n\n<script setup lang=\"ts\">\n import logger from '@leaflink/snitch';\n import { computed, inject, ref, useAttrs, useCssModule } from 'vue';\n import InlineSvg from 'vue-inline-svg';\n\n import { StashProvideState } from '../../../types/misc';\n import { t } from '../../locale';\n import Button from '../Button/Button.vue';\n import Icon from '../Icon/Icon.vue';\n import { FILE_TYPES, FileUploadSize, FileUploadSizes } from './FileUpload.constants';\n\n export type FileType = 'CSV' | 'PDF' | 'PNG' | 'JPEG' | 'DOC' | 'XLS';\n\n export interface FileUploadProps {\n /**\n * Files to display in the component\n */\n files?: File[];\n\n /**\n * Accepted file types\n */\n fileTypes?: FileType[];\n\n /**\n * Should display only the button\n */\n buttonOnly?: boolean;\n\n /**\n * Allows upload of multiple files\n */\n multiple?: boolean;\n\n /**\n * Is the input disabled\n */\n disabled?: boolean;\n\n /**\n * Component size\n */\n size?: FileUploadSize;\n }\n\n const props = withDefaults(defineProps<FileUploadProps>(), {\n files: () => [],\n fileTypes: () => ['CSV', 'PDF', 'PNG', 'JPEG', 'DOC', 'XLS'],\n buttonOnly: false,\n disabled: false,\n multiple: false,\n size: 'standard',\n });\n\n const classes = useCssModule();\n\n const emit = defineEmits<{\n (e: 'file-select', { files }: { files: FileUploadProps['files'] }): void;\n (e: 'file-delete', file: File): void;\n (e: 'file-error', message: string): void;\n }>();\n\n const isDraggingOver = ref(false);\n const fileUploadRef = ref<HTMLInputElement>();\n\n const stashOptions = inject<StashProvideState>('stashOptions');\n const attributes = useAttrs();\n\n const inputAttrs = computed(() => {\n const attrs = { ...attributes };\n\n delete attrs['data-test'];\n delete attrs.class;\n delete attrs.type;\n delete attrs.accept;\n\n return attrs;\n });\n\n function concatArraysToFirst(a: string[], b: string[]) {\n return a.concat(b);\n }\n\n const acceptedMimeTypes = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].MIME_TYPES).reduce(concatArraysToFirst);\n });\n\n const acceptedFileExtensions = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].EXTENSION).reduce(concatArraysToFirst);\n });\n\n const illustrationPath = computed(() => {\n return `${stashOptions?.staticPath}/illustrations/FileUpload/${FILE_TYPES[props.fileTypes[0]].ILLUSTRATION}.svg`;\n });\n\n function openFileDialog() {\n if (fileUploadRef.value) {\n fileUploadRef.value.value = '';\n fileUploadRef.value.click();\n }\n }\n\n function handleDragEnter() {\n isDraggingOver.value = true;\n }\n\n function handleDragLeave() {\n isDraggingOver.value = false;\n }\n\n function handleFileError(error: Error) {\n const message = t('ll.fileUpload.errors.incorrectFileType', {\n fileTypes: acceptedFileExtensions.value.join(', '),\n });\n\n emit('file-error', message);\n\n logger.log(error);\n }\n\n async function areFileTypesAccepted(files: File[]) {\n if (!acceptedMimeTypes.value.length) return true;\n\n const mimeTypes = await Promise.all(files.map((file) => readMimeType(file)));\n\n const allCorrectMimeTypes =\n !!mimeTypes.length && mimeTypes.every((mimeType) => acceptedMimeTypes.value.includes(mimeType));\n\n if (!allCorrectMimeTypes) {\n throw new Error('One or more files contains an unacceptable mime type.');\n }\n\n const allCorrectFileExtensions = files.every((file) => {\n const extension = file.name.split('.').pop();\n\n return extension && acceptedFileExtensions.value.includes(extension);\n });\n\n if (!allCorrectFileExtensions) {\n throw new Error('One or more files contains an unacceptable extension.');\n }\n\n return true;\n }\n\n async function processFiles(files: File[]) {\n try {\n await areFileTypesAccepted(files);\n\n emit('file-select', { files });\n } catch (error) {\n handleFileError(error as Error);\n }\n }\n\n /**\n * Sets file(s) to selected file(s) from dialogue\n * @param {Object} event - file select event that contains file(s)\n * @returns {Array} An array of files\n */\n function handleFileInput(event: Event) {\n const files = [...((event.target as HTMLInputElement)?.files || [])];\n\n processFiles(files);\n }\n\n /**\n * Sets file to dropped file if it is proper file type\n * @param {Object} event - file select event that contains file\n */\n function handleDropFile(event: DragEvent) {\n if (props.disabled) {\n return;\n }\n\n const files = [...(event.dataTransfer?.files || [])];\n\n isDraggingOver.value = false;\n\n return processFiles(files);\n }\n\n function handleFileDelete(file: File) {\n emit('file-delete', file);\n }\n\n function readMimeType(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file.type) {\n return resolve(file.type);\n } else if (window.FileReader) {\n const fileReader = new FileReader();\n\n fileReader.onload = () => {\n const mimeType =\n fileReader.result && (fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/)\n ? ((fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/) as string[])[0]\n : '';\n\n resolve(mimeType);\n };\n\n fileReader.readAsDataURL(file);\n } else {\n reject(new Error('Failed to read file.'));\n }\n });\n }\n</script>\n\n<template>\n <div class=\"stash-file-upload\" :class=\"attributes.class\" data-test=\"stash-file-upload\">\n <div v-if=\"buttonOnly\">\n <Button secondary type=\"button\" :disabled=\"props.disabled\" @click.stop.prevent=\"openFileDialog\">\n <slot name=\"submitText\">\n {{ t('ll.fileUpload.uploadFile') }}\n </slot>\n </Button>\n </div>\n <div\n v-else\n class=\"tw-rounded tw-p-6\"\n :class=\"[\n classes['file-dropbox'],\n {\n [classes['is-dragging']]: isDraggingOver,\n [classes['is-disabled']]: props.disabled,\n },\n ]\"\n @dragover.prevent=\"handleDragEnter\"\n @drop.prevent=\"handleDropFile\"\n @dragleave.prevent=\"handleDragLeave\"\n >\n <div\n class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-text-center\"\n :class=\"[{ 'tw-items-center md:tw-flex-row': size === FileUploadSizes.Dense }]\"\n >\n <template v-if=\"!files.length\">\n <InlineSvg v-if=\"size !== FileUploadSizes.Dense\" :src=\"illustrationPath\" name=\"file\" width=\"84\" height=\"96\" />\n <span class=\"tw-text-ice-900\">\n {{ t('ll.fileUpload.dragDropFileHere') }}\n </span>\n <span\n :class=\"\n size === FileUploadSizes.Dense\n ? 'md:tw-ml-1.5 md:tw-mr-3 md:tw-my-0 tw-my-1.5 tw-text-ice-900'\n : 'tw-mt-1.5 tw-my-1.5'\n \"\n >\n {{ t('ll.fileUpload.or') }}\n </span>\n <Button\n class=\"tw-mt-1.5\"\n secondary\n type=\"button\"\n :class=\"classes['file-select-button']\"\n :disabled=\"disabled\"\n @click.stop.prevent=\"openFileDialog\"\n >\n <!-- @slot for custom submit text -->\n <slot name=\"submitText\">{{ t('ll.fileUpload.uploadFile') }}</slot>\n </Button>\n </template>\n <template v-else>\n <div v-for=\"file in files\" :key=\"file.name\">\n <Icon name=\"file\" />\n <span>{{ file.name }}</span>\n <Button :class=\"[classes['remove-button'], classes['button']]\" @click.stop.prevent=\"handleFileDelete(file)\">\n {{ t('ll.fileUpload.remove') }}\n </Button>\n </div>\n </template>\n </div>\n <div v-if=\"$slots.hint && !files.length\" class=\"tw-mt-6 tw-text-center tw-text-xs tw-text-ice-700\">\n <!-- @slot for displaying helpful text and/or links -->\n <slot name=\"hint\"></slot>\n </div>\n </div>\n <input\n v-show=\"false\"\n v-bind=\"inputAttrs\"\n ref=\"fileUploadRef\"\n data-test=\"stash-file-upload|input\"\n type=\"file\"\n :disabled=\"disabled\"\n :accept=\"acceptedMimeTypes.join(',')\"\n :multiple=\"props.multiple\"\n @change=\"handleFileInput\"\n />\n </div>\n</template>\n\n<style module>\n .file-dropbox {\n background: var(--color-ice-200);\n background-image: url(\"data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='4' ry='4' stroke='%23C5C9D4FF' stroke-width='1' stroke-dasharray='5 %2c 5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n border: theme('borderWidth.DEFAULT') solid var(--color-ice-500);\n border-color: transparent;\n }\n\n .is-dragging {\n background-image: none;\n border-color: var(--color-ice-500);\n\n & > * {\n pointer-events: none;\n }\n }\n\n .is-disabled {\n cursor: no-drop;\n }\n\n /* Constrain the upload icon for drag/drop to the required size */\n .upload-icon {\n height: 98px;\n width: 84px;\n }\n\n .remove-button.button {\n background: transparent;\n border: none;\n color: var(--color-red-500);\n\n &:hover {\n background: transparent;\n border: none;\n }\n }\n</style>\n"],"names":["FILE_TYPES","FileUploadSizes","props","__props","classes","useCssModule","emit","__emit","isDraggingOver","ref","fileUploadRef","stashOptions","inject","attributes","useAttrs","inputAttrs","computed","attrs","concatArraysToFirst","a","b","acceptedMimeTypes","fileType","acceptedFileExtensions","illustrationPath","openFileDialog","handleDragEnter","handleDragLeave","handleFileError","error","message","t","logger","areFileTypesAccepted","files","mimeTypes","file","readMimeType","mimeType","extension","processFiles","handleFileInput","event","_a","handleDropFile","handleFileDelete","resolve","reject","fileReader"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FileUpload.js","sources":["../src/components/FileUpload/FileUpload.constants.ts","../src/components/FileUpload/FileUpload.vue"],"sourcesContent":["export const FILE_TYPES = {\n CSV: {\n EXTENSION: ['csv'],\n MIME_TYPES: ['text/csv', 'application/octet-stream', 'application/vnd.ms-excel'],\n ILLUSTRATION: 'csv',\n },\n PDF: {\n EXTENSION: ['pdf'],\n MIME_TYPES: ['application/pdf'],\n ILLUSTRATION: 'pdf',\n },\n PNG: {\n EXTENSION: ['png'],\n MIME_TYPES: ['image/png'],\n ILLUSTRATION: 'image',\n },\n JPEG: {\n EXTENSION: ['jpg', 'jpeg'],\n MIME_TYPES: ['image/jpeg'],\n ILLUSTRATION: 'image',\n },\n DOC: {\n EXTENSION: ['doc', 'docx'],\n MIME_TYPES: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n ILLUSTRATION: 'document',\n },\n XLS: {\n EXTENSION: ['xls', 'xlsx'],\n MIME_TYPES: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n ILLUSTRATION: 'csv',\n },\n};\n\nexport enum FileUploadSizes {\n Dense = 'dense',\n Standard = 'standard',\n}\n\nexport type FileUploadSize = `${FileUploadSizes}`;\n","<script lang=\"ts\">\n export * from './FileUpload.constants';\n</script>\n\n<script setup lang=\"ts\">\n import logger from '@leaflink/snitch';\n import { computed, inject, ref, useAttrs, useCssModule } from 'vue';\n import InlineSvg from 'vue-inline-svg';\n\n import { StashProvideState } from '../../../types/misc';\n import { t } from '../../locale';\n import Button from '../Button/Button.vue';\n import Icon from '../Icon/Icon.vue';\n import { FILE_TYPES, FileUploadSize, FileUploadSizes } from './FileUpload.constants';\n\n export type FileType = 'CSV' | 'PDF' | 'PNG' | 'JPEG' | 'DOC' | 'XLS';\n\n export interface FileUploadProps {\n /**\n * Files to display in the component\n */\n files?: File[];\n\n /**\n * Accepted file types\n */\n fileTypes?: FileType[];\n\n /**\n * Should display only the button\n */\n buttonOnly?: boolean;\n\n /**\n * Allows upload of multiple files\n */\n multiple?: boolean;\n\n /**\n * Is the input disabled\n */\n disabled?: boolean;\n\n /**\n * Component size\n */\n size?: FileUploadSize;\n }\n\n const props = withDefaults(defineProps<FileUploadProps>(), {\n files: () => [],\n fileTypes: () => ['CSV', 'PDF', 'PNG', 'JPEG', 'DOC', 'XLS'],\n buttonOnly: false,\n disabled: false,\n multiple: false,\n size: 'standard',\n });\n\n const classes = useCssModule();\n\n const emit = defineEmits<{\n (e: 'file-select', { files }: { files: FileUploadProps['files'] }): void;\n (e: 'file-delete', file: File): void;\n (e: 'file-error', message: string): void;\n }>();\n\n const isDraggingOver = ref(false);\n const fileUploadRef = ref<HTMLInputElement>();\n\n const stashOptions = inject<StashProvideState>('stashOptions');\n const attributes = useAttrs();\n\n const inputAttrs = computed(() => {\n const attrs = { ...attributes };\n\n delete attrs['data-test'];\n delete attrs.class;\n delete attrs.type;\n delete attrs.accept;\n\n return attrs;\n });\n\n function concatArraysToFirst(a: string[], b: string[]) {\n return a.concat(b);\n }\n\n const acceptedMimeTypes = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].MIME_TYPES).reduce(concatArraysToFirst);\n });\n\n const acceptedFileExtensions = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].EXTENSION).reduce(concatArraysToFirst);\n });\n\n const illustrationPath = computed(() => {\n return `${stashOptions?.staticPath}/illustrations/FileUpload/${FILE_TYPES[props.fileTypes[0]].ILLUSTRATION}.svg`;\n });\n\n function openFileDialog() {\n if (fileUploadRef.value) {\n fileUploadRef.value.value = '';\n fileUploadRef.value.click();\n }\n }\n\n function handleDragEnter() {\n isDraggingOver.value = true;\n }\n\n function handleDragLeave() {\n isDraggingOver.value = false;\n }\n\n function handleFileError(error: Error) {\n const message = t('ll.fileUpload.errors.incorrectFileType', {\n fileTypes: acceptedFileExtensions.value.join(', '),\n });\n\n emit('file-error', message);\n\n logger.log(error);\n }\n\n async function areFileTypesAccepted(files: File[]) {\n if (!acceptedMimeTypes.value.length) return true;\n\n const mimeTypes = await Promise.all(files.map((file) => readMimeType(file)));\n\n const allCorrectMimeTypes =\n !!mimeTypes.length && mimeTypes.every((mimeType) => acceptedMimeTypes.value.includes(mimeType));\n\n if (!allCorrectMimeTypes) {\n throw new Error('One or more files contains an unacceptable mime type.');\n }\n\n const allCorrectFileExtensions = files.every((file) => {\n const extension = file.name.split('.').pop();\n\n return extension && acceptedFileExtensions.value.includes(extension);\n });\n\n if (!allCorrectFileExtensions) {\n throw new Error('One or more files contains an unacceptable extension.');\n }\n\n return true;\n }\n\n async function processFiles(files: File[]) {\n try {\n await areFileTypesAccepted(files);\n\n emit('file-select', { files });\n } catch (error) {\n handleFileError(error as Error);\n }\n }\n\n /**\n * Sets file(s) to selected file(s) from dialogue\n * @param {Object} event - file select event that contains file(s)\n * @returns {Array} An array of files\n */\n function handleFileInput(event: Event) {\n const files = [...((event.target as HTMLInputElement)?.files || [])];\n\n processFiles(files);\n }\n\n /**\n * Sets file to dropped file if it is proper file type\n * @param {Object} event - file select event that contains file\n */\n function handleDropFile(event: DragEvent) {\n if (props.disabled) {\n return;\n }\n\n const files = [...(event.dataTransfer?.files || [])];\n\n isDraggingOver.value = false;\n\n return processFiles(files);\n }\n\n function handleFileDelete(file: File) {\n emit('file-delete', file);\n }\n\n function readMimeType(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file.type) {\n return resolve(file.type);\n } else if (window.FileReader) {\n const fileReader = new FileReader();\n\n fileReader.onload = () => {\n const mimeType =\n fileReader.result && (fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/)\n ? ((fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/) as string[])[0]\n : '';\n\n resolve(mimeType);\n };\n\n fileReader.readAsDataURL(file);\n } else {\n reject(new Error('Failed to read file.'));\n }\n });\n }\n</script>\n\n<template>\n <div class=\"stash-file-upload\" :class=\"attributes.class\" data-test=\"stash-file-upload\">\n <div v-if=\"buttonOnly\">\n <Button secondary type=\"button\" :disabled=\"props.disabled\" @click.stop.prevent=\"openFileDialog\">\n <slot name=\"submitText\">\n {{ t('ll.fileUpload.uploadFile') }}\n </slot>\n </Button>\n </div>\n <div\n v-else\n class=\"rounded p-6\"\n :class=\"[\n classes['file-dropbox'],\n {\n [classes['is-dragging']]: isDraggingOver,\n [classes['is-disabled']]: props.disabled,\n },\n ]\"\n @dragover.prevent=\"handleDragEnter\"\n @drop.prevent=\"handleDropFile\"\n @dragleave.prevent=\"handleDragLeave\"\n >\n <div\n class=\"flex flex-col items-center justify-center text-center\"\n :class=\"[{ 'items-center md:flex-row': size === FileUploadSizes.Dense }]\"\n >\n <template v-if=\"!files.length\">\n <InlineSvg v-if=\"size !== FileUploadSizes.Dense\" :src=\"illustrationPath\" name=\"file\" width=\"84\" height=\"96\" />\n <span class=\"text-ice-900\">\n {{ t('ll.fileUpload.dragDropFileHere') }}\n </span>\n <span\n :class=\"size === FileUploadSizes.Dense ? 'md:ml-1.5 md:mr-3 md:my-0 my-1.5 text-ice-900' : 'mt-1.5 my-1.5'\"\n >\n {{ t('ll.fileUpload.or') }}\n </span>\n <Button\n class=\"mt-1.5\"\n secondary\n type=\"button\"\n :class=\"classes['file-select-button']\"\n :disabled=\"disabled\"\n @click.stop.prevent=\"openFileDialog\"\n >\n <!-- @slot for custom submit text -->\n <slot name=\"submitText\">{{ t('ll.fileUpload.uploadFile') }}</slot>\n </Button>\n </template>\n <template v-else>\n <div v-for=\"file in files\" :key=\"file.name\">\n <Icon name=\"file\" />\n <span>{{ file.name }}</span>\n <Button :class=\"[classes['remove-button'], classes['button']]\" @click.stop.prevent=\"handleFileDelete(file)\">\n {{ t('ll.fileUpload.remove') }}\n </Button>\n </div>\n </template>\n </div>\n <div v-if=\"$slots.hint && !files.length\" class=\"mt-6 text-center text-xs text-ice-700\">\n <!-- @slot for displaying helpful text and/or links -->\n <slot name=\"hint\"></slot>\n </div>\n </div>\n <input\n v-show=\"false\"\n v-bind=\"inputAttrs\"\n ref=\"fileUploadRef\"\n data-test=\"stash-file-upload|input\"\n type=\"file\"\n :disabled=\"disabled\"\n :accept=\"acceptedMimeTypes.join(',')\"\n :multiple=\"props.multiple\"\n @change=\"handleFileInput\"\n />\n </div>\n</template>\n\n<style module>\n @layer utilities {\n .file-dropbox {\n background: var(--color-ice-200);\n background-image: url(\"data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='4' ry='4' stroke='%23C5C9D4FF' stroke-width='1' stroke-dasharray='5 %2c 5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n border: var(--border-width) solid var(--color-ice-500);\n border-color: transparent;\n }\n\n .is-dragging {\n background-image: none;\n border-color: var(--color-ice-500);\n\n & > * {\n pointer-events: none;\n }\n }\n\n .is-disabled {\n cursor: no-drop;\n }\n\n /* Constrain the upload icon for drag/drop to the required size */\n .upload-icon {\n height: 98px;\n width: 84px;\n }\n\n .remove-button.button {\n background: transparent;\n border: none;\n color: var(--color-red-500);\n\n &:hover {\n background: transparent;\n border: none;\n }\n }\n }\n</style>\n"],"names":["FILE_TYPES","FileUploadSizes","props","__props","classes","useCssModule","emit","__emit","isDraggingOver","ref","fileUploadRef","stashOptions","inject","attributes","useAttrs","inputAttrs","computed","attrs","concatArraysToFirst","a","b","acceptedMimeTypes","fileType","acceptedFileExtensions","illustrationPath","openFileDialog","handleDragEnter","handleDragLeave","handleFileError","error","message","t","logger","areFileTypesAccepted","files","mimeTypes","file","readMimeType","mimeType","extension","processFiles","handleFileInput","event","_a","handleDropFile","handleFileDelete","resolve","reject","fileReader"],"mappings":"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"}
|