@camunda/ccma-saas-frontend 0.0.34 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/lib/esm/components/c4-billing-page/elements/value-metric.js +3 -3
  2. package/lib/esm/components/c4-billing-page/elements/value-metric.spec.d.ts +1 -0
  3. package/lib/esm/components/c4-billing-page/elements/value-metric.spec.js +49 -0
  4. package/lib/esm/components/c4-create-cluster/c4-create-cluster.js +34 -7
  5. package/lib/esm/components/c4-create-cluster/c4-create-cluster.spec.d.ts +1 -0
  6. package/lib/esm/components/c4-create-cluster/c4-create-cluster.spec.js +187 -0
  7. package/lib/esm/components/c4-create-cluster/c4-create-cluster.types.d.ts +3 -0
  8. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.d.ts +3 -0
  9. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.js +41 -0
  10. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.types.d.ts +10 -0
  11. package/lib/esm/components/c4-delete-organization-modal/c4-delete-organization-modal.types.js +6 -0
  12. package/lib/esm/components/c4-job-dashboard-details-page/c4-job-dashboard-details-page.d.ts +1 -0
  13. package/lib/esm/components/c4-job-dashboard-details-page/c4-job-dashboard-details-page.js +5 -2
  14. package/lib/esm/components/c4-job-dashboard-details-page/c4-job-dashboard-details-page.types.d.ts +1 -1
  15. package/lib/esm/components/c4-job-dashboard-details-page/elements/c4-job-dashboard-piechart.js +2 -2
  16. package/lib/esm/components/c4-job-dashboard-details-page/elements/c4-job-dashboard-time-chart.js +6 -1
  17. package/lib/esm/components/c4-job-dashboard-overview-page/c4-job-dashboard-overview.js +4 -2
  18. package/lib/esm/components/c4-job-dashboard-overview-page/c4-job-dashboard-overview.types.d.ts +1 -1
  19. package/lib/esm/components/c4-theme-switcher/c4-theme-switcher.js +25 -2
  20. package/lib/esm/externalUrls.const.d.ts +90 -0
  21. package/lib/esm/externalUrls.const.js +191 -0
  22. package/lib/esm/externalUrls.const.spec.d.ts +1 -0
  23. package/lib/esm/externalUrls.const.spec.js +108 -0
  24. package/lib/esm/i18n/en/pages/createcluster.json +3 -1
  25. package/lib/esm/index.d.ts +3 -0
  26. package/lib/esm/index.js +2 -0
  27. package/lib/esm/widgets/cluster-client-details/c4-cluster-client-details-app.js +2 -1
  28. package/lib/esm/widgets/cluster-client-details/c4-cluster-client-details-app.types.d.ts +2 -2
  29. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.js +47 -31
  30. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.permissions.spec.js +34 -1
  31. package/lib/esm/widgets/cluster-details/c4-cluster-details-app.types.d.ts +68 -37
  32. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-connector-secrets-tab.d.ts +6 -0
  33. package/lib/esm/widgets/cluster-details/tabs/c4-cluster-details-connector-secrets-tab.js +5 -5
  34. package/lib/esm/widgets/clusters/c4-clusters-page.js +5 -3
  35. package/lib/esm/widgets/clusters/c4-clusters-page.spec.js +37 -2
  36. package/lib/esm/widgets/clusters/c4-clusters-page.types.d.ts +1 -1
  37. package/lib/esm/widgets/create-cluster/c4-create-cluster-page.js +23 -15
  38. package/lib/esm/widgets/create-cluster/c4-create-cluster-page.types.d.ts +1 -1
  39. package/lib/esm/widgets/dashboard/c4-dashboard-app.js +8 -7
  40. package/lib/esm/widgets/dashboard/c4-dashboard-app.types.d.ts +9 -8
  41. package/lib/esm/widgets/group-details/c4-group-details-page.js +2 -3
  42. package/lib/esm/widgets/group-details/c4-group-details-page.types.d.ts +1 -1
  43. package/lib/esm/widgets/management/c4-management-app.js +90 -66
  44. package/lib/esm/widgets/management/c4-management-app.types.d.ts +16 -15
  45. package/lib/esm/widgets/management/tabs/c4-management-usage-tab.js +3 -4
  46. package/lib/esm/widgets/user-details/c4-user-details-page.js +2 -3
  47. package/lib/esm/widgets/user-details/c4-user-details-page.types.d.ts +1 -1
  48. package/package.json +2 -2
@@ -103,14 +103,14 @@ const MeterVisualization = ({ currentPrefix, current, currentPostfix, max, maxPo
103
103
  display: 'grid',
104
104
  gridTemplateColumns: '1fr auto',
105
105
  width: '100%',
106
- }, children: [_jsx("p", { style: { fontSize: '18px' }, children: data.current }), _jsx("p", { style: { color: 'var(--cds-text-helper)', alignSelf: 'end' }, children: data.max })] }), _jsx(MeterChart, { data: [
106
+ }, children: [_jsx("p", { style: { fontSize: '18px' }, children: data.current }), _jsx("p", { style: { color: 'var(--cds-text-helper)', alignSelf: 'end' }, children: data.max })] }), max > 0 && (_jsx(MeterChart, { data: [
107
107
  {
108
108
  group,
109
109
  value: current,
110
110
  },
111
- ], options: chartOptions })] }));
111
+ ], options: chartOptions }))] }));
112
112
  return component;
113
113
  };
114
114
  function formatNumber(number) {
115
- return new Intl.NumberFormat('en-US', { maximumSignificantDigits: 3 }).format(number);
115
+ return new Intl.NumberFormat('en-US').format(number);
116
116
  }
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
4
+ * under one or more contributor license agreements. Licensed under a commercial license.
5
+ * You may not use this file except in compliance with the commercial license.
6
+ */
7
+ import { render, screen } from '@testing-library/react';
8
+ import { vi } from 'vitest';
9
+ import { ValueMetric } from './value-metric.js';
10
+ vi.mock('@carbon/charts-react', () => ({
11
+ MeterChart: ({ data }) => (_jsx("div", { "data-testid": 'meter-chart', "data-value": data[0]?.value })),
12
+ }));
13
+ function buildChartProps(current, max = 500000) {
14
+ return {
15
+ title: 'Process instances',
16
+ loading: false,
17
+ metric: {
18
+ kind: 'chart',
19
+ value: {
20
+ current,
21
+ max,
22
+ },
23
+ },
24
+ };
25
+ }
26
+ describe('ValueMetric', () => {
27
+ describe('MeterChart (progress bar)', () => {
28
+ it('renders the progress bar when included (max) > 0', () => {
29
+ render(_jsx(ValueMetric, { ...buildChartProps(0, 500000) }));
30
+ expect(screen.getByTestId('meter-chart')).toBeInTheDocument();
31
+ });
32
+ it('renders the progress bar when both current and max are > 0', () => {
33
+ render(_jsx(ValueMetric, { ...buildChartProps(400000, 500000) }));
34
+ expect(screen.getByTestId('meter-chart')).toBeInTheDocument();
35
+ });
36
+ it('does NOT render the progress bar when included (max) is zero', () => {
37
+ render(_jsx(ValueMetric, { ...buildChartProps(0, 0) }));
38
+ expect(screen.queryByTestId('meter-chart')).not.toBeInTheDocument();
39
+ });
40
+ it('does NOT render the progress bar when max is zero even if current > 0', () => {
41
+ render(_jsx(ValueMetric, { ...buildChartProps(5, 0) }));
42
+ expect(screen.queryByTestId('meter-chart')).not.toBeInTheDocument();
43
+ });
44
+ it('still renders the current and max values when included (max) is zero', () => {
45
+ render(_jsx(ValueMetric, { ...buildChartProps(0, 0) }));
46
+ expect(screen.getAllByText('0')).toHaveLength(2);
47
+ });
48
+ });
49
+ });
@@ -7,7 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
7
7
  import { C3ClusterTag, } from '@camunda/camunda-composite-components';
8
8
  import { ClusterPlanTypeCategory, checkVersion, VersionCheckOperator, } from '@camunda/ccma-shared-types';
9
9
  import { Button, Checkbox, Column, ContentSwitcher, FormLabel, Grid, InlineLoading, InlineNotification, Link, RadioButton, RadioButtonGroup, RadioTile, SkeletonText, Stack, Switch, Tag, TextArea, TextInput, Tile, } from '@carbon/react';
10
- import { Code, DataBase } from '@carbon/react/icons/index.esm.js';
10
+ import { ChevronDown, ChevronUp, Code, DataBase } from '@carbon/react/icons/index.esm.js';
11
11
  import { useRef, useState } from 'react';
12
12
  import styled from 'styled-components';
13
13
  import { C4ClusterTagSelector } from '../c4-cluster-tag-selector/c4-cluster-tag-selector.js';
@@ -87,6 +87,7 @@ export const C4CreateCluster = ({ loading, cta, formElements, autoUpdate, taskli
87
87
  const defaultClusterPlanType = formElements.clusterPlanType.options.find((option) => option.default === true && option.disabled === false);
88
88
  const [clusterPlanType, setClusterPlanType] = useState(defaultClusterPlanType ? defaultClusterPlanType?.uuid : '');
89
89
  const [selectedTag, setSelectedTag] = useState('dev');
90
+ const [showOlderGenerations, setShowOlderGenerations] = useState(false);
90
91
  const currentClusterPlanType = (clusterPlanType || defaultClusterPlanType?.uuid) ?? '';
91
92
  const setClusterPlanTypeWithEffects = (clusterPlanTypeUuid) => {
92
93
  updateNumberOfAllocatedHwPackages(1);
@@ -402,6 +403,37 @@ export const C4CreateCluster = ({ loading, cta, formElements, autoUpdate, taskli
402
403
  setAuthorizationEnabled(selection === 'true');
403
404
  }, children: [_jsx(RadioButton, { id: 'authorization-enabled', labelText: 'Enabled', value: 'true' }), _jsx(RadioButton, { id: 'authorization-disabled', labelText: 'Disabled', value: 'false' })] }, authorizationEnabled ? 'true' : 'false'), _jsxs("div", { children: [_jsx("p", { children: "When enabled, authorization-based access control is enforced." }), _jsxs("p", { children: ["When disabled, all users and clients will have full access to the cluster.", ' ', _jsx("a", { href: authorizationToggle.authorizationDocs, target: '_blank', rel: 'noopener', children: "Learn more" })] })] }), !authorizationEnabled && (_jsx(InlineNotification, { lowContrast: true, kind: ['stage', 'prod'].includes(selectedTag) ? 'warning' : 'info', hideCloseButton: true, title: 'Authorizations disabled', children: _jsx("p", { children: "All authenticated users & clients will have full access to the cluster." }) }))] }) }) })] }));
404
405
  };
406
+ const renderGenerationRadioButtons = (generations) => {
407
+ return (_jsx(RadioButtonGroupWithGrid, { children: generations.map((generation) => (_jsx(Column, { sm: 4, md: 4, lg: 3, style: {
408
+ justifySelf: 'start',
409
+ marginBottom: '0.5rem',
410
+ }, children: _jsx(RadioButton, { id: generation.uuid, value: generation.uuid, labelText: generation.title, checked: selectedGeneration === generation.uuid, onChange: (value) => {
411
+ updateSelectedGeneration(value);
412
+ }, disabled: isSubmitting() || noAvailableSlots }, generation.uuid) }, generation.uuid))) }));
413
+ };
414
+ const renderGenerationSection = () => {
415
+ const allGenerations = getSelectedChannel()?.generations ?? [];
416
+ const existingClusterGenerations = allGenerations.filter((g) => g.classification === 'existingCluster');
417
+ const channelGenerations = allGenerations.filter((g) => g.classification !== 'existingCluster');
418
+ const hasOlderGenerations = existingClusterGenerations.length > 0;
419
+ return (_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsxs(Stack, { gap: 2, children: [_jsx(FormLabel, { children: "Generation" }), renderGenerationRadioButtons(hasOlderGenerations ? channelGenerations : allGenerations), hasOlderGenerations && (_jsxs("div", { style: {
420
+ marginTop: 'var(--cds-spacing-03)',
421
+ }, children: [_jsx(Button, { kind: 'ghost', size: 'sm', type: 'button', onClick: () => {
422
+ setShowOlderGenerations((prev) => {
423
+ if (prev &&
424
+ existingClusterGenerations.some((g) => g.uuid === selectedGeneration)) {
425
+ return prev;
426
+ }
427
+ return !prev;
428
+ });
429
+ }, renderIcon: showOlderGenerations ? ChevronUp : ChevronDown, "aria-expanded": showOlderGenerations, "aria-controls": 'older-generations-panel', children: formElements.generation.olderGenerationsLabel ??
430
+ 'View older generations' }), showOlderGenerations && (_jsxs("div", { id: 'older-generations-panel', style: {
431
+ marginTop: 'var(--cds-spacing-03)',
432
+ }, children: [renderGenerationRadioButtons(existingClusterGenerations), _jsx("p", { style: {
433
+ color: 'var(--cds-text-helper)',
434
+ }, children: formElements.generation.olderGenerationsHelperText ??
435
+ 'These are older generations that are used in your organization.' })] }))] }))] }) }) }));
436
+ };
405
437
  const renderContent = () => {
406
438
  if (loading) {
407
439
  return (_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(SkeletonText, {}) }) }));
@@ -481,12 +513,7 @@ export const C4CreateCluster = ({ loading, cta, formElements, autoUpdate, taskli
481
513
  : enableMap.prod }) }) }), _jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx("p", { children: formElements.stageLabel.textBelow }) }) })] }), _jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(C4Divider, {}) }) }), _jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsxs(Stack, { gap: 2, children: [_jsx(FormElementTitle, { title: formElements.generation.title }), _jsx("div", { style: { overflowX: 'auto', marginBottom: '0.5rem' }, children: _jsx(ContentSwitcher, { size: 'md', style: {
482
514
  height: '100%',
483
515
  width: `${7 * formElements.generation.channels.length}rem`,
484
- }, onChange: (data) => updateSelectedChannel(String(data.name ?? '')), selectedIndex: defaultChannelIndex, children: formElements.generation.channels.map((channel) => (_jsx(Switch, { name: channel.uuid, disabled: noAvailableSlots, children: channel.title }, channel.uuid))) }) }), getSelectedChannel() && (_jsxs(Stack, { gap: 4, children: [_jsx(Grid, { children: _jsx(Column, { span: 7, children: _jsx("p", { children: getSelectedChannel()?.description }) }) }), _jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsxs(Stack, { gap: 2, children: [_jsx(FormLabel, { children: "Generation" }), _jsx(RadioButtonGroupWithGrid, { children: (getSelectedChannel()?.generations ?? []).map((generation) => (_jsx(Column, { sm: 4, md: 4, lg: 3, style: {
485
- justifySelf: 'start',
486
- marginBottom: '0.5rem',
487
- }, children: _jsx(RadioButton, { id: generation.uuid, value: generation.uuid, labelText: generation.title, checked: selectedGeneration === generation.uuid, onChange: (value) => {
488
- updateSelectedGeneration(value);
489
- }, disabled: isSubmitting() || noAvailableSlots }, generation.uuid) }, generation.uuid))) })] }) }) })] }))] }) }) }), authorizationToggle?.enabled && renderAuthorizationRadio(), tasklistModeToggle?.enabled && renderTasklistV2Content(), autoUpdate?.enabled && (_jsxs(_Fragment, { children: [_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(C4Divider, {}) }) }), _jsxs(Stack, { gap: 2, children: [_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(FormElementTitle, { title: formElements.autoUpdate?.title ?? '' }) }) }), autoUpdateEnabled && (_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(Checkbox, { id: 'autoupdate', labelText: formElements.autoUpdate?.label ?? '', checked: autoUpdateChecked, onChange: (event) => {
516
+ }, onChange: (data) => updateSelectedChannel(String(data.name ?? '')), selectedIndex: defaultChannelIndex, children: formElements.generation.channels.map((channel) => (_jsx(Switch, { name: channel.uuid, disabled: noAvailableSlots, children: channel.title }, channel.uuid))) }) }), getSelectedChannel() && (_jsxs(Stack, { gap: 4, children: [_jsx(Grid, { children: _jsx(Column, { span: 7, children: _jsx("p", { children: getSelectedChannel()?.description }) }) }), renderGenerationSection()] }))] }) }) }), authorizationToggle?.enabled && renderAuthorizationRadio(), tasklistModeToggle?.enabled && renderTasklistV2Content(), autoUpdate?.enabled && (_jsxs(_Fragment, { children: [_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(C4Divider, {}) }) }), _jsxs(Stack, { gap: 2, children: [_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(FormElementTitle, { title: formElements.autoUpdate?.title ?? '' }) }) }), autoUpdateEnabled && (_jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx(Checkbox, { id: 'autoupdate', labelText: formElements.autoUpdate?.label ?? '', checked: autoUpdateChecked, onChange: (event) => {
490
517
  setAutoUpdateChecked(event.target.checked);
491
518
  }, disabled: isSubmitting() || !autoUpdateEnabled || noAvailableSlots }, 'autoupdate') }) })), _jsx(Grid, { children: _jsx(Column, { span: 12, children: _jsx("p", { children: autoUpdateEnabled
492
519
  ? formElements.autoUpdate?.description
@@ -0,0 +1,187 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
4
+ * under one or more contributor license agreements. Licensed under a commercial license.
5
+ * You may not use this file except in compliance with the commercial license.
6
+ */
7
+ import { render, screen } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { C4CreateCluster } from './c4-create-cluster.js';
10
+ function buildChannelsWithClassification() {
11
+ return [
12
+ {
13
+ title: 'Stable',
14
+ description: 'Stable channel',
15
+ uuid: 'stable',
16
+ default: true,
17
+ generations: [
18
+ {
19
+ title: 'Camunda 8.8+gen18',
20
+ uuid: 'gen-8.8-18',
21
+ default: true,
22
+ classification: 'channel',
23
+ },
24
+ {
25
+ title: 'Camunda 8.7+gen24',
26
+ uuid: 'gen-8.7-24',
27
+ default: false,
28
+ classification: 'channel',
29
+ },
30
+ {
31
+ title: 'Camunda 8.4+gen15',
32
+ uuid: 'gen-8.4-15',
33
+ default: false,
34
+ classification: 'existingCluster',
35
+ },
36
+ {
37
+ title: 'Camunda 8.3+gen10',
38
+ uuid: 'gen-8.3-10',
39
+ default: false,
40
+ classification: 'existingCluster',
41
+ },
42
+ ],
43
+ },
44
+ ];
45
+ }
46
+ function buildChannelsWithoutClassification() {
47
+ return [
48
+ {
49
+ title: 'Stable',
50
+ description: 'Stable channel',
51
+ uuid: 'stable',
52
+ default: true,
53
+ generations: [
54
+ {
55
+ title: 'Camunda 8.8+gen18',
56
+ uuid: 'gen-8.8-18',
57
+ default: true,
58
+ },
59
+ {
60
+ title: 'Camunda 8.7+gen24',
61
+ uuid: 'gen-8.7-24',
62
+ default: false,
63
+ },
64
+ ],
65
+ },
66
+ ];
67
+ }
68
+ function buildProps(channels) {
69
+ return {
70
+ loading: false,
71
+ banner: {
72
+ noSlotsLeft: {
73
+ title: 'No slots',
74
+ description: 'No slots available',
75
+ },
76
+ },
77
+ cta: {
78
+ label: 'Create cluster',
79
+ states: {
80
+ submitting: 'Creating...',
81
+ success: 'Created',
82
+ error: 'Error',
83
+ },
84
+ create: vi.fn().mockResolvedValue({ success: true, clusterId: 'c-1' }),
85
+ postCreate: vi.fn(),
86
+ },
87
+ formElements: {
88
+ name: {
89
+ title: 'Cluster name',
90
+ placeholder: 'Name',
91
+ validation: () => ({ isValid: true, message: '' }),
92
+ },
93
+ clusterPlanType: {
94
+ title: 'Cluster type',
95
+ options: [
96
+ {
97
+ title: 'Production',
98
+ type: 'prod',
99
+ uuid: 'prod',
100
+ description: 'Production cluster',
101
+ default: true,
102
+ },
103
+ ],
104
+ },
105
+ region: {
106
+ title: 'Region',
107
+ backupRegionTitle: 'Backup',
108
+ description: 'Select region',
109
+ backupDescription: 'Select backup',
110
+ dualRegionBackups: false,
111
+ options: [
112
+ {
113
+ title: 'EU West',
114
+ uuid: 'eu-west',
115
+ provider: 'aws',
116
+ default: true,
117
+ backups: [],
118
+ },
119
+ ],
120
+ },
121
+ generation: {
122
+ title: 'Channel',
123
+ channels,
124
+ olderGenerationsLabel: 'View older generations',
125
+ olderGenerationsHelperText: 'These are older generations that are used in your organization.',
126
+ },
127
+ stageLabel: {
128
+ title: 'Tag',
129
+ textBelow: 'Assign a tag',
130
+ boxes: {
131
+ dev: { text: 'dev' },
132
+ test: { text: 'test' },
133
+ stage: { text: 'stage' },
134
+ prod: { text: 'prod' },
135
+ },
136
+ },
137
+ },
138
+ };
139
+ }
140
+ describe('C4CreateCluster older generations toggle', () => {
141
+ it('hides existingCluster generations by default', () => {
142
+ render(_jsx(C4CreateCluster, { ...buildProps(buildChannelsWithClassification()) }));
143
+ expect(screen.getByLabelText('Camunda 8.8+gen18')).toBeInTheDocument();
144
+ expect(screen.getByLabelText('Camunda 8.7+gen24')).toBeInTheDocument();
145
+ expect(screen.queryByLabelText('Camunda 8.4+gen15')).not.toBeInTheDocument();
146
+ expect(screen.queryByLabelText('Camunda 8.3+gen10')).not.toBeInTheDocument();
147
+ expect(screen.getByRole('button', { name: /view older generations/i })).toBeInTheDocument();
148
+ });
149
+ it('shows existingCluster generations when toggle is clicked', async () => {
150
+ render(_jsx(C4CreateCluster, { ...buildProps(buildChannelsWithClassification()) }));
151
+ const toggle = screen.getByRole('button', {
152
+ name: /view older generations/i,
153
+ });
154
+ await userEvent.click(toggle);
155
+ expect(screen.getByLabelText('Camunda 8.4+gen15')).toBeInTheDocument();
156
+ expect(screen.getByLabelText('Camunda 8.3+gen10')).toBeInTheDocument();
157
+ expect(screen.getByText('These are older generations that are used in your organization.')).toBeInTheDocument();
158
+ });
159
+ it('prevents collapsing when an older generation is selected', async () => {
160
+ render(_jsx(C4CreateCluster, { ...buildProps(buildChannelsWithClassification()) }));
161
+ const toggle = screen.getByRole('button', {
162
+ name: /view older generations/i,
163
+ });
164
+ await userEvent.click(toggle);
165
+ const olderRadio = screen.getByLabelText('Camunda 8.4+gen15');
166
+ await userEvent.click(olderRadio);
167
+ await userEvent.click(toggle);
168
+ expect(screen.getByLabelText('Camunda 8.4+gen15')).toBeInTheDocument();
169
+ expect(screen.getByLabelText('Camunda 8.3+gen10')).toBeInTheDocument();
170
+ });
171
+ it('allows collapsing when a channel generation is selected', async () => {
172
+ render(_jsx(C4CreateCluster, { ...buildProps(buildChannelsWithClassification()) }));
173
+ const toggle = screen.getByRole('button', {
174
+ name: /view older generations/i,
175
+ });
176
+ await userEvent.click(toggle);
177
+ expect(screen.getByLabelText('Camunda 8.4+gen15')).toBeInTheDocument();
178
+ await userEvent.click(toggle);
179
+ expect(screen.queryByLabelText('Camunda 8.4+gen15')).not.toBeInTheDocument();
180
+ });
181
+ it('does not render the toggle when no existingCluster generations exist', () => {
182
+ render(_jsx(C4CreateCluster, { ...buildProps(buildChannelsWithoutClassification()) }));
183
+ expect(screen.queryByRole('button', { name: /view older generations/i })).not.toBeInTheDocument();
184
+ expect(screen.getByLabelText('Camunda 8.8+gen18')).toBeInTheDocument();
185
+ expect(screen.getByLabelText('Camunda 8.7+gen24')).toBeInTheDocument();
186
+ });
187
+ });
@@ -61,6 +61,7 @@ export interface C4CreateClusterChannelWithGenerations {
61
61
  default: boolean;
62
62
  uuid: string;
63
63
  version?: string;
64
+ classification?: 'channel' | 'existingCluster';
64
65
  }>;
65
66
  }
66
67
  export interface C4CreateClusterProps {
@@ -143,6 +144,8 @@ export interface C4CreateClusterProps {
143
144
  generation: {
144
145
  title: string;
145
146
  channels: C4CreateClusterChannelWithGenerations[];
147
+ olderGenerationsLabel?: string;
148
+ olderGenerationsHelperText?: string;
146
149
  };
147
150
  autoUpdate?: {
148
151
  title: string;
@@ -0,0 +1,3 @@
1
+ import { type JSX } from 'react';
2
+ import type { C4DeleteOrganizationModalProps } from './c4-delete-organization-modal.types';
3
+ export declare const C4DeleteOrganizationModal: ({ t, open, loading, error, organizationName, onRequestClose, onRequestSubmit, preventCloseOnClickOutside, }: C4DeleteOrganizationModalProps) => JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
4
+ * under one or more contributor license agreements. Licensed under a commercial license.
5
+ * You may not use this file except in compliance with the commercial license.
6
+ */
7
+ import { InlineNotification, Modal, Stack, TextInput } from '@carbon/react';
8
+ import { useEffect, useState } from 'react';
9
+ function safeTranslate(t, key, fallback, options) {
10
+ try {
11
+ const translated = t?.(key, options);
12
+ if (!translated || translated === key) {
13
+ return fallback;
14
+ }
15
+ if (key.includes(':')) {
16
+ const withoutNamespace = key.split(':').slice(1).join(':');
17
+ if (translated === withoutNamespace) {
18
+ return fallback;
19
+ }
20
+ }
21
+ return translated;
22
+ }
23
+ catch {
24
+ return fallback;
25
+ }
26
+ }
27
+ export const C4DeleteOrganizationModal = ({ t, open, loading, error, organizationName, onRequestClose, onRequestSubmit, preventCloseOnClickOutside, }) => {
28
+ const [confirmText, setConfirmText] = useState('');
29
+ useEffect(() => {
30
+ if (!open) {
31
+ setConfirmText('');
32
+ }
33
+ }, [open]);
34
+ const modalHeading = safeTranslate(t, 'orgmanagement:tabs.settings.orgmanagement.deleteOrganization', 'Delete organization');
35
+ const primaryButtonText = safeTranslate(t, 'orgmanagement:tabs.overview.actions.deleteOrg', 'Delete');
36
+ const secondaryButtonText = safeTranslate(t, 'common:cancel', 'Cancel');
37
+ const message = safeTranslate(t, 'orgmanagement:tabs.settings.orgmanagement.deleteConfirmation', `Are you sure you want to delete "${organizationName}"? The organization and its clusters, Web Modeler files, and other assets will be deleted. This action cannot be undone.`, { organizationName });
38
+ const confirmLabel = safeTranslate(t, 'orgmanagement:tabs.settings.orgmanagement.deleteConfirmLabel', 'Type the word DELETE to confirm');
39
+ const errorMessage = safeTranslate(t, 'orgmanagement:tabs.settings.orgmanagement.deleteError', 'Failed to delete organization. Please try again.');
40
+ return (_jsx(Modal, { modalHeading: modalHeading, open: open, onRequestClose: onRequestClose, onRequestSubmit: onRequestSubmit, primaryButtonText: primaryButtonText, primaryButtonDisabled: loading || confirmText.trim() !== 'DELETE', secondaryButtonText: secondaryButtonText, size: 'sm', danger: true, id: 'delete-organization-modal', preventCloseOnClickOutside: preventCloseOnClickOutside ?? true, children: _jsxs(Stack, { gap: 6, children: [error && (_jsx(InlineNotification, { kind: 'error', title: errorMessage, hideCloseButton: true })), _jsx("p", { children: message }), _jsx(TextInput, { id: 'c4-delete-organization-confirmation', labelText: confirmLabel, value: confirmText, onChange: (e) => setConfirmText(e.target.value), disabled: loading })] }) }));
41
+ };
@@ -0,0 +1,10 @@
1
+ export interface C4DeleteOrganizationModalProps {
2
+ t?: (key: string, options?: Record<string, unknown>) => string;
3
+ open: boolean;
4
+ loading: boolean;
5
+ error?: boolean;
6
+ organizationName: string;
7
+ onRequestClose: () => void;
8
+ onRequestSubmit: () => void | Promise<void>;
9
+ preventCloseOnClickOutside?: boolean;
10
+ }
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
3
+ * under one or more contributor license agreements. Licensed under a commercial license.
4
+ * You may not use this file except in compliance with the commercial license.
5
+ */
6
+ export {};
@@ -5,4 +5,5 @@ export interface ColorScale {
5
5
  Failed: string;
6
6
  }
7
7
  export declare const CHART_HEIGHT = "20vh";
8
+ export declare const PIE_CHART_MAX_WIDTH_PX = 400;
8
9
  export declare const C4JobDashboardDetailsPage: ({ jobType, config, isLoadingConfig, lastUpdatedAt, errorData, timeSeries, workerData, jobData, onTimeRangeChange, breadcrumb, translations, docsUrls, trackErrorClick, }: C4JobDashboardDetailsPageProps) => import("react/jsx-runtime").JSX.Element;
@@ -6,6 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  */
7
7
  import { InlineNotification, Stack, Tile } from '@carbon/react';
8
8
  import styled from 'styled-components';
9
+ import { getC4JobDashboardDocsUrls } from '../../externalUrls.const.js';
9
10
  import { C4Breadcrumb } from '../c4-breadcrumb/c4-breadcrumb.js';
10
11
  import { C4JobDashboardErrorHandler } from '../c4-job-dashboard-shared/c4-job-dashboard-error-handler.js';
11
12
  import C4JobDashboardTimeRangeDropdown from '../c4-job-dashboard-shared/c4-job-dashboard-time-range-dropdown.js';
@@ -41,7 +42,9 @@ const PieChartWrapper = styled(TileWithShadow) `
41
42
  }
42
43
  `;
43
44
  export const CHART_HEIGHT = '20vh';
45
+ export const PIE_CHART_MAX_WIDTH_PX = 400;
44
46
  export const C4JobDashboardDetailsPage = ({ jobType, config, isLoadingConfig, lastUpdatedAt, errorData, timeSeries, workerData, jobData, onTimeRangeChange, breadcrumb, translations, docsUrls, trackErrorClick, }) => {
47
+ const resolvedDocsUrls = getC4JobDashboardDocsUrls(docsUrls);
45
48
  const colorScale = {
46
49
  Created: '#002D9C',
47
50
  Completed: '#009D9A',
@@ -50,7 +53,7 @@ export const C4JobDashboardDetailsPage = ({ jobType, config, isLoadingConfig, la
50
53
  const featureIsDisabled = !isLoadingConfig && config && !config.enabled;
51
54
  if (featureIsDisabled) {
52
55
  const error = { status: 403, details: 'Feature is disabled', message: '' };
53
- return (_jsx(C4JobDashboardErrorHandler, { itemsLength: 0, showIcons: true, translations: translations.errorHandler, error: error, docsUrls: docsUrls }));
56
+ return (_jsx(C4JobDashboardErrorHandler, { itemsLength: 0, showIcons: true, translations: translations.errorHandler, error: error, docsUrls: resolvedDocsUrls }));
54
57
  }
55
- return (_jsxs(Stack, { gap: 4, children: [_jsx(C4Breadcrumb, { title: translations.title, elements: breadcrumb.elements }), _jsxs("p", { children: [translations.jobTypeLabel, " ", _jsx("strong", { children: jobType })] }), _jsx(C4TimestampInformation, { description: translations.lastUpdated, lastUpdatedAt: lastUpdatedAt }), _jsx(C4JobDashboardTimeRangeDropdown, { onChange: onTimeRangeChange, exportInterval: config?.exportInterval, disabled: isLoadingConfig, translations: translations.dropdown }), jobData.isIncomplete && (_jsx(InlineNotification, { style: { maxWidth: '100%' }, lowContrast: true, kind: 'warning', title: translations.dataLimitWarning.title, subtitle: translations.dataLimitWarning.description })), _jsx(ChartContainer, { children: _jsxs(ChartGrid, { children: [_jsx(TimeChartWrapper, { children: _jsx(C4JobDashboardTimeChart, { timeSeries: timeSeries.items, colorScale: colorScale, isLoading: timeSeries.isLoading, translations: translations, error: timeSeries.error, docsUrls: docsUrls }) }), _jsx(PieChartWrapper, { children: _jsx(C4JobDashboardPieChart, { jobData: jobData.data, colorScale: colorScale, isLoading: jobData.isLoading, translations: translations, error: jobData.error, docsUrls: docsUrls }) })] }) }), _jsx(TileWithShadow, { children: _jsx(C4JobDashboardWorkerTable, { workerData: workerData.items, isLoading: workerData.isLoading, translations: translations, error: workerData.error, docsUrls: docsUrls }) }), _jsx(TileWithShadow, { children: _jsx(C4JobDashboardErrorTable, { errors: errorData.items, operateBaseUrl: errorData.operateBaseUrl, isLoading: errorData.isLoading, translations: translations, error: errorData.error, docsUrls: docsUrls, trackErrorClick: trackErrorClick }) })] }));
58
+ return (_jsxs(Stack, { gap: 4, children: [_jsx(C4Breadcrumb, { title: translations.title, elements: breadcrumb.elements }), _jsxs("p", { children: [translations.jobTypeLabel, " ", _jsx("strong", { children: jobType })] }), _jsx(C4TimestampInformation, { description: translations.lastUpdated, lastUpdatedAt: lastUpdatedAt }), _jsx(C4JobDashboardTimeRangeDropdown, { onChange: onTimeRangeChange, exportInterval: config?.exportInterval, disabled: isLoadingConfig, translations: translations.dropdown }), jobData.isIncomplete && (_jsx(InlineNotification, { style: { maxWidth: '100%' }, lowContrast: true, kind: 'warning', title: translations.dataLimitWarning.title, subtitle: translations.dataLimitWarning.description })), _jsx(ChartContainer, { children: _jsxs(ChartGrid, { children: [_jsx(TimeChartWrapper, { children: _jsx(C4JobDashboardTimeChart, { timeSeries: timeSeries.items, colorScale: colorScale, isLoading: timeSeries.isLoading, translations: translations, error: timeSeries.error, docsUrls: resolvedDocsUrls }) }), _jsx(PieChartWrapper, { children: _jsx(C4JobDashboardPieChart, { jobData: jobData.data, colorScale: colorScale, isLoading: jobData.isLoading, translations: translations, error: jobData.error, docsUrls: resolvedDocsUrls }) })] }) }), _jsx(TileWithShadow, { children: _jsx(C4JobDashboardWorkerTable, { workerData: workerData.items, isLoading: workerData.isLoading, translations: translations, error: workerData.error, docsUrls: resolvedDocsUrls }) }), _jsx(TileWithShadow, { children: _jsx(C4JobDashboardErrorTable, { errors: errorData.items, operateBaseUrl: errorData.operateBaseUrl, isLoading: errorData.isLoading, translations: translations, error: errorData.error, docsUrls: resolvedDocsUrls, trackErrorClick: trackErrorClick }) })] }));
56
59
  };
@@ -100,5 +100,5 @@ export interface C4JobDashboardDetailsPageProps {
100
100
  onTimeRangeChange: (value: number) => void;
101
101
  breadcrumb: C4BreadcrumbProps;
102
102
  translations: JobDashboardDetailsTranslations;
103
- docsUrls: DocsUrls;
103
+ docsUrls?: DocsUrls;
104
104
  }
@@ -6,7 +6,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
6
6
  */
7
7
  import { DonutChart } from '@carbon/charts-react';
8
8
  import { C4JobDashboardErrorHandler } from '../../c4-job-dashboard-shared/c4-job-dashboard-error-handler.js';
9
- import { CHART_HEIGHT } from '../c4-job-dashboard-details-page.js';
9
+ import { CHART_HEIGHT, PIE_CHART_MAX_WIDTH_PX, } from '../c4-job-dashboard-details-page.js';
10
10
  import C4JobDashboardChartHeading from './c4-job-dashboard-chart-heading.js';
11
11
  function C4JobDashboardPieChart({ jobData, error, colorScale, isLoading, translations, docsUrls, }) {
12
12
  const completedCount = jobData?.completed?.count ?? 0;
@@ -70,6 +70,6 @@ function C4JobDashboardPieChart({ jobData, error, colorScale, isLoading, transla
70
70
  const noData = (jobData?.created.count === jobData?.failed.count &&
71
71
  jobData?.completed.count === 0) ||
72
72
  !jobData;
73
- return (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginBottom: 25 }, children: _jsx(C4JobDashboardChartHeading, { title: translations.charts.createdVsCompleted.title, tooltipLabel: translations.charts.createdVsCompleted.tooltip }) }), (!jobData || noData || error) && !isLoading ? (_jsx(C4JobDashboardErrorHandler, { itemsLength: noData ? 0 : 1, showIcons: false, translations: translations.errorHandler, error: error, docsUrls: docsUrls })) : (_jsx("div", { children: _jsx(DonutChart, { data: pieData, options: pieOptions }) }))] }));
73
+ return (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginBottom: 25 }, children: _jsx(C4JobDashboardChartHeading, { title: translations.charts.createdVsCompleted.title, tooltipLabel: translations.charts.createdVsCompleted.tooltip }) }), (!jobData || noData || error) && !isLoading ? (_jsx(C4JobDashboardErrorHandler, { itemsLength: noData ? 0 : 1, showIcons: false, translations: translations.errorHandler, error: error, docsUrls: docsUrls })) : (_jsx("div", { style: { maxWidth: PIE_CHART_MAX_WIDTH_PX, margin: '0 auto' }, children: _jsx(DonutChart, { data: pieData, options: pieOptions }) }))] }));
74
74
  }
75
75
  export default C4JobDashboardPieChart;
@@ -44,6 +44,11 @@ function C4JobDashboardTimeChart({ timeSeries, isLoading, colorScale, translatio
44
44
  const lastTimeDataPoint = Math.max(...timeSeries.map((t) => new Date(t.time).getTime()));
45
45
  const coversMultipleDays = new Date(firstTimeDataPoint).toDateString() !==
46
46
  new Date(lastTimeDataPoint).toDateString();
47
+ const formattedColorScale = {
48
+ [translations.charts.legend.completed]: colorScale.Completed,
49
+ [translations.charts.legend.failed]: colorScale.Failed,
50
+ [translations.charts.legend.created]: colorScale.Created,
51
+ };
47
52
  const areaOptions = {
48
53
  resizable: true,
49
54
  toolbar: {
@@ -89,7 +94,7 @@ function C4JobDashboardTimeChart({ timeSeries, isLoading, colorScale, translatio
89
94
  },
90
95
  },
91
96
  color: {
92
- scale: colorScale,
97
+ scale: formattedColorScale,
93
98
  },
94
99
  };
95
100
  return (_jsxs(Stack, { gap: 6, children: [_jsx(C4JobDashboardChartHeading, { title: translations.charts.jobWorkload.title, tooltipLabel: translations.charts.jobWorkload.tooltip }), _jsxs("strong", { children: [translations.charts.jobWorkload.subtitle, " "] }), (!timeSeries || timeSeries.length === 0 || error) && !isLoading ? (_jsx(C4JobDashboardErrorHandler, { itemsLength: timeSeries.length, showIcons: false, translations: translations.errorHandler, error: error, docsUrls: docsUrls })) : (_jsx("div", { style: { height: CHART_HEIGHT }, children: _jsx(StackedAreaChart, { data: chartData, options: areaOptions }) }))] }));
@@ -8,6 +8,7 @@ import { C3DataTable, } from '@camunda/camunda-composite-components';
8
8
  import { InlineNotification, Stack } from '@carbon/react';
9
9
  import { Launch } from '@carbon/react/icons/index.esm.js';
10
10
  import { useEffect, useState } from 'react';
11
+ import { getC4JobDashboardDocsUrls } from '../../externalUrls.const.js';
11
12
  import { C4Breadcrumb } from '../c4-breadcrumb/c4-breadcrumb.js';
12
13
  import { C4BackgroundFetchingIndicator } from '../c4-job-dashboard-shared/c4-background-fetching-indicator.js';
13
14
  import { C4JobDashboardErrorHandler } from '../c4-job-dashboard-shared/c4-job-dashboard-error-handler.js';
@@ -16,6 +17,7 @@ import C4JobDashboardTimeRangeDropdown from '../c4-job-dashboard-shared/c4-job-d
16
17
  import C4TimestampInformation from '../c4-job-dashboard-shared/c4-timestamp-information.js';
17
18
  import { C4LinkButton } from '../c4-link-button/c4-link-button.js';
18
19
  export const C4JobDashboardOverviewPage = ({ error, items, isIncomplete, config, onTimeRangeChange, breadcrumb, isLoadingConfig, isLoadingTable, translations, lastUpdatedAt, docsUrls, }) => {
20
+ const resolvedDocsUrls = getC4JobDashboardDocsUrls(docsUrls);
19
21
  const [rows, setRows] = useState([]);
20
22
  useEffect(() => {
21
23
  const originalRows = items.map((row, index) => ({
@@ -51,9 +53,9 @@ export const C4JobDashboardOverviewPage = ({ error, items, isIncomplete, config,
51
53
  details: 'Feature is disabled',
52
54
  message: '',
53
55
  };
54
- return (_jsx(C4JobDashboardErrorHandler, { itemsLength: 0, showIcons: true, translations: translations.errorHandler, error: error, docsUrls: docsUrls }));
56
+ return (_jsx(C4JobDashboardErrorHandler, { itemsLength: 0, showIcons: true, translations: translations.errorHandler, error: error, docsUrls: resolvedDocsUrls }));
55
57
  }
56
- return (_jsxs(Stack, { gap: 3, children: [_jsx(C4Breadcrumb, { loading: false, elements: breadcrumb.elements, title: translations.title }), _jsxs("p", { children: [translations.description, ' ', _jsx(C4LinkButton, { onClick: () => window.open('https://docs.camunda.io/docs/components/concepts/job-workers/'), label: translations.learnMoreLabel, icon: _jsx(Launch, {}) })] }), _jsx(C4TimestampInformation, { description: translations.lastUpdated, lastUpdatedAt: lastUpdatedAt }), _jsx(C4JobDashboardTimeRangeDropdown, { onChange: onTimeRangeChange, exportInterval: config?.exportInterval, disabled: isLoadingConfig, translations: translations.dropdown }), shouldShowErrorHandler ? (_jsx(C4JobDashboardErrorHandler, { error: error, itemsLength: items.length, showIcons: true, translations: translations.errorHandler, docsUrls: docsUrls })) : (_jsxs(_Fragment, { children: [isIncomplete && (_jsx(InlineNotification, { style: { maxWidth: '100%' }, lowContrast: true, kind: 'warning', title: translations.dataLimitWarning.title, subtitle: translations.dataLimitWarning.description })), _jsx(C3DataTable, { id: 'usage-history-table', data: rows, toolbar: toolbar, headers: [
58
+ return (_jsxs(Stack, { gap: 3, children: [_jsx(C4Breadcrumb, { loading: false, elements: breadcrumb.elements, title: translations.title }), _jsxs("p", { children: [translations.description, ' ', _jsx(C4LinkButton, { onClick: () => window.open('https://docs.camunda.io/docs/components/concepts/job-workers/'), label: translations.learnMoreLabel, icon: _jsx(Launch, {}) })] }), _jsx(C4TimestampInformation, { description: translations.lastUpdated, lastUpdatedAt: lastUpdatedAt }), _jsx(C4JobDashboardTimeRangeDropdown, { onChange: onTimeRangeChange, exportInterval: config?.exportInterval, disabled: isLoadingConfig, translations: translations.dropdown }), shouldShowErrorHandler ? (_jsx(C4JobDashboardErrorHandler, { error: error, itemsLength: items.length, showIcons: true, translations: translations.errorHandler, docsUrls: resolvedDocsUrls })) : (_jsxs(_Fragment, { children: [isIncomplete && (_jsx(InlineNotification, { style: { maxWidth: '100%' }, lowContrast: true, kind: 'warning', title: translations.dataLimitWarning.title, subtitle: translations.dataLimitWarning.description })), _jsx(C3DataTable, { id: 'usage-history-table', data: rows, toolbar: toolbar, headers: [
57
59
  {
58
60
  key: 'jobType',
59
61
  label: translations.tableHeaders.jobType,
@@ -43,5 +43,5 @@ export interface C4JobDashboardOverviewPageProps {
43
43
  isLoadingConfig: boolean;
44
44
  isLoadingTable: boolean;
45
45
  translations: JobDashboardOverviewTranslations;
46
- docsUrls: DocsUrls;
46
+ docsUrls?: DocsUrls;
47
47
  }
@@ -21,6 +21,12 @@ import cyberzeeIcon from './cyberzee-removebg-preview-100.png';
21
21
  const STORAGE_KEY = 'camunda-theme-mode';
22
22
  const ACTIVATION_KEY = 'camunda-theme-switcher-enabled';
23
23
  const ACTIVATION_EVENT = 'camunda-theme-switcher-activated';
24
+ /**
25
+ * Custom events dispatched by the AprilFoolsNotification component.
26
+ * Used to reposition the theme switcher when the notification is visible.
27
+ */
28
+ const APRIL_FOOLS_SHOWN_EVENT = 'camunda-april-fools-shown';
29
+ const APRIL_FOOLS_HIDDEN_EVENT = 'camunda-april-fools-hidden';
24
30
  /**
25
31
  * Cyberzee icon component for theme toggle
26
32
  */
@@ -54,6 +60,7 @@ const getThemeButtonContent = (theme) => {
54
60
  export const C4ThemeSwitcher = ({ position = 'bottom-right', visible = true, onThemeChange, }) => {
55
61
  const [currentTheme, setCurrentTheme] = useState('normal');
56
62
  const [isActivated, setIsActivated] = useState(() => localStorage.getItem(ACTIVATION_KEY) === 'true');
63
+ const [isAprilFoolsVisible, setIsAprilFoolsVisible] = useState(false);
57
64
  const { resolvedTheme } = useC3Profile();
58
65
  // Register developer-console activation function
59
66
  useEffect(() => {
@@ -70,6 +77,17 @@ export const C4ThemeSwitcher = ({ position = 'bottom-right', visible = true, onT
70
77
  window.removeEventListener(ACTIVATION_EVENT, handleActivation);
71
78
  };
72
79
  }, []);
80
+ // Listen for April Fools notification visibility to shift position left
81
+ useEffect(() => {
82
+ const handleShown = () => setIsAprilFoolsVisible(true);
83
+ const handleHidden = () => setIsAprilFoolsVisible(false);
84
+ window.addEventListener(APRIL_FOOLS_SHOWN_EVENT, handleShown);
85
+ window.addEventListener(APRIL_FOOLS_HIDDEN_EVENT, handleHidden);
86
+ return () => {
87
+ window.removeEventListener(APRIL_FOOLS_SHOWN_EVENT, handleShown);
88
+ window.removeEventListener(APRIL_FOOLS_HIDDEN_EVENT, handleHidden);
89
+ };
90
+ }, []);
73
91
  // Apply theme class to body
74
92
  const applyThemeClass = useCallback((theme, carbonTheme) => {
75
93
  // Remove all theme classes
@@ -174,9 +192,14 @@ export const C4ThemeSwitcher = ({ position = 'bottom-right', visible = true, onT
174
192
  if (!visible || (!isActivated && currentTheme === 'normal')) {
175
193
  return _jsx(_Fragment, { children: " " });
176
194
  }
177
- // Position styles
195
+ // Position styles. When the April Fools notification is visible in the
196
+ // bottom-right corner (right: 1rem, maxWidth: 28rem), shift the button
197
+ // further left so the two never overlap.
178
198
  const positionStyles = {
179
- 'bottom-right': { bottom: '20px', right: '80px' },
199
+ 'bottom-right': {
200
+ bottom: '20px',
201
+ right: isAprilFoolsVisible ? 'calc(28rem + 2rem)' : '80px',
202
+ },
180
203
  'bottom-left': { bottom: '20px', left: '20px' },
181
204
  'top-right': { top: '20px', right: '20px' },
182
205
  'top-left': { top: '20px', left: '20px' },