Skip to content

Commit 19bdc71

Browse files
committed
feat(FR-2578): add BAIId component for truncated UUID/globalId display (#6700)
resolves [FR-2578](https://lablup.atlassian.net/browse/FR-2578) ## Summary Adds a new `BAIId` component to the `backend.ai-ui` package for rendering identifiers (plain UUIDs or Relay global IDs) in a compact, copyable form. The component wraps `BAIText` with the defaults the project already uses inline at many call sites: ```tsx <BAIText copyable ellipsis monospace style={{ maxWidth: 100 }}> {toLocalId(record.id)} </BAIText> ``` ## API - Accepts exactly one of two mutually exclusive props: `uuid` or `globalId` (no `children`). - `globalId` is decoded via `toLocalId`; decode failures (non-base64 or non-Relay payloads) fall back to the raw input so a bad value doesn't crash the subtree. - Props interface **extends `Omit<BAITextProps, 'children'>`** per the `component-props-extension` rule, so every `BAIText` prop passes through. - Defaults (`copyable`, `ellipsis`, `monospace`, `style.maxWidth: 100`) are all overridable via props. ## Files - `packages/backend.ai-ui/src/components/BAIId.tsx` — component - `packages/backend.ai-ui/src/components/BAIId.stories.tsx` — Storybook story (UUID, global ID, override defaults) - `packages/backend.ai-ui/src/components/index.ts` — export ## Screenshots (Storybook) ### UUID ![BAIId — UUID](https://raw.githubusercontent.com/lablup/backend.ai-webui/assets/images/screenshots/pr-6700/20260414-235552-baiid-uuid.png) ### Global ID ![BAIId — Global ID](https://raw.githubusercontent.com/lablup/backend.ai-webui/assets/images/screenshots/pr-6700/20260414-235556-baiid-global-id.png) ### Override defaults ![BAIId — Override defaults](https://raw.githubusercontent.com/lablup/backend.ai-webui/assets/images/screenshots/pr-6700/20260414-235600-baiid-override-defaults.png) ## Verification `bash scripts/verify.sh` → `=== ALL PASS ===` (Relay, Lint, Format, TypeScript). **Checklist:** (if applicable) - [ ] Documentation - [ ] Minium required manager version - [ ] Specific setting for review (eg., KB link, endpoint or how to setup) - [ ] Minimum requirements to check during review - [ ] Test case(s) to demonstrate the difference of before/after [FR-2578]: https://lablup.atlassian.net/browse/FR-2578?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent 28bb644 commit 19bdc71

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import BAIFlex from './BAIFlex';
2+
import BAIId from './BAIId';
3+
import type { Meta, StoryObj } from '@storybook/react-vite';
4+
5+
const SAMPLE_UUID = '5a59ce9b-afa1-4059-9341-683110eb4408';
6+
const SAMPLE_GLOBAL_ID = btoa(`UserNode:${SAMPLE_UUID}`);
7+
8+
const meta: Meta<typeof BAIId> = {
9+
title: 'Text/BAIId',
10+
component: BAIId,
11+
tags: ['autodocs'],
12+
parameters: {
13+
layout: 'padded',
14+
docs: {
15+
description: {
16+
component: `
17+
**BAIId** is a thin wrapper around \`BAIText\` for rendering identifiers
18+
(plain UUIDs or Relay global IDs) in a compact, copyable form.
19+
20+
It accepts exactly one of two mutually exclusive props:
21+
22+
- \`uuid\`: a plain UUID string.
23+
- \`globalId\`: a base64-encoded Relay global ID; decoded via \`toLocalId\`.
24+
25+
By default it renders with:
26+
- \`copyable\`
27+
- \`ellipsis\` (CSS-based, Safari-compatible)
28+
- \`monospace\`
29+
- \`style={{ maxWidth: 100 }}\`
30+
31+
All defaults are overridable via props; all other \`BAIText\` props pass through.
32+
`,
33+
},
34+
},
35+
},
36+
argTypes: {
37+
uuid: {
38+
control: { type: 'text' },
39+
description: 'Plain UUID string. Mutually exclusive with `globalId`.',
40+
table: { type: { summary: 'string' } },
41+
},
42+
globalId: {
43+
control: { type: 'text' },
44+
description:
45+
'Relay global ID (base64). Decoded to the local id via `toLocalId`. Mutually exclusive with `uuid`.',
46+
table: { type: { summary: 'string' } },
47+
},
48+
},
49+
};
50+
51+
export default meta;
52+
53+
type Story = StoryObj<typeof BAIId>;
54+
55+
export const Default: Story = {
56+
name: 'Basic',
57+
args: {
58+
uuid: SAMPLE_UUID,
59+
},
60+
parameters: {
61+
docs: {
62+
description: {
63+
story:
64+
'Renders a plain UUID in a compact, copyable form. The displayed text may be truncated with ellipsis depending on the available width and `style.maxWidth`; the copy icon copies the full id.',
65+
},
66+
},
67+
},
68+
};
69+
70+
export const WithGlobalId: Story = {
71+
name: 'Global ID',
72+
args: {
73+
globalId: SAMPLE_GLOBAL_ID,
74+
},
75+
parameters: {
76+
docs: {
77+
description: {
78+
story:
79+
'Renders a Relay global ID after decoding the base64 payload and extracting the local id with `toLocalId`. The copy icon copies the decoded local id.',
80+
},
81+
},
82+
},
83+
};
84+
85+
export const OverrideDefaults: Story = {
86+
name: 'Override defaults',
87+
render: () => (
88+
<BAIFlex direction="column" gap="sm">
89+
<BAIId uuid={SAMPLE_UUID} copyable={false} />
90+
<BAIId uuid={SAMPLE_UUID} monospace={false} />
91+
<BAIId uuid={SAMPLE_UUID} style={{ maxWidth: 200 }} />
92+
<BAIId uuid={SAMPLE_UUID} ellipsis={false} style={{ maxWidth: 'none' }} />
93+
</BAIFlex>
94+
),
95+
parameters: {
96+
docs: {
97+
description: {
98+
story:
99+
'All defaults (`copyable`, `ellipsis`, `monospace`, `style.maxWidth`) can be overridden. Other `BAIText` props pass through.',
100+
},
101+
},
102+
},
103+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { toLocalId } from '../helper';
2+
import BAIText, { type BAITextProps } from './BAIText';
3+
import React from 'react';
4+
5+
export type BAIIdProps = Omit<BAITextProps, 'children'> &
6+
({ uuid: string; globalId?: never } | { uuid?: never; globalId: string });
7+
8+
// Safely decode a Relay global id to its local id.
9+
// Falls back to the raw input when the value is not a valid Relay global id
10+
// (e.g., not base64, or decoded payload has no `:` separator), so a bad
11+
// backend value doesn't crash the subtree.
12+
const safeDecodeGlobalId = (globalId: string): string => {
13+
try {
14+
return toLocalId(globalId) ?? globalId;
15+
} catch {
16+
return globalId;
17+
}
18+
};
19+
20+
const BAIId: React.FC<BAIIdProps> = ({
21+
uuid,
22+
globalId,
23+
copyable = true,
24+
ellipsis = true,
25+
monospace = true,
26+
style,
27+
...restProps
28+
}) => {
29+
const value = uuid ?? safeDecodeGlobalId(globalId as string);
30+
31+
return (
32+
<BAIText
33+
copyable={copyable}
34+
ellipsis={ellipsis}
35+
monospace={monospace}
36+
style={{ maxWidth: 100, ...style }}
37+
{...restProps}
38+
>
39+
{value}
40+
</BAIText>
41+
);
42+
};
43+
44+
export default BAIId;

packages/backend.ai-ui/src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export { default as BAIBackButton } from './BAIBackButton';
4343
export type { BAIBackButtonProps } from './BAIBackButton';
4444
export { default as BAIText } from './BAIText';
4545
export type { BAITextProps } from './BAIText';
46+
export { default as BAIId } from './BAIId';
47+
export type { BAIIdProps } from './BAIId';
4648
export { default as BAISelect } from './BAISelect';
4749
export type { BAISelectProps } from './BAISelect';
4850
export { default as BAINotificationItem } from './BAINotificationItem';

0 commit comments

Comments
 (0)