Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,19 @@
* 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 +247,236 @@ export const FileUploadDropZone = ({
};

FileUploadDropZone.displayName = 'FileUploadDropZone';

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

export const FileListItemProgressBar = ({
className,
completeLabel = 'Complete',
deleteLabel = 'Delete',
failed,
failedLabel = 'Failed',
name,
onDelete,
onRetry,
progress,
size,
tryAgainLabel = 'Try again',
uploadingLabel = 'Uploading...',
}: 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">
{completeLabel}
</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">
{uploadingLabel}
</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">
{failedLabel}
</p>
</>
)}
</div>
</div>
</div>
<ButtonUtility
className="tw:-mr-2 tw:-mt-2 tw:self-start"
color="tertiary"
icon={Trash01}
size="xs"
tooltip={deleteLabel}
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}>
{tryAgainLabel}
</Button>
)}
</div>
</li>
);
};

export const FileListItemProgressFill = ({
className,
deleteLabel = 'Delete',
failed,
failedLabel = 'Upload failed, please try again',
name,
onDelete,
onRetry,
progress,
size,
tryAgainLabel = 'Try again',
}: 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 ? failedLabel : 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}>
{tryAgainLabel}
</Button>
)}
</div>
<ButtonUtility
className="tw:-mr-2 tw:-mt-2 tw:self-start"
color="tertiary"
icon={Trash01}
size="xs"
tooltip={deleteLabel}
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
34 changes: 34 additions & 0 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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export enum SidebarItem {
LINEAGE = 'lineage',
COLUMN_BULK_OPERATIONS = 'column-bulk-operations',
DATA_MARKETPLACE = 'data-marketplace',
KNOWLEDGE_CENTER = 'knowledge-center',
CONTEXT_CENTER = 'context-center',
ARTICLE = 'articles',
}

export const SIDEBAR_LIST_ITEMS = {
Expand Down Expand Up @@ -70,6 +71,7 @@ export const SIDEBAR_LIST_ITEMS = {
SidebarItem.GOVERNANCE,
SidebarItem.COLUMN_BULK_OPERATIONS,
],
[SidebarItem.ARTICLE]: [SidebarItem.CONTEXT_CENTER, SidebarItem.ARTICLE],

// Profile Dropdown
'user-name': ['dropdown-profile', 'user-name'],
Expand Down
Loading
Loading