| {imagePromotion.metadata.name || ''} |
{imagePromotion.spec.target.catalogName} |
- {imagePromotion.spec.target.catalogItemName} |
+
+
+ |
diff --git a/libs/ui-components/src/components/common/ResourceLink.css b/libs/ui-components/src/components/common/ResourceLink.css
deleted file mode 100644
index e0d81363d9..0000000000
--- a/libs/ui-components/src/components/common/ResourceLink.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.fctl-resource-link {
- display: inline-flex;
- align-items: center;
-}
-
-.fctl-resource-link.fctl-resource-link__full {
- display: flex;
-}
-
-.fctl-resource-link .fctl-resource-link__text {
- text-wrap: nowrap;
- overflow-x: hidden;
-}
-
-.fctl-resource-link .pf-v6-c-button {
- /* The copy name button is hidden by default */
- clip: rect(0 0 0 0);
- clip-path: inset(50%);
- overflow: hidden;
- white-space: nowrap;
-}
-
-.fctl-resource-link:hover .pf-v6-c-button {
- /* Makes the copy name button visible when hovering over the name */
- clip: initial;
- clip-path: initial;
- overflow: visible;
- white-space: initial;
-}
diff --git a/libs/ui-components/src/components/common/ResourceLink.tsx b/libs/ui-components/src/components/common/ResourceLink.tsx
index f0075f0355..c6b08013de 100644
--- a/libs/ui-components/src/components/common/ResourceLink.tsx
+++ b/libs/ui-components/src/components/common/ResourceLink.tsx
@@ -1,54 +1,32 @@
import * as React from 'react';
import { Link, type RouteWithPostfix } from '../../hooks/useNavigate';
-import CopyButton from './CopyButton';
+import TruncatedText from './TruncatedText';
-import './ResourceLink.css';
-
-const maxDisplayLength = 50;
+export { getDisplayText } from '../../utils/displayText';
type ResourceDisplayLinkProps = {
id: string;
name?: string;
- variant?: 'shortened' | 'full';
routeLink?: RouteWithPostfix;
'data-testid'?: string;
};
-export const getDisplayText = (name: string | undefined) => {
- if (!name) {
- return '-';
- }
- if (name.length <= maxDisplayLength) {
- return name;
- }
- return `${name.substring(0, 6)}...${name.substring(name.length - 7)}`;
-};
-
-const ResourceLink = ({
- id,
- name,
- variant = 'shortened',
- routeLink,
- 'data-testid': dataTestId,
-}: ResourceDisplayLinkProps) => {
+const ResourceLink = ({ id, name, routeLink, 'data-testid': dataTestId }: ResourceDisplayLinkProps) => {
const nameOrId = name || id;
- const displayText = getDisplayText(nameOrId);
- const showCopy = nameOrId !== displayText;
-
- const textEl = {variant === 'full' ? nameOrId : displayText};
return (
-
- {routeLink ? (
-
- {textEl}
-
- ) : (
- {textEl}
- )}
- {showCopy && nameOrId && }
-
+
+ {(textContent) =>
+ routeLink ? (
+
+ {textContent}
+
+ ) : (
+ {textContent}
+ )
+ }
+
);
};
diff --git a/libs/ui-components/src/components/common/TruncatedText.css b/libs/ui-components/src/components/common/TruncatedText.css
new file mode 100644
index 0000000000..ff8b0e90b9
--- /dev/null
+++ b/libs/ui-components/src/components/common/TruncatedText.css
@@ -0,0 +1,35 @@
+.fctl-truncated-text {
+ display: inline-flex;
+ align-items: center;
+ max-width: 100%;
+ min-width: 0;
+}
+
+.fctl-truncated-text__copy {
+ text-wrap: nowrap;
+ overflow-x: hidden;
+}
+
+.fctl-truncated-text .pf-v6-c-button {
+ /* The copy button is hidden by default */
+ clip-path: inset(50%);
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.fctl-truncated-text:hover .pf-v6-c-button,
+.fctl-truncated-text:focus-within .pf-v6-c-button {
+ /* Reveal the copy button on hover or when a descendant has keyboard focus */
+ clip-path: none;
+ overflow: visible;
+ white-space: initial;
+}
+
+@media (hover: none) {
+ .fctl-truncated-text .pf-v6-c-button {
+ /* Touch devices have no reliable hover; keep the copy button visible */
+ clip-path: none;
+ overflow: visible;
+ white-space: initial;
+ }
+}
diff --git a/libs/ui-components/src/components/common/TruncatedText.tsx b/libs/ui-components/src/components/common/TruncatedText.tsx
new file mode 100644
index 0000000000..46abc4f8a6
--- /dev/null
+++ b/libs/ui-components/src/components/common/TruncatedText.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+
+import { defaultMaxDisplayLength, getDisplayText } from '../../utils/displayText';
+import CopyButton from './CopyButton';
+
+import './TruncatedText.css';
+
+type TruncatedTextProps = {
+ text: string;
+ showCopy?: boolean;
+ maxChars?: number;
+ leadingChars?: number;
+ children?: (textContent: string) => React.ReactNode;
+};
+
+const TruncatedText = ({
+ text,
+ showCopy,
+ leadingChars,
+ maxChars = defaultMaxDisplayLength,
+ children,
+}: TruncatedTextProps) => {
+ const displayText = getDisplayText(text, maxChars, leadingChars);
+ const shouldShowCopy = showCopy ?? text !== displayText;
+
+ return (
+
+ {children ? children(displayText) : displayText}
+ {shouldShowCopy && }
+
+ );
+};
+
+export default TruncatedText;
diff --git a/libs/ui-components/src/utils/catalog.ts b/libs/ui-components/src/utils/catalog.ts
index 1567fd38f4..779b8a50a5 100644
--- a/libs/ui-components/src/utils/catalog.ts
+++ b/libs/ui-components/src/utils/catalog.ts
@@ -59,6 +59,9 @@ export const catalogItemCacheKey = (id: CatalogItemId): string => `${id.catalog}
export const formatCatalogItemRef = (ref: CatalogItemRefSpec): string => `${ref.catalog}/${ref.item}:${ref.version}`;
+/** User-facing catalog item label: display name when set, otherwise system name. */
+export const getCatalogItemLabel = (item: CatalogItem): string => item.spec.displayName || item.metadata.name || '';
+
export const toCatalogItemId = (ref: Pick): CatalogItemId => ({
catalog: ref.catalog,
item: ref.item,
@@ -172,7 +175,7 @@ export const getFullContainerURI = (artifacts: CatalogItemArtifact[], version: C
export const resolveCatalogRef = (item: CatalogItem, ref: CatalogItemRefSpec): ResolvedCatalogRef => {
const version = getCurrentVersion(item, ref.version, ref);
- const displayName = item.spec.displayName || item.metadata.name || ref.item;
+ const displayName = getCatalogItemLabel(item) || ref.item;
const imageUri = version ? getFullContainerURI(item.spec.artifacts, version) : undefined;
return {
item,
diff --git a/libs/ui-components/src/utils/displayText.ts b/libs/ui-components/src/utils/displayText.ts
new file mode 100644
index 0000000000..62a6df8dac
--- /dev/null
+++ b/libs/ui-components/src/utils/displayText.ts
@@ -0,0 +1,12 @@
+/** EDM-4074: default max length before middle-ellipsis shortening applies. */
+export const defaultMaxDisplayLength = 50;
+
+export const getDisplayText = (text: string | undefined, maxLength = defaultMaxDisplayLength, leadingChars = 6) => {
+ if (!text) {
+ return '-';
+ }
+ if (text.length <= maxLength) {
+ return text;
+ }
+ return `${text.substring(0, leadingChars)}...${text.substring(text.length - 7)}`;
+};
|