Skip to content

Commit deb85ce

Browse files
committed
Allow custom render labels for unified resources
1 parent 7494880 commit deb85ce

File tree

8 files changed

+173
-26
lines changed

8 files changed

+173
-26
lines changed

web/packages/shared/components/UnifiedResources/CardsView/CardsView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function CardsView({
3636
isProcessing,
3737
pinningSupport,
3838
visibleInputFields,
39+
resourceLabelConfig,
3940
}: ResourceViewProps) {
4041
return (
4142
<CardsContainer className="CardsContainer" gap={2}>
@@ -53,6 +54,7 @@ export function CardsView({
5354
onShowStatusInfo={onShowStatusInfo}
5455
showingStatusInfo={showingStatusInfo}
5556
visibleInputFields={visibleInputFields}
57+
resourceLabelConfig={resourceLabelConfig}
5658
/>
5759
)
5860
)}

web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.story.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import { MemoryRouter } from 'react-router';
2121
import styled from 'styled-components';
2222

2323
import { ButtonBorder } from 'design';
24+
import { Plus } from 'design/Icon';
25+
import { LabelKind } from 'design/Label';
26+
import { IconPlacement } from 'design/Label/types';
2427
import { gap, GapProps } from 'design/system';
2528

2629
// eslint-disable-next-line no-restricted-imports -- FIXME
@@ -90,6 +93,9 @@ const additionalResources = [
9093
type StoryProps = {
9194
withCheckbox: boolean;
9295
withPin: boolean;
96+
withLabelButtonIcon: boolean;
97+
withLabelButtonIconPlacement: IconPlacement;
98+
labelKind: LabelKind;
9399
};
94100

95101
const meta: Meta<StoryProps> = {
@@ -101,11 +107,23 @@ const meta: Meta<StoryProps> = {
101107
withPin: {
102108
control: { type: 'boolean' },
103109
},
110+
withLabelButtonIcon: {
111+
control: { type: 'boolean' },
112+
},
113+
withLabelButtonIconPlacement: {
114+
control: { type: 'select' },
115+
options: ['left', 'right'],
116+
},
117+
labelKind: {
118+
control: { type: 'select' },
119+
options: ['secondary', 'outline-primary', 'outline-warning', ''],
120+
},
104121
},
105122
// default
106123
args: {
107124
withCheckbox: true,
108125
withPin: true,
126+
withLabelButtonIcon: false,
109127
},
110128
};
111129
export default meta;
@@ -163,6 +181,13 @@ export function Cards(props: StoryProps) {
163181
checkbox: props.withCheckbox,
164182
pin: props.withPin,
165183
}}
184+
{...((props.withLabelButtonIcon || props.labelKind) && {
185+
resourceLabelConfig: {
186+
Icon: props.withLabelButtonIcon ? Plus : undefined,
187+
iconPlacement: props.withLabelButtonIconPlacement,
188+
kind: props.labelKind,
189+
},
190+
})}
166191
/>
167192
))}
168193
</Grid>

web/packages/shared/components/UnifiedResources/CardsView/ResourceCard.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import styled, { css } from 'styled-components';
2121

2222
import { Box, ButtonLink, Flex, Label, Text } from 'design';
2323
import { CheckboxInput } from 'design/Checkbox';
24+
import { LabelButtonWithIcon } from 'design/Label/LabelButtonWithIcon';
2425
import { ResourceIcon } from 'design/ResourceIcon';
2526
import { HoverTooltip } from 'design/Tooltip';
2627

@@ -67,6 +68,7 @@ export function ResourceCard({
6768
showingStatusInfo,
6869
viewItem,
6970
visibleInputFields = { pin: true, checkbox: true },
71+
resourceLabelConfig,
7072
}: Omit<ResourceItemProps, 'expandAllLabels'>) {
7173
const {
7274
name,
@@ -284,14 +286,26 @@ export function ResourceCard({
284286
</MoreLabelsButton>
285287
{labels.map((label, i) => {
286288
const labelText = makeLabelTag(label);
289+
const sharedProps = {
290+
title: labelText,
291+
onClick: () => onLabelClick?.(label),
292+
kind: resourceLabelConfig?.kind || 'secondary',
293+
'data-is-label': '',
294+
};
295+
if (resourceLabelConfig?.Icon) {
296+
return (
297+
<StyledLabelButton
298+
key={i}
299+
{...sharedProps}
300+
Icon={resourceLabelConfig.Icon}
301+
placement={resourceLabelConfig.iconPlacement ?? 'right'}
302+
>
303+
{labelText}
304+
</StyledLabelButton>
305+
);
306+
}
287307
return (
288-
<StyledLabel
289-
key={i}
290-
title={labelText}
291-
onClick={() => onLabelClick?.(label)}
292-
kind="secondary"
293-
data-is-label=""
294-
>
308+
<StyledLabel key={i} {...sharedProps}>
295309
{labelText}
296310
</StyledLabel>
297311
);
@@ -475,7 +489,7 @@ const LabelsContainer = styled(Box)<{ showAll?: boolean }>`
475489
overflow: hidden;
476490
`;
477491

478-
const StyledLabel = styled(Label)`
492+
const sharedLabelStyle = css`
479493
height: ${labelHeight}px;
480494
margin: 1px 0;
481495
overflow: hidden;
@@ -485,6 +499,14 @@ const StyledLabel = styled(Label)`
485499
line-height: ${labelHeight - labelVerticalMargin}px;
486500
`;
487501

502+
const StyledLabel = styled(Label)`
503+
${sharedLabelStyle}
504+
`;
505+
506+
const StyledLabelButton = styled(LabelButtonWithIcon)`
507+
${sharedLabelStyle}
508+
`;
509+
488510
/**
489511
* The inner labels container always adapts to the size of labels. Its height
490512
* is measured by the resize observer.

web/packages/shared/components/UnifiedResources/ListView/ListView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export function ListView({
3535
isProcessing,
3636
expandAllLabels,
3737
visibleInputFields,
38+
resourceLabelConfig,
3839
}: ResourceViewProps) {
3940
return (
4041
<Flex className="ListContainer">
@@ -53,6 +54,7 @@ export function ListView({
5354
onShowStatusInfo={onShowStatusInfo}
5455
showingStatusInfo={showingStatusInfo}
5556
visibleInputFields={visibleInputFields}
57+
resourceLabelConfig={resourceLabelConfig}
5658
/>
5759
)
5860
)}

web/packages/shared/components/UnifiedResources/ListView/ResourceListItem.story.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import { Meta } from '@storybook/react-vite';
2020

2121
import { ButtonBorder, Flex } from 'design';
22+
import { Plus } from 'design/Icon';
23+
import { LabelKind } from 'design/Label';
24+
import { IconPlacement } from 'design/Label/types';
2225

2326
// eslint-disable-next-line no-restricted-imports -- FIXME
2427
import { apps } from 'teleport/Apps/fixtures';
@@ -83,6 +86,9 @@ const additionalResources = [
8386
type StoryProps = {
8487
withCheckbox: boolean;
8588
withPin: boolean;
89+
withLabelButtonIcon: boolean;
90+
withLabelButtonIconPlacement: IconPlacement;
91+
labelKind: LabelKind;
8692
};
8793

8894
const meta: Meta<StoryProps> = {
@@ -94,11 +100,23 @@ const meta: Meta<StoryProps> = {
94100
withPin: {
95101
control: { type: 'boolean' },
96102
},
103+
withLabelButtonIcon: {
104+
control: { type: 'boolean' },
105+
},
106+
withLabelButtonIconPlacement: {
107+
control: { type: 'select' },
108+
options: ['left', 'right'],
109+
},
110+
labelKind: {
111+
control: { type: 'select' },
112+
options: ['secondary', 'outline-primary', 'outline-warning', ''],
113+
},
97114
},
98115
// default
99116
args: {
100117
withCheckbox: true,
101118
withPin: true,
119+
withLabelButtonIcon: false,
102120
},
103121
};
104122
export default meta;
@@ -145,6 +163,13 @@ export function ListItems(props: StoryProps) {
145163
checkbox: props.withCheckbox,
146164
pin: props.withPin,
147165
}}
166+
{...((props.withLabelButtonIcon || props.labelKind) && {
167+
resourceLabelConfig: {
168+
Icon: props.withLabelButtonIcon ? Plus : undefined,
169+
iconPlacement: props.withLabelButtonIconPlacement,
170+
kind: props.labelKind,
171+
},
172+
})}
148173
/>
149174
))}
150175
</Flex>

web/packages/shared/components/UnifiedResources/ListView/ResourceListItem.tsx

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import styled, { css } from 'styled-components';
2222
import { Box, ButtonIcon, Flex, Label, Text } from 'design';
2323
import { CheckboxInput } from 'design/Checkbox';
2424
import { Tags, Warning } from 'design/Icon';
25+
import { LabelButtonWithIcon } from 'design/Label/LabelButtonWithIcon';
2526
import { ResourceIcon } from 'design/ResourceIcon';
2627
import { HoverTooltip } from 'design/Tooltip';
2728

@@ -51,6 +52,7 @@ export function ResourceListItem({
5152
showingStatusInfo,
5253
viewItem,
5354
visibleInputFields = { pin: true, checkbox: true },
55+
resourceLabelConfig,
5456
}: ResourceItemProps) {
5557
const {
5658
name,
@@ -281,26 +283,32 @@ export function ResourceListItem({
281283
>
282284
{labels.map((label, i) => {
283285
const labelText = makeLabelTag(label);
284-
// We can use the index i as the key since it will always be unique to this label.
286+
const sharedProps = {
287+
title: labelText,
288+
onClick: () => onLabelClick?.(label),
289+
kind: resourceLabelConfig?.kind || 'secondary',
290+
mr: 2,
291+
};
292+
293+
// We can use the index i as the key since
294+
// it will always be unique to this label.
295+
296+
if (resourceLabelConfig?.Icon) {
297+
return (
298+
<StyledLabelButton
299+
key={i}
300+
{...sharedProps}
301+
Icon={resourceLabelConfig.Icon}
302+
placement={resourceLabelConfig.iconPlacement ?? 'right'}
303+
>
304+
{labelText}
305+
</StyledLabelButton>
306+
);
307+
}
285308
return (
286-
<Label
287-
key={i}
288-
title={labelText}
289-
onClick={() => onLabelClick?.(label)}
290-
kind="secondary"
291-
mr={2}
292-
css={`
293-
cursor: pointer;
294-
height: 20px;
295-
line-height: 19px;
296-
max-width: 100%;
297-
overflow: hidden;
298-
text-overflow: ellipsis;
299-
white-space: nowrap;
300-
`}
301-
>
309+
<StyledLabel key={i} {...sharedProps}>
302310
{labelText}
303-
</Label>
311+
</StyledLabel>
304312
);
305313
})}
306314
</Box>
@@ -361,6 +369,24 @@ const RowContainer = styled(Box)<{
361369
}
362370
`;
363371

372+
const sharedLabelStyle = css`
373+
cursor: pointer;
374+
height: 20px;
375+
line-height: 19px;
376+
max-width: 100%;
377+
overflow: hidden;
378+
text-overflow: ellipsis;
379+
white-space: nowrap;
380+
`;
381+
382+
const StyledLabel = styled(Label)`
383+
${sharedLabelStyle}
384+
`;
385+
386+
const StyledLabelButton = styled(LabelButtonWithIcon)`
387+
${sharedLabelStyle}
388+
`;
389+
364390
const RowInnerContainer = styled(Flex)<BackgroundColorProps>`
365391
display: grid;
366392
column-gap: ${props => props.theme.space[3]}px;

web/packages/shared/components/UnifiedResources/UnifiedResources.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import { mapResourceToViewItem } from './shared/viewItemsFactory';
6969
import {
7070
IncludedResourceMode,
7171
PinningSupport,
72+
ResourceLabelConfig,
7273
SharedUnifiedResource,
7374
UnifiedResourceDefinition,
7475
UnifiedResourcesPinning,
@@ -225,6 +226,13 @@ export interface UnifiedResourcesProps {
225226
*/
226227
onShowStatusInfo(resource: UnifiedResourceDefinition): void;
227228

229+
/**
230+
* resourceLabelConfig provides a way to custom handle resource labels.
231+
*
232+
* Look up the type to see the default behaviors if fields are not
233+
* provided.
234+
*/
235+
resourceLabelConfig?: ResourceLabelConfig;
228236
/**
229237
* onLabelClick is a custom label click handler.
230238
*
@@ -271,6 +279,7 @@ export function UnifiedResources(props: UnifiedResourcesProps) {
271279
visibleFilterPanelFields,
272280
visibleResourceItemFields,
273281
onLabelClick,
282+
resourceLabelConfig,
274283
className,
275284
forceNoResources,
276285
} = props;
@@ -711,6 +720,7 @@ export function UnifiedResources(props: UnifiedResourcesProps) {
711720
availabilityFilter?.mode === 'requestable',
712721
},
713722
}),
723+
resourceLabelConfig,
714724
key: generateUnifiedResourceKey(resource),
715725
onShowStatusInfo: () => onShowStatusInfo(resource),
716726
showingStatusInfo:

0 commit comments

Comments
 (0)