Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@
"author": "OpenMetadata",
"license": "Apache-2.0",
"dependencies": {
"@material/material-color-utilities": "^0.3.0"
"@material/material-color-utilities": "^0.3.0",
"@untitledui/file-icons": "^0.0.9",
"motion": "^12.38.0"
},
"peerDependencies": {
"@emotion/react": ">=11.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@
* Source: https://github.com/untitleduico/react/blob/main/components/application/file-upload/file-upload-base.tsx
*/

import { UploadCloud02 } from '@untitledui/icons';
import type { DragEvent, ChangeEvent } from 'react';
import { useId, useRef, useState } from 'react';
import { Button } from '@/components/base/buttons/button';
import { ButtonUtility } from '@/components/base/buttons/button-utility';
import { ProgressBar } from '@/components/base/progress-indicators/progress-indicators';
import { FeaturedIcon } from '@/components/foundations/featured-icon/featured-icon';
import { cx } from '@/utils/cx';
import {
CheckCircle,
Trash01,
UploadCloud02,
XCircle,
} from '@untitledui/icons';
import type {
ChangeEvent,
ComponentPropsWithRef,
DragEvent,
} from 'react';
import { useId, useRef, useState } from 'react';

export const getReadableFileSize = (bytes: number): string => {
if (bytes === 0) {
Expand Down Expand Up @@ -240,3 +251,216 @@ export const FileUploadDropZone = ({
};

FileUploadDropZone.displayName = 'FileUploadDropZone';

export interface FileListItemProps {
name: string;
size: number;
progress: number;
failed?: boolean;
className?: string;
onDelete?: () => void;
onRetry?: () => void;
}

export const FileListItemProgressBar = ({
className,
failed,
name,
onDelete,
onRetry,
progress,
size,
}: FileListItemProps) => {
const isComplete = progress === 100;

return (
<li
className={cx(
'tw:relative tw:flex tw:gap-3 tw:rounded-xl tw:bg-primary tw:p-4 tw:ring-1 tw:ring-secondary tw:transition-shadow tw:duration-100 tw:ease-linear tw:ring-inset',
failed && 'tw:ring-2 tw:ring-error',
className
)}>
<div className="tw:flex tw:size-10 tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:bg-secondary">
<UploadCloud02 className="tw:size-5 tw:text-tertiary" strokeWidth={1.5} />
</div>

<div className="tw:flex tw:min-w-0 tw:flex-1 tw:flex-col tw:items-start">
<div className="tw:flex tw:w-full tw:min-w-0 tw:max-w-full tw:flex-1">
<div className="tw:min-w-0 tw:flex-1">
<p className="tw:truncate tw:text-sm tw:font-medium tw:text-secondary">
{name}
</p>
<div className="tw:mt-0.5 tw:flex tw:items-center tw:gap-2">
<p className="tw:truncate tw:whitespace-nowrap tw:text-sm tw:text-tertiary">
{getReadableFileSize(size)}
</p>
<hr className="tw:h-3 tw:w-px tw:rounded-full tw:border-none tw:bg-border-primary" />
<div className="tw:flex tw:items-center tw:gap-1">
{isComplete && !failed && (
<>
<CheckCircle className="tw:size-4 tw:stroke-[2.5px] tw:text-fg-success-primary" />
<p className="tw:text-sm tw:font-medium tw:text-success-primary">
Complete
</p>
</>
)}
{!isComplete && !failed && (
<>
<UploadCloud02 className="tw:size-4 tw:stroke-[2.5px] tw:text-fg-quaternary" />
<p className="tw:text-sm tw:font-medium tw:text-quaternary">
Uploading...
</p>
</>
)}
{failed && (
<>
Comment thread
gitar-bot[bot] marked this conversation as resolved.
<XCircle className="tw:size-4 tw:text-fg-error-primary" />
<p className="tw:text-sm tw:font-medium tw:text-error-primary">
Failed
</p>
</>
)}
</div>
</div>
</div>
<ButtonUtility
className="tw:-mr-2 tw:-mt-2 tw:self-start"
color="tertiary"
icon={Trash01}
size="xs"
tooltip="Delete"
onClick={onDelete}
/>
</div>

{!failed && (
<div className="tw:mt-1 tw:w-full">
<ProgressBar
labelPosition="right"
max={100}
min={0}
value={progress}
/>
</div>
)}

{failed && (
<Button
className="tw:mt-1.5"
color="link-destructive"
size="sm"
onClick={onRetry}>
Try again
</Button>
)}
</div>
</li>
);
};

export const FileListItemProgressFill = ({
className,
failed,
name,
onDelete,
onRetry,
progress,
size,
}: FileListItemProps) => {
const isComplete = progress === 100;

return (
<li
className={cx(
'tw:relative tw:flex tw:gap-3 tw:overflow-hidden tw:rounded-xl tw:bg-primary tw:p-4',
className
)}>
<div
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={progress}
className={cx(
'tw:absolute tw:inset-0 tw:size-full tw:bg-secondary tw:transition tw:duration-75 tw:ease-linear',
isComplete && 'tw:opacity-0'
)}
role="progressbar"
style={{ transform: `translateX(-${100 - progress}%)` }}
/>
<div
className={cx(
'tw:absolute tw:inset-0 tw:size-full tw:rounded-[inherit] tw:ring-1 tw:ring-secondary tw:transition tw:duration-100 tw:ease-linear tw:ring-inset',
failed && 'tw:ring-2 tw:ring-error'
)}
/>
<div className="tw:relative tw:flex tw:size-10 tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:bg-secondary">
<UploadCloud02 className="tw:size-5 tw:text-tertiary" strokeWidth={1.5} />
</div>

<div className="tw:relative tw:flex tw:min-w-0 tw:flex-1">
<div className="tw:relative tw:flex tw:min-w-0 tw:flex-1 tw:flex-col tw:items-start">
<div className="tw:w-full tw:min-w-0 tw:flex-1">
<p className="tw:truncate tw:text-sm tw:font-medium tw:text-secondary">
{name}
</p>
<div className="tw:mt-0.5 tw:flex tw:items-center tw:gap-2">
<p className="tw:text-sm tw:text-tertiary">
{failed
? 'Upload failed, please try again'
: getReadableFileSize(size)}
</p>
{!failed && (
<>
<hr className="tw:h-3 tw:w-px tw:rounded-full tw:border-none tw:bg-border-primary" />
<div className="tw:flex tw:items-center tw:gap-1">
{isComplete ? (
<CheckCircle className="tw:size-4 tw:stroke-[2.5px] tw:text-fg-success-primary" />
) : (
<UploadCloud02 className="tw:size-4 tw:stroke-[2.5px] tw:text-fg-quaternary" />
)}
<p className="tw:text-sm tw:text-tertiary">{progress}%</p>
</div>
</>
)}
</div>
</div>
{failed && (
<Button
className="tw:mt-1.5"
color="link-destructive"
size="sm"
onClick={onRetry}>
Try again
</Button>
)}
</div>
<ButtonUtility
className="tw:-mr-2 tw:-mt-2 tw:self-start"
color="tertiary"
icon={Trash01}
size="xs"
tooltip="Delete"
onClick={onDelete}
/>
</div>
</li>
);
};

const FileUploadRoot = (props: ComponentPropsWithRef<'div'>) => (
<div
{...props}
className={cx('tw:flex tw:flex-col tw:gap-4', props.className)}
/>
);

const FileUploadList = (props: ComponentPropsWithRef<'ul'>) => (
<ul {...props} className={cx('tw:flex tw:flex-col tw:gap-3', props.className)} />
);

export const FileUpload = {
DropZone: FileUploadDropZone,
List: FileUploadList,
ListItemProgressBar: FileListItemProgressBar,
ListItemProgressFill: FileListItemProgressFill,
Root: FileUploadRoot,
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { HTMLAttributes } from 'react';

const sizes = {
xs: {
wh: 6,
c: 3,
r: 2,
},
sm: {
wh: 8,
c: 4,
Expand All @@ -16,7 +21,7 @@ const sizes = {
export const Dot = ({
size = 'md',
...props
}: HTMLAttributes<HTMLOrSVGElement> & { size?: 'sm' | 'md' }) => {
}: HTMLAttributes<HTMLOrSVGElement> & { size?: 'sm' | 'md' | 'xs' }) => {
return (
<svg
fill="none"
Expand Down
54 changes: 52 additions & 2 deletions openmetadata-ui-core-components/src/main/resources/ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2968,6 +2968,11 @@
"@typescript-eslint/types" "8.57.1"
eslint-visitor-keys "^5.0.0"

"@untitledui/file-icons@^0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@untitledui/file-icons/-/file-icons-0.0.9.tgz#5f2d25bde9bcdecb4efff00a775009caf14a4ce5"
integrity sha512-wz3btNnSSv2hTujgyxEFL21oyjPtGTj3osU8ZEMe8nwdlmLQEILLe96NMg9b1ciiOC5UOWNDeYIt7IfxEM6qtg==

"@untitledui/icons@^0.0.21":
version "0.0.21"
resolved "https://registry.npmjs.org/@untitledui/icons/-/icons-0.0.21.tgz"
Expand Down Expand Up @@ -4121,6 +4126,15 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.6"
signal-exit "^4.0.1"

framer-motion@^12.38.0:
version "12.38.0"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.38.0.tgz#cf28e072a95942881ca4e33fd33be41192fd146b"
integrity sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==
dependencies:
motion-dom "^12.38.0"
motion-utils "^12.36.0"
tslib "^2.4.0"

fs-extra@~11.3.0:
version "11.3.4"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz"
Expand Down Expand Up @@ -4913,6 +4927,26 @@ mlly@^1.7.4:
pkg-types "^1.3.1"
ufo "^1.6.3"

motion-dom@^12.38.0:
version "12.38.0"
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.38.0.tgz#9ef3253ea0fb28b6757588327073848d940e9aab"
integrity sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==
dependencies:
motion-utils "^12.36.0"

motion-utils@^12.36.0:
version "12.36.0"
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.36.0.tgz#cff2df2a28c3fe53a3de7e0103ba7f73ff7d77a7"
integrity sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==

motion@^12.38.0:
version "12.38.0"
resolved "https://registry.yarnpkg.com/motion/-/motion-12.38.0.tgz#851fb591f98ed98815e223c09a0de13614ea4155"
integrity sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==
dependencies:
framer-motion "^12.38.0"
tslib "^2.4.0"

ms@^2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
Expand Down Expand Up @@ -5728,7 +5762,16 @@ string-argv@~0.3.1:
resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==

"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.1.0:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -5805,7 +5848,14 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading