diff --git a/apps/demo/src/locales/en/ui.json b/apps/demo/src/locales/en/ui.json index 09a6db4dc..5ad1b4c26 100644 --- a/apps/demo/src/locales/en/ui.json +++ b/apps/demo/src/locales/en/ui.json @@ -164,6 +164,16 @@ "withSlot": "With slot" } }, + "file": "File", + "fileCard": { + "title": "File card", + "usage": { + "basic": "Basic", + "buttonProps": "Action button's props", + "messages": "Custom messages", + "visibilityDetail": "With visibility detail" + } + }, "getStarted": "Get started", "gridContainer": { "usage": { diff --git a/apps/demo/src/locales/fr/ui.json b/apps/demo/src/locales/fr/ui.json index f2ec3d1f1..6fead942f 100644 --- a/apps/demo/src/locales/fr/ui.json +++ b/apps/demo/src/locales/fr/ui.json @@ -164,6 +164,16 @@ "withSlot": "With slot" } }, + "file": "File", + "fileCard": { + "title": "File card", + "usage": { + "basic": "Basic", + "buttonProps": "Action button's props", + "messages": "Custom messages", + "visibilityDetail": "With visibility detail" + } + }, "getStarted": "Get started", "gridContainer": { "usage": { diff --git a/apps/demo/src/router/ui.ts b/apps/demo/src/router/ui.ts index b75d716b6..6ccb8443f 100644 --- a/apps/demo/src/router/ui.ts +++ b/apps/demo/src/router/ui.ts @@ -7,6 +7,7 @@ const ConfirmationModal = () => const Data = () => import("@/views/UI/data/Index.vue"); const Divider = () => import("@/views/UI/divider/Index.vue"); const Dropdown = () => import("@/views/UI/dropdown/Index.vue"); +const FileCard = () => import("@/views/UI/fileCard/Index.vue"); const GridContainer = () => import("@/views/UI/gridContainer/Index.vue"); const LoadingPage = () => import("@/views/UI/loading/Index.vue"); const Message = () => import("@/views/UI/message/Index.vue"); @@ -70,6 +71,11 @@ const routes = [ name: "data", path: "data", }, + { + component: FileCard, + name: "fileCard", + path: "file-card", + }, { component: GridContainer, name: "gridContainer", diff --git a/apps/demo/src/views/UI/UiPage.vue b/apps/demo/src/views/UI/UiPage.vue index a2caa9bcb..321c0e455 100644 --- a/apps/demo/src/views/UI/UiPage.vue +++ b/apps/demo/src/views/UI/UiPage.vue @@ -62,6 +62,15 @@ const menu = [ }, ], }, + { + name: t("ui.file"), + children: [ + { + name: t("ui.fileCard.title"), + routeName: "fileCard", + }, + ], + }, { name: t("ui.menu"), children: [ diff --git a/apps/demo/src/views/UI/fileCard/Index.vue b/apps/demo/src/views/UI/fileCard/Index.vue new file mode 100644 index 000000000..3414071dc --- /dev/null +++ b/apps/demo/src/views/UI/fileCard/Index.vue @@ -0,0 +1,281 @@ + + + diff --git a/packages/vue-ui/src/FileCard/ConfirmationFileActions.vue b/packages/vue-ui/src/FileCard/ConfirmationFileActions.vue new file mode 100644 index 000000000..b445edc35 --- /dev/null +++ b/packages/vue-ui/src/FileCard/ConfirmationFileActions.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/packages/vue-ui/src/FileCard/Index.vue b/packages/vue-ui/src/FileCard/Index.vue new file mode 100644 index 000000000..e8bacec86 --- /dev/null +++ b/packages/vue-ui/src/FileCard/Index.vue @@ -0,0 +1,383 @@ + + + + + + + diff --git a/packages/vue-ui/src/assets/css/file-card.css b/packages/vue-ui/src/assets/css/file-card.css new file mode 100644 index 000000000..613b5585f --- /dev/null +++ b/packages/vue-ui/src/assets/css/file-card.css @@ -0,0 +1,107 @@ +.file-card { + box-shadow: -1px -1px 29px -14px rgba(0, 0, 0, 0.28); + max-width: 100%; +} + +.file-card button .icon-left > svg { + width: 1rem; +} + +.file-thumbnail-details-wrapper { + display: flex; + padding: 1rem; + flex-wrap: wrap; + justify-content: flex-start; + align-items: flex-start; + gap: 1rem; +} + +.file-card .file-thumbnail { + align-items: center; + display: flex; + justify-content: center; + min-height: 6rem; + min-width: 6rem; +} + +.file-card .file-thumbnail > i { + font-size: 5rem; +} + +.file-card .details-wrapper { + display: flex; + flex: 1; + flex-direction: column; + gap: 1rem; + justify-content: flex-start; + min-width: 15rem; +} + +.file-card .name-description-details-wrapper { + display: flex; + flex-direction: column; +} + +.file-card .name { + font-size: 1.5rem; + font-weight: bold; +} + +.file-card .file-size { + font-size: 1.3rem; + margin-left: 1rem; +} + +.file-card .description-wrapper-details { + align-items: center; + display: flex; + margin-top: 0.5rem; +} + +.file-card .file-upload-download-details-wrapper { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.file-card .file-upload-details { + display: flex; + gap: 1rem; +} + +.file-card .uploaded-by, +.file-card .uploaded-at { + display: flex; + flex-direction: column; +} + +.file-card .uploaded-by > span:first-child, +.file-card .uploaded-at > span:first-child, +.file-card .download-count > span:first-child, +.file-card .last-downloaded-at > span:first-child { + font-size: 1rem; + font-weight: bold; + margin-bottom: 0.3rem; +} + +.file-card .download-count { + margin-bottom: 0.3rem; +} + +.file-card .download-count > span:last-child, +.file-card.last-downloaded-at > span:last-child { + margin-left: 0.4rem; +} + +.file-card .file-download-details { + display: flex; + flex-direction: column; +} + +.file-card .file-actions { + display: flex; + gap: 1rem; + justify-content: flex-end; + flex-wrap: wrap; + padding: 0rem 1rem 1rem 1rem; +} diff --git a/packages/vue-ui/src/index.ts b/packages/vue-ui/src/index.ts index f9ea65335..1784f3fc3 100644 --- a/packages/vue-ui/src/index.ts +++ b/packages/vue-ui/src/index.ts @@ -13,6 +13,7 @@ import ConfirmationModal from "./ConfirmationModal/Index.vue"; import Divider from "./Divider/Index.vue"; import Dropdown from "./Dropdown/Index.vue"; import Errors from "./Errors/Index.vue"; +import FileCard from "./FileCard/Index.vue"; import DebouncedInput from "./FormWidgets/DebouncedInput/Index.vue"; import GridContainer from "./GridContainer/Index.vue"; import LoadingPage from "./LoadingPage/Index.vue"; @@ -50,6 +51,7 @@ export { ConfirmationModal, DebouncedInput, Errors, + FileCard, GridContainer, GoogleSignInButton, LoadingButton, @@ -68,4 +70,4 @@ export { YoutubeFacade, }; -export type { Error } from "./types"; +export type { Error, FileMessages, IFile } from "./types"; diff --git a/packages/vue-ui/src/types/file.ts b/packages/vue-ui/src/types/file.ts new file mode 100644 index 000000000..b575c8357 --- /dev/null +++ b/packages/vue-ui/src/types/file.ts @@ -0,0 +1,32 @@ +export type FileMessages = { + archiveAction?: string; + archiveConfirmationHeader?: string; + archiveConfirmationMessage?: string; + downloadAction?: string; + editDescriptionAction?: string; + renameAction?: string; + deleteAction?: string; + deleteConfirmationHeader?: string; + deleteConfirmationMessage?: string; + originalFileNameHeader?: string; + descriptionHeader?: string; + downloadCountHeader?: string; + lastDownloadedAtHeader?: string; + uploadedByHeader?: string; + uploadedAtHeader?: string; + actionsHeader?: string; + shareAction?: string; + viewAction?: string; +}; + +export interface IFile { + id: number | string; + originalFileName: string; + description?: string; + size?: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + uploadedBy: any; + uploadedAt: number; + downloadCount?: number; + lastDownloadedAt?: number; +} diff --git a/packages/vue-ui/src/types/index.ts b/packages/vue-ui/src/types/index.ts index fb58fb17c..7247af29a 100644 --- a/packages/vue-ui/src/types/index.ts +++ b/packages/vue-ui/src/types/index.ts @@ -1,3 +1,4 @@ export type { DropdownMenu } from "./dropdown-menu"; export type { Error } from "./error"; +export type { FileMessages, IFile } from "./file"; export type { StepProperties } from "./stepper"; diff --git a/packages/vue-ui/src/utils/date.ts b/packages/vue-ui/src/utils/date.ts new file mode 100644 index 000000000..8affb0937 --- /dev/null +++ b/packages/vue-ui/src/utils/date.ts @@ -0,0 +1,50 @@ +const defaultDateOptions: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "short", + day: "numeric", +}; + +const defaultDateTimeOptions: Intl.DateTimeFormatOptions = { + ...defaultDateOptions, + hour: "numeric", + minute: "numeric", + hour12: true, +}; + +export const formatDate = ( + date: number | string, + locale?: string, + options?: Intl.DateTimeFormatOptions, +) => { + if (!date) { + return null; + } + + const dateFormatOptions = { ...defaultDateOptions, ...options }; + + const formattedDate = new Date(date).toLocaleDateString( + locale || "en-GB", + dateFormatOptions, + ); + + return formattedDate === "Invalid Date" ? null : formattedDate; +}; + +export const formatDateTime = ( + date: number | string, + locale?: string, + options?: Intl.DateTimeFormatOptions, +) => { + if (!date) { + return null; + } + + const dateTimeFormatOptions = { ...defaultDateTimeOptions, ...options }; + + const formattedDateTime = new Date(date).toLocaleDateString( + locale || "en-GB", + dateTimeFormatOptions, + ); + + return formattedDateTime === "Invalid Date" ? null : formattedDateTime; +}; diff --git a/packages/vue-ui/src/utils/index.ts b/packages/vue-ui/src/utils/index.ts index f092c0a60..b8a1c2182 100644 --- a/packages/vue-ui/src/utils/index.ts +++ b/packages/vue-ui/src/utils/index.ts @@ -1 +1,2 @@ export * from "./UseDebouncedValue"; +export * from "./date";