Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8680aae
Added missing padding around the Managed dashboard card
fabrice-akamai Sep 26, 2025
d53e3f6
changed spacing to spacingFunction
fabrice-akamai Sep 26, 2025
ef5485f
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 2, 2026
2a8188d
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 3, 2026
2db02f3
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 10, 2026
a8323a1
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 19, 2026
77ec16e
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 25, 2026
4a803c8
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 27, 2026
52afd48
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Apr 8, 2026
9a5347d
Implement the images multi-select table
fabrice-akamai Apr 8, 2026
4fb3bfe
Remove unnecessary async statement
fabrice-akamai Apr 8, 2026
135a683
Improve the image select component
fabrice-akamai Apr 8, 2026
7fe6322
Rename variable for clarity
fabrice-akamai Apr 8, 2026
fb074ec
Added changeset: Private Image Sharing: implement the images select t…
fabrice-akamai Apr 8, 2026
3cf7aea
Update packages/manager/.changeset/pr-13567-upcoming-features-1775674…
fabrice-akamai Apr 8, 2026
b1f39cd
Fix bug with the image description in the image payload
fabrice-akamai Apr 8, 2026
18ff17a
Add pendoId support for the landing header
fabrice-akamai Apr 9, 2026
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
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Private Image Sharing: implement the images select table in the create share groups page ([#13567](https://github.com/linode/manager/pull/13567))
62 changes: 38 additions & 24 deletions packages/manager/src/components/ImageSelect/ImageSelectTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ import type {
} from './constants';
import type { Filter, Image } from '@linode/api-v4';
import type { LinkProps } from '@tanstack/react-router';
import type { IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS } from 'src/components/ImageSelect/constants';

type SelectionMode = 'multi' | 'single';

interface Props {
/**
Expand All @@ -56,6 +59,7 @@ interface Props {
* Error message to display above the table, e.g. from form validation.
*/
errorText?: string;

/**
* Callback fired when the user selects an image row.
*/
Expand All @@ -65,12 +69,18 @@ interface Props {
*/
pendoIDs:
| typeof IMAGE_SELECT_TABLE_LINODE_CREATE_PENDO_IDS
| typeof IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS;
| typeof IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS
| typeof IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS;
queryParamsPrefix?: string;
/**
* The ID of the currently selected image.
* The IDs of the currently selected images, when using multi-select mode with checkboxes.
*/
selectedImageId?: null | string;
selectedImageIds: string[];
/**
* Whether this table should use single-select mode with radio buttons, or multi-select mode with checkboxes.
* The default is single select.
*/
selectionMode: SelectionMode;
}

type OptionType = { label: string; value: string };
Expand All @@ -82,7 +92,8 @@ export const ImageSelectTable = (props: Props) => {
onSelect,
pendoIDs,
queryParamsPrefix,
selectedImageId,
selectionMode,
selectedImageIds,
} = props;

const theme = useTheme();
Expand Down Expand Up @@ -256,29 +267,31 @@ export const ImageSelectTable = (props: Props) => {
<Hidden lgDown>
<TableHeaderCell>Replicated in</TableHeaderCell>
</Hidden>
<Hidden smDown>
<TableHeaderCell
style={{ whiteSpace: 'nowrap', ...TABLE_CELL_BASE_STYLE }}
>
<Stack alignItems="center" direction="row">
Share Group
<TooltipIcon
data-pendo-id={pendoIDs.shareGroupInfoIcon}
status="info"
sxTooltipIcon={{
padding: '4px',
}}
text={SHARE_GROUP_COLUMN_HEADER_TOOLTIP}
tooltipPosition="right"
/>
</Stack>
</TableHeaderCell>
</Hidden>
{selectionMode === 'single' && (
<Hidden smDown>
<TableHeaderCell
style={{ whiteSpace: 'nowrap', ...TABLE_CELL_BASE_STYLE }}
>
<Stack alignItems="center" direction="row">
Share Group
<TooltipIcon
data-pendo-id={pendoIDs.shareGroupInfoIcon}
status="info"
sxTooltipIcon={{
padding: '4px',
}}
text={SHARE_GROUP_COLUMN_HEADER_TOOLTIP}
tooltipPosition="right"
/>
</Stack>
</TableHeaderCell>
</Hidden>
)}
<Hidden lgDown>
<TableHeaderCell
style={{ whiteSpace: 'nowrap', ...TABLE_CELL_BASE_STYLE }}
>
Size
{selectionMode === 'single' ? 'Size' : 'Original Image'}
</TableHeaderCell>
</Hidden>
<TableHeaderCell
Expand Down Expand Up @@ -319,7 +332,8 @@ export const ImageSelectTable = (props: Props) => {
onSelect={() => onSelect(image)}
pendoIDs={pendoIDs}
regions={regions ?? []}
selected={image.id === selectedImageId}
selectedImageIds={selectedImageIds}
selectionMode={selectionMode}
timezone={profile?.timezone}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,31 @@ import type {
} from './constants';
import type { Image, ImageRegion, Region } from '@linode/api-v4';
import type { Theme } from '@linode/ui';
import type { IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS } from 'src/components/ImageSelect/constants';

interface Props {
image: Image;
onSelect: () => void;
onSelect?: () => void;
pendoIDs:
| typeof IMAGE_SELECT_TABLE_LINODE_CREATE_PENDO_IDS
| typeof IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS;
| typeof IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS
| typeof IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS;
regions: Region[];
selected: boolean;
selectedImageIds: string[];
selectionMode: 'multi' | 'single';
timezone?: string;
}

export const ImageSelectTableRow = (props: Props) => {
const { image, onSelect, pendoIDs, regions, selected, timezone } = props;
const {
image,
onSelect,
pendoIDs,
regions,
selectedImageIds,
timezone,
selectionMode,
} = props;

const {
capabilities,
Expand Down Expand Up @@ -89,16 +100,27 @@ export const ImageSelectTableRow = (props: Props) => {
</StyledFormattedRegionList>
);

const selected = selectedImageIds.includes(id);
return (
<TableRow key={id} rowborder>
<TableRow
key={id}
rowborder
select={onSelect}
selectable={selectionMode === 'multi'}
selected={selected}
>
<TableCell style={{ ...TABLE_CELL_BASE_STYLE }}>
<FormControlLabel
checked={selected}
control={<Radio />}
label={label}
onChange={onSelect}
sx={{ gap: 2 }}
/>
{selectionMode === 'single' ? (
<FormControlLabel
checked={selected}
control={<Radio />}
label={label}
onChange={onSelect}
sx={{ gap: 2 }}
/>
) : (
label
)}
{type === 'manual' && capabilities.includes('cloud-init') && (
<TooltipIcon
data-pendo-id={pendoIDs.metadataSupportedIcon}
Expand Down Expand Up @@ -129,17 +151,19 @@ export const ImageSelectTableRow = (props: Props) => {
/>
</TableCell>
</Hidden>
<Hidden smDown>
<TableCell
style={{
whiteSpace: 'nowrap',
paddingLeft: matchesLgDown ? '58px' : undefined,
...TABLE_CELL_BASE_STYLE,
}}
>
{getShareGroupDisplay()}
</TableCell>
</Hidden>
{selectionMode === 'single' && (
<Hidden smDown>
<TableCell
style={{
whiteSpace: 'nowrap',
paddingLeft: matchesLgDown ? '58px' : undefined,
...TABLE_CELL_BASE_STYLE,
}}
>
{getShareGroupDisplay()}
</TableCell>
</Hidden>
)}
<Hidden lgDown>
<TableCell style={{ whiteSpace: 'nowrap', ...TABLE_CELL_BASE_STYLE }}>
{getSizeDisplay()}
Expand Down
15 changes: 15 additions & 0 deletions packages/manager/src/components/ImageSelect/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,18 @@ export const IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS = {
replicatedRegionPopover: 'Linodes Rebuild Images-Replicated in',
shareGroupInfoIcon: 'Linodes Rebuild Images-Share Group info icon',
};

export const IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS = {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pendo Ids based on the Figma mockup

searchImagesBar: 'Share Groups Create Images-Search click',
tagFilterSelect: 'Share Groups Create Images-Filter by Tag click',
regionFilterSelect: 'Share Groups Create Images-Filter by Region click',
metadataSupportedIcon: 'Share Groups Create Images-Metadata Supported icon',
replicatedRegionPopover: 'Share Groups Create Images-Replicated in',
shareGroupInfoIcon: 'Share Groups Create Images-Share Group info icon',
createImageLink: 'Share Groups Create Images-Create Image Link',
uploadImageLink: 'Share Groups Create Images-Upload Image Link',
imageCheckbox: 'Share Groups Create Images-Image Checkbox',
pageSizeSelect: 'Share Groups Create Images-Page Size Select',
previousPageButton: 'Share Groups Create Images-Previous Page Button',
nextPageButton: 'Share Groups Create Images-Next Page Button',
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,4 @@ export const StyledImageTableContainer = styled(Box, {
'& cds-table-row:last-child:not([rowborder])': {
borderBottom: `1px solid ${theme.tokens.component.Table.Row.Border}`,
},

'& cds-table-header-cell, & cds-table-cell': {
boxSizing: 'border-box',
},
}));
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import {
} from '@linode/ui';
import { useNavigate } from '@tanstack/react-router';
import * as React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Controller, useController, useForm } from 'react-hook-form';

import type { CreateSharegroupPayload } from '@linode/api-v4';
import { IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS } from 'src/components/ImageSelect/constants';
import { ImageSelectTable } from 'src/components/ImageSelect/ImageSelectTable';

import { CREATE_SHARE_GROUP_PENDO_IDS } from '../../constants';

import type { CreateSharegroupPayload, Image } from '@linode/api-v4';

export const ShareGroupsCreate = () => {
const navigate = useNavigate();
Expand All @@ -23,7 +28,10 @@ export const ShareGroupsCreate = () => {
const { control, handleSubmit, setError } =
useForm<CreateSharegroupPayload>();

const selectedImages = [];
const { field: imagesController, fieldState } = useController({
control,
name: 'images',
});

const onSubmit = handleSubmit(async (values) => {
try {
Expand All @@ -43,6 +51,20 @@ export const ShareGroupsCreate = () => {
}
}
});

const onChange = (image: Image) => {
const selectedImages = imagesController.value ?? [];

const { id, label, description } = image;
const imagePayload = { id, label, description: description ?? undefined };

if (!selectedImages.some((img) => img.id === id)) {
imagesController.onChange([...selectedImages, imagePayload]);
} else {
imagesController.onChange(selectedImages.filter((img) => img.id !== id));
}
};

return (
<form onSubmit={onSubmit}>
<Paper>
Expand All @@ -61,6 +83,7 @@ export const ShareGroupsCreate = () => {
noMarginTop
required
{...field}
data-pendo-id={CREATE_SHARE_GROUP_PENDO_IDS.label}
errorText={fieldState.error?.message}
onChange={(e) =>
field.onChange(
Expand All @@ -81,6 +104,7 @@ export const ShareGroupsCreate = () => {
multiline
noMarginTop
{...field}
data-pendo-id={CREATE_SHARE_GROUP_PENDO_IDS.description}
onChange={(e) =>
field.onChange(
e.target.value === '' ? undefined : e.target.value
Expand All @@ -95,18 +119,31 @@ export const ShareGroupsCreate = () => {
<Divider sx={{ marginTop: 4, marginBottom: 4 }} />
<Stack spacing={2}>
<Typography variant="h2">Images</Typography>
<Notice variant="info">Images table is coming soon...</Notice>
<ImageSelectTable
currentRoute="/images/share-groups/create"
errorText={fieldState.error?.message}
onSelect={onChange}
pendoIDs={IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS}
selectedImageIds={
imagesController.value?.map((img) => img.id) ?? []
}
selectionMode="multi"
/>
</Stack>
<Divider sx={{ marginTop: 4, marginBottom: 4 }} />
<Stack spacing={2}>
<Typography variant="h2">
Selected images ({selectedImages.length})
Selected images ({imagesController.value?.length ?? 0})
</Typography>
<Notice variant="info">Selected images is coming soon...</Notice>
</Stack>
</Paper>
<Box display="flex" flexWrap="wrap" justifyContent="flex-end" mt={2}>
<Button buttonType="primary" type="submit">
<Button
buttonType="primary"
data-pendo-id={CREATE_SHARE_GROUP_PENDO_IDS.createButton}
type="submit"
>
Create Share Group
</Button>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import * as React from 'react';
import { DocumentTitleSegment } from 'src/components/DocumentTitle/DocumentTitle';
import { LandingHeader } from 'src/components/LandingHeader/LandingHeader';

import { CREATE_SHARE_GROUP_PENDO_IDS } from '../../constants';
import { ShareGroupsCreate } from './ShareGroupsCreate';

export const ShareGroupsCreateContainer = () => {
return (
<>
<DocumentTitleSegment segment="Create a Share Group" />
<LandingHeader
data-pendo-id={CREATE_SHARE_GROUP_PENDO_IDS.landingHeader}
docsLabel="Docs"
docsLink="https://techdocs.akamai.com/cloud-computing/docs/image-sharing"
spacingBottom={4}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export const DEFAULT_PAGE_SIZES = [25, 50, 75, 100];

export const CREATE_SHARE_GROUP_PENDO_IDS = {
landingHeader: 'Share Groups Create Images-Landing Header',
label: 'Share Groups Create Images-Label',
description: 'Share Groups Create Images-Description',
createButton: 'Share Groups Create Images-Create Button',
};
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ export const Images = () => {
errorText={fieldState.error?.message}
onSelect={onChange}
pendoIDs={IMAGE_SELECT_TABLE_LINODE_CREATE_PENDO_IDS}
selectedImageId={field.value}
selectedImageIds={field.value ? [field.value] : []}
selectionMode="single"
/>
) : (
<Box alignItems="flex-end" display="flex" flexWrap="wrap" gap={2}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export const Image = (props: Props) => {
onSelect={(image) => field.onChange(image?.id ?? null)}
pendoIDs={IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS}
queryParamsPrefix="images"
selectedImageId={field.value}
selectedImageIds={field.value ? [field.value] : []}
selectionMode="single"
/>
) : (
<ImageSelect
Expand Down
Loading