Skip to content

Commit cc46019

Browse files
feat: bring release status components [TOL-3103] (#1875)
feat: bring release status components
1 parent 5658235 commit cc46019

File tree

9 files changed

+543
-0
lines changed

9 files changed

+543
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { ReactNode } from 'react';
2+
3+
import { Text } from '@contentful/f36-components';
4+
import tokens from '@contentful/f36-tokens';
5+
import { css } from 'emotion';
6+
7+
const styles = {
8+
content: css({
9+
display: 'block',
10+
}),
11+
banner: css({
12+
background: tokens.gray100,
13+
padding: tokens.spacingXs,
14+
margin: `${tokens.spacingXs} ${tokens.spacingS}`,
15+
borderRadius: tokens.borderRadiusSmall,
16+
}),
17+
};
18+
19+
export function Banner({ content, highlight }: { content: ReactNode; highlight?: ReactNode }) {
20+
return (
21+
<div className={styles.banner}>
22+
<Text fontSize="fontSizeS" fontColor="gray700" className={styles.content}>
23+
{content}
24+
</Text>
25+
{highlight && (
26+
<Text fontSize="fontSizeS" fontColor="gray700" as="strong" fontWeight="fontWeightDemiBold">
27+
{highlight}
28+
</Text>
29+
)}
30+
</div>
31+
);
32+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
3+
import { Badge, BadgeVariant } from '@contentful/f36-components';
4+
5+
import type { ReleaseAction } from './types';
6+
7+
type ReleaseEntityActionBadgeProps = {
8+
action: ReleaseAction;
9+
className?: string;
10+
};
11+
12+
const config: Record<ReleaseAction, { label: string; variant: BadgeVariant }> = {
13+
publish: {
14+
label: 'Will publish',
15+
variant: 'positive' as const,
16+
},
17+
unpublish: {
18+
label: 'Becomes draft',
19+
variant: 'warning' as const,
20+
},
21+
'not-in-release': {
22+
label: 'Not in release',
23+
variant: 'secondary' as const,
24+
},
25+
};
26+
27+
export function ReleaseEntityStatusBadge({ className, action }: ReleaseEntityActionBadgeProps) {
28+
const badgeConfig = config[action];
29+
30+
return (
31+
<Badge
32+
testId="release-entity-action-status"
33+
className={className}
34+
variant={badgeConfig.variant}
35+
>
36+
{badgeConfig.label}
37+
</Badge>
38+
);
39+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react';
2+
3+
import { Badge, BadgeVariant, Text } from '@contentful/f36-components';
4+
import tokens from '@contentful/f36-tokens';
5+
import type { LocaleProps } from 'contentful-management';
6+
import { css } from 'emotion';
7+
8+
const styles = {
9+
locale: css({
10+
textOverflow: 'ellipsis',
11+
overflow: 'hidden',
12+
whiteSpace: 'nowrap',
13+
}),
14+
status: css({
15+
flexShrink: 0,
16+
}),
17+
localePublishStatus: css({
18+
display: 'flex',
19+
gap: tokens.spacingXs,
20+
justifyContent: 'space-between',
21+
padding: `${tokens.spacingXs} ${tokens.spacingS}`,
22+
}),
23+
};
24+
25+
type ReleaseEntityStatusLocaleProps = {
26+
locale: Pick<LocaleProps, 'code' | 'default' | 'name'>;
27+
label: string;
28+
variant: BadgeVariant;
29+
};
30+
31+
export function ReleaseEntityStatusLocale({
32+
locale,
33+
label,
34+
variant,
35+
}: ReleaseEntityStatusLocaleProps) {
36+
return (
37+
<div className={styles.localePublishStatus} data-test-id="locale-publishing-status">
38+
<Text className={styles.locale} fontColor="gray700">
39+
{locale.name}{' '}
40+
<Text fontColor="gray500">
41+
({locale.code}){locale.default && ', Default'}
42+
</Text>
43+
</Text>
44+
<Badge className={styles.status} variant={variant}>
45+
{label}
46+
</Badge>
47+
</div>
48+
);
49+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
3+
import { MenuSectionTitle } from '@contentful/f36-components';
4+
import type { LocaleProps } from 'contentful-management';
5+
import { sortBy } from 'lodash';
6+
7+
import { Banner } from './Banner';
8+
import { ReleaseEntityStatusLocale } from './ReleaseEntityStatusLocale';
9+
import { ReleaseLocalesStatus, ReleaseLocalesStatusMap } from './types';
10+
11+
function groupAndSortLocales(
12+
entries: [string, ReleaseLocalesStatus][],
13+
activeLocales?: Pick<LocaleProps, 'code'>[]
14+
) {
15+
// Group into selected locales (for editing) and non selected
16+
const { selected, nonSelected } = entries.reduce(
17+
(prev, [localeCode, localeStatusType]) => {
18+
return activeLocales?.some((sl) => sl.code === localeCode)
19+
? {
20+
...prev,
21+
selected: [...prev.selected, localeStatusType],
22+
}
23+
: {
24+
...prev,
25+
nonSelected: [...prev.nonSelected, localeStatusType],
26+
};
27+
},
28+
{ selected: [] as ReleaseLocalesStatus[], nonSelected: [] as ReleaseLocalesStatus[] }
29+
);
30+
31+
return {
32+
// selected are just sorted by name
33+
selected: sortBy(selected, 'locale.name'),
34+
// non-selected are grouped by status and sort inside the group alphabetical
35+
nonSelected: nonSelected.sort((a, b) => {
36+
if (a.status === b.status) {
37+
return a.locale.name.localeCompare(b.locale.name);
38+
}
39+
40+
if (
41+
a.status === 'becomesDraft' ||
42+
(a.status === 'willPublish' && b.status === 'remainsDraft')
43+
) {
44+
return -1;
45+
}
46+
47+
return 1;
48+
}),
49+
};
50+
}
51+
52+
type ReleaseEntityStatusLocalesListProps = {
53+
statusMap: ReleaseLocalesStatusMap;
54+
activeLocales?: Pick<LocaleProps, 'code'>[];
55+
};
56+
57+
export function ReleaseEntityStatusLocalesList({
58+
statusMap,
59+
activeLocales,
60+
}: ReleaseEntityStatusLocalesListProps) {
61+
const entries = [...statusMap.entries()];
62+
const counters = entries.reduce(
63+
(prev, [, { status }]) => ({
64+
becomesDraft: prev.becomesDraft + (status === 'becomesDraft' ? 1 : 0),
65+
willPublish: prev.willPublish + (status === 'willPublish' ? 1 : 0),
66+
remainsDraft: prev.remainsDraft + (status === 'remainsDraft' ? 1 : 0),
67+
}),
68+
{ willPublish: 0, becomesDraft: 0, remainsDraft: 0 }
69+
);
70+
71+
const { selected, nonSelected } = groupAndSortLocales(entries, activeLocales);
72+
return (
73+
<>
74+
<Banner
75+
content="The statuses of the locales for this content:"
76+
highlight={`${counters.becomesDraft} becomes draft, ${counters.willPublish} will publish, ${counters.remainsDraft} remains draft`}
77+
/>
78+
79+
<div data-test-id="locale-publishing-selected">
80+
<MenuSectionTitle>Locales in the entry editor:</MenuSectionTitle>
81+
{selected.map(({ locale, label, variant }) => (
82+
<ReleaseEntityStatusLocale
83+
key={`selected-${locale.code}`}
84+
label={label}
85+
variant={variant}
86+
locale={locale}
87+
/>
88+
))}
89+
</div>
90+
91+
{nonSelected.length > 0 && (
92+
<div data-test-id="locale-publishing-others">
93+
<MenuSectionTitle>Other locales:</MenuSectionTitle>
94+
{nonSelected.map(({ locale, label, variant }) => (
95+
<ReleaseEntityStatusLocale
96+
key={`others-${locale.code}`}
97+
label={label}
98+
variant={variant}
99+
locale={locale}
100+
/>
101+
))}
102+
</div>
103+
)}
104+
</>
105+
);
106+
}

0 commit comments

Comments
 (0)