@teambit/api-reference 0.0.0-e5aa0f6545c2cd1830b05fea0126053c9ed41e46 → 0.0.0-e5bd818efb09c539c653aa0c7b2e70fb199f1011

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.
@@ -0,0 +1,250 @@
1
+ .apiCompareContainer {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ overflow: auto;
6
+ padding: 24px;
7
+ gap: 16px;
8
+ }
9
+
10
+ .summary {
11
+ display: flex;
12
+ gap: 12px;
13
+ align-items: center;
14
+ padding: 12px 16px;
15
+ background: var(--bit-bg-dent, #f6f6f6);
16
+ border-radius: 8px;
17
+ font-size: 13px;
18
+ }
19
+
20
+ .summaryDivider {
21
+ opacity: 0.3;
22
+ color: var(--bit-text-color-light, #6c707c);
23
+ }
24
+
25
+ .summaryBadge {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: 4px;
29
+ padding: 3px 10px;
30
+ border-radius: 12px;
31
+ font-weight: 500;
32
+ font-size: 12px;
33
+ white-space: nowrap;
34
+ }
35
+
36
+ .addedBadge {
37
+ composes: summaryBadge;
38
+ background: color-mix(in srgb, var(--bit-color-added, #2ea043) 15%, transparent);
39
+ color: var(--bit-color-added, #2ea043);
40
+ }
41
+
42
+ .removedBadge {
43
+ composes: summaryBadge;
44
+ background: color-mix(in srgb, var(--bit-color-removed, #f85149) 15%, transparent);
45
+ color: var(--bit-color-removed, #f85149);
46
+ }
47
+
48
+ .modifiedBadge {
49
+ composes: summaryBadge;
50
+ background: color-mix(in srgb, var(--bit-color-modified, #d29922) 15%, transparent);
51
+ color: var(--bit-color-modified, #d29922);
52
+ }
53
+
54
+ .changeList {
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 8px;
58
+ }
59
+
60
+ .sectionTitle {
61
+ margin: 16px 0 8px;
62
+ font-size: 12px;
63
+ font-weight: 600;
64
+ text-transform: uppercase;
65
+ letter-spacing: 0.5px;
66
+ color: var(--bit-text-color-light, #6c707c);
67
+ }
68
+
69
+ .diffEntry {
70
+ border: 1px solid var(--bit-border-color-lightest, #ededed);
71
+ border-radius: 8px;
72
+ overflow: hidden;
73
+ }
74
+
75
+ .diffEntryHeader {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 10px;
79
+ padding: 10px 16px;
80
+ cursor: pointer;
81
+ user-select: none;
82
+
83
+ &:hover {
84
+ background: var(--bit-bg-dent, #f6f6f6);
85
+ }
86
+ }
87
+
88
+ .statusBadge {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ padding: 2px 8px;
92
+ border-radius: 4px;
93
+ font-weight: 600;
94
+ font-size: 11px;
95
+ text-transform: uppercase;
96
+ letter-spacing: 0.3px;
97
+ flex-shrink: 0;
98
+ white-space: nowrap;
99
+ }
100
+
101
+ .statusAdded {
102
+ composes: statusBadge;
103
+ background: color-mix(in srgb, var(--bit-color-added, #2ea043) 15%, transparent);
104
+ color: var(--bit-color-added, #2ea043);
105
+ }
106
+
107
+ .statusRemoved {
108
+ composes: statusBadge;
109
+ background: color-mix(in srgb, var(--bit-color-removed, #f85149) 15%, transparent);
110
+ color: var(--bit-color-removed, #f85149);
111
+ }
112
+
113
+ .statusModified {
114
+ composes: statusBadge;
115
+ background: color-mix(in srgb, var(--bit-color-modified, #d29922) 15%, transparent);
116
+ color: var(--bit-color-modified, #d29922);
117
+ }
118
+
119
+ .exportName {
120
+ font-weight: 600;
121
+ font-size: 14px;
122
+ font-family: var(--bit-font-mono, monospace);
123
+ color: var(--bit-text-color-heavy, #2b2b2b);
124
+ }
125
+
126
+ .schemaType {
127
+ color: var(--bit-text-color-light, #6c707c);
128
+ font-size: 12px;
129
+ }
130
+
131
+ .impactBadge {
132
+ composes: summaryBadge;
133
+ margin-left: auto;
134
+ font-size: 11px;
135
+ }
136
+
137
+ .expandIcon {
138
+ color: var(--bit-text-color-light, #6c707c);
139
+ font-size: 10px;
140
+ transition: transform 0.2s;
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ .expandIconOpen {
145
+ composes: expandIcon;
146
+ transform: rotate(90deg);
147
+ }
148
+
149
+ .diffEntryBody {
150
+ padding: 0 16px 16px;
151
+ border-top: 1px solid var(--bit-border-color-lightest, #ededed);
152
+ }
153
+
154
+ .detailsList {
155
+ list-style: none;
156
+ padding: 12px 0 0;
157
+ margin: 0;
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 8px;
161
+ }
162
+
163
+ .detailItem {
164
+ display: flex;
165
+ align-items: flex-start;
166
+ gap: 8px;
167
+ font-size: 13px;
168
+ line-height: 1.5;
169
+ color: var(--bit-text-color-heavy, #2b2b2b);
170
+ }
171
+
172
+ .detailDot {
173
+ width: 7px;
174
+ height: 7px;
175
+ min-width: 7px;
176
+ border-radius: 50%;
177
+ margin-top: 6px;
178
+ flex-shrink: 0;
179
+ }
180
+
181
+ .dotBreaking {
182
+ composes: detailDot;
183
+ background: var(--bit-color-removed, #f85149);
184
+ }
185
+
186
+ .dotNonBreaking {
187
+ composes: detailDot;
188
+ background: var(--bit-color-added, #2ea043);
189
+ }
190
+
191
+ .dotPatch {
192
+ composes: detailDot;
193
+ background: var(--bit-color-modified, #d29922);
194
+ }
195
+
196
+ .detailDescription {
197
+ color: var(--bit-text-color-heavy, #2b2b2b);
198
+ }
199
+
200
+ .signatureBlock {
201
+ margin-top: 12px;
202
+ padding: 12px;
203
+ background: var(--bit-bg-dent, #f6f6f6);
204
+ border-radius: 6px;
205
+ font-family: var(--bit-font-mono, monospace);
206
+ font-size: 12px;
207
+ white-space: pre-wrap;
208
+ word-break: break-word;
209
+ line-height: 1.6;
210
+ color: var(--bit-text-color-heavy, #2b2b2b);
211
+ }
212
+
213
+ .signatureLabel {
214
+ font-weight: 600;
215
+ color: var(--bit-text-color-light, #6c707c);
216
+ margin-top: 12px;
217
+ margin-bottom: 4px;
218
+ font-size: 11px;
219
+ text-transform: uppercase;
220
+ letter-spacing: 0.5px;
221
+ }
222
+
223
+ .signatureRemoved {
224
+ composes: signatureBlock;
225
+ text-decoration: line-through;
226
+ opacity: 0.6;
227
+ }
228
+
229
+ .emptyState {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ height: 100%;
234
+ color: var(--bit-text-color-light, #6c707c);
235
+ font-size: 14px;
236
+ }
237
+
238
+ .toggleSignatures {
239
+ background: none;
240
+ border: none;
241
+ color: var(--bit-accent-color, #0366d6);
242
+ font-size: 12px;
243
+ cursor: pointer;
244
+ padding: 4px 0;
245
+ margin-top: 12px;
246
+
247
+ &:hover {
248
+ text-decoration: underline;
249
+ }
250
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import type { Section } from '@teambit/component';
3
+ import type { TabItem } from '@teambit/component.ui.component-compare.models.component-compare-props';
4
+ import type { ChangeType } from '@teambit/component.ui.component-compare.models.component-compare-change-type';
5
+ import { APICompare } from './api-compare';
6
+
7
+ // Use string cast since the external ChangeType enum doesn't have API yet
8
+ const ChangeTypeAPI = 'API' as unknown as ChangeType;
9
+
10
+ export class APICompareSection implements TabItem, Section {
11
+ navigationLink = {
12
+ href: 'api',
13
+ children: 'API',
14
+ };
15
+
16
+ props = this.navigationLink;
17
+
18
+ route = {
19
+ path: 'api/*',
20
+ element: <APICompare />,
21
+ };
22
+
23
+ order = 15;
24
+ changeType = ChangeTypeAPI;
25
+ id = 'api';
26
+ }
@@ -0,0 +1,202 @@
1
+ import React from 'react';
2
+ import { useComponentCompare } from '@teambit/component.ui.component-compare.context';
3
+ import type { APIDiffResult, APIDiffChange, APIDiffDetail } from './api-compare.types';
4
+ import styles from './api-compare.module.scss';
5
+
6
+ export type { APIDiffResult, APIDiffChange, APIDiffDetail };
7
+
8
+ function impactClass(impact: string): string {
9
+ switch (impact) {
10
+ case 'BREAKING':
11
+ return styles.removedBadge;
12
+ case 'NON_BREAKING':
13
+ return styles.addedBadge;
14
+ case 'PATCH':
15
+ return styles.modifiedBadge;
16
+ default:
17
+ return styles.summaryBadge;
18
+ }
19
+ }
20
+
21
+ function impactLabel(impact: string): string {
22
+ switch (impact) {
23
+ case 'BREAKING':
24
+ return 'Breaking';
25
+ case 'NON_BREAKING':
26
+ return 'Non-breaking';
27
+ case 'PATCH':
28
+ return 'Patch';
29
+ default:
30
+ return impact;
31
+ }
32
+ }
33
+
34
+ function getStatusClass(status: string): string {
35
+ switch (status) {
36
+ case 'ADDED':
37
+ return styles.statusAdded;
38
+ case 'REMOVED':
39
+ return styles.statusRemoved;
40
+ case 'MODIFIED':
41
+ return styles.statusModified;
42
+ default:
43
+ return styles.statusBadge;
44
+ }
45
+ }
46
+
47
+ function getStatusLabel(status: string): string {
48
+ switch (status) {
49
+ case 'ADDED':
50
+ return '+ Added';
51
+ case 'REMOVED':
52
+ return '- Removed';
53
+ case 'MODIFIED':
54
+ return '~ Modified';
55
+ default:
56
+ return status;
57
+ }
58
+ }
59
+
60
+ function dotClass(impact: string): string {
61
+ switch (impact) {
62
+ case 'BREAKING':
63
+ return styles.dotBreaking;
64
+ case 'NON_BREAKING':
65
+ return styles.dotNonBreaking;
66
+ default:
67
+ return styles.dotPatch;
68
+ }
69
+ }
70
+
71
+ function DetailItem({ detail }: { detail: APIDiffDetail }) {
72
+ return (
73
+ <li className={styles.detailItem}>
74
+ <span className={dotClass(detail.impact)} />
75
+ <span className={styles.detailDescription}>{detail.description}</span>
76
+ </li>
77
+ );
78
+ }
79
+
80
+ function APIDiffEntry({ change }: { change: APIDiffChange }) {
81
+ const [expanded, setExpanded] = React.useState(change.status === 'MODIFIED');
82
+ const [showSignatures, setShowSignatures] = React.useState(false);
83
+
84
+ const hasBody =
85
+ change.status === 'MODIFIED'
86
+ ? (change.changes && change.changes.length > 0) || change.baseSignature || change.compareSignature
87
+ : change.baseSignature || change.compareSignature;
88
+
89
+ return (
90
+ <div className={styles.diffEntry}>
91
+ <div
92
+ className={styles.diffEntryHeader}
93
+ onClick={() => hasBody && setExpanded(!expanded)}
94
+ role="button"
95
+ tabIndex={0}
96
+ aria-expanded={hasBody ? expanded : undefined}
97
+ onKeyDown={(e) => {
98
+ if (e.key === 'Enter' || e.key === ' ') {
99
+ e.preventDefault();
100
+ hasBody && setExpanded(!expanded);
101
+ }
102
+ }}
103
+ >
104
+ <span className={getStatusClass(change.status)}>{getStatusLabel(change.status)}</span>
105
+ <span className={styles.exportName}>{change.exportName}</span>
106
+ <span className={styles.schemaType}>{change.schemaType}</span>
107
+ <span className={`${impactClass(change.impact)} ${styles.impactBadge}`}>{impactLabel(change.impact)}</span>
108
+ {hasBody && <span className={expanded ? styles.expandIconOpen : styles.expandIcon}>▶</span>}
109
+ </div>
110
+ {expanded && hasBody && (
111
+ <div className={styles.diffEntryBody}>
112
+ {change.status === 'MODIFIED' && change.changes && change.changes.length > 0 && (
113
+ <ul className={styles.detailsList}>
114
+ {change.changes.map((detail, i) => (
115
+ <DetailItem key={`${detail.changeKind}-${i}`} detail={detail} />
116
+ ))}
117
+ </ul>
118
+ )}
119
+ {change.status === 'MODIFIED' && (change.baseSignature || change.compareSignature) && (
120
+ <>
121
+ <button className={styles.toggleSignatures} onClick={() => setShowSignatures(!showSignatures)}>
122
+ {showSignatures ? '▼ Hide signatures' : '▶ Show signatures'}
123
+ </button>
124
+ {showSignatures && (
125
+ <>
126
+ {change.baseSignature && (
127
+ <div>
128
+ <div className={styles.signatureLabel}>Base</div>
129
+ <div className={styles.signatureRemoved}>{change.baseSignature}</div>
130
+ </div>
131
+ )}
132
+ {change.compareSignature && (
133
+ <div>
134
+ <div className={styles.signatureLabel}>Compare</div>
135
+ <div className={styles.signatureBlock}>{change.compareSignature}</div>
136
+ </div>
137
+ )}
138
+ </>
139
+ )}
140
+ </>
141
+ )}
142
+ {change.status === 'ADDED' && change.compareSignature && (
143
+ <div className={styles.signatureBlock}>{change.compareSignature}</div>
144
+ )}
145
+ {change.status === 'REMOVED' && change.baseSignature && (
146
+ <div className={styles.signatureRemoved}>{change.baseSignature}</div>
147
+ )}
148
+ </div>
149
+ )}
150
+ </div>
151
+ );
152
+ }
153
+
154
+ function ChangeSection({ title, changes }: { title: string; changes: APIDiffChange[] }) {
155
+ if (changes.length === 0) return null;
156
+
157
+ const sorted = [...changes].sort((a, b) => {
158
+ const order: Record<string, number> = { REMOVED: 0, MODIFIED: 1, ADDED: 2 };
159
+ return (order[a.status] ?? 3) - (order[b.status] ?? 3);
160
+ });
161
+
162
+ return (
163
+ <>
164
+ <h3 className={styles.sectionTitle}>{title}</h3>
165
+ <div className={styles.changeList}>
166
+ {sorted.map((change) => (
167
+ <APIDiffEntry key={`${change.visibility}-${change.status}-${change.exportName}`} change={change} />
168
+ ))}
169
+ </div>
170
+ </>
171
+ );
172
+ }
173
+
174
+ export function APICompare() {
175
+ const compareContext = useComponentCompare();
176
+ const apiDiffResult: APIDiffResult | null | undefined = (compareContext as any)?.apiDiffResult;
177
+
178
+ if (apiDiffResult === undefined) {
179
+ return <div className={styles.emptyState}>Loading API diff...</div>;
180
+ }
181
+
182
+ if (!apiDiffResult || !apiDiffResult.hasChanges) {
183
+ return <div className={styles.emptyState}>No API changes between these versions</div>;
184
+ }
185
+
186
+ const { publicChanges, internalChanges, added, removed, modified, breaking, impact } = apiDiffResult;
187
+
188
+ return (
189
+ <div className={styles.apiCompareContainer}>
190
+ <div className={styles.summary}>
191
+ <span className={impactClass(impact)}>{impactLabel(impact)}</span>
192
+ <span className={styles.summaryDivider}>|</span>
193
+ {added > 0 && <span className={styles.addedBadge}>+{added} added</span>}
194
+ {removed > 0 && <span className={styles.removedBadge}>-{removed} removed</span>}
195
+ {modified > 0 && <span className={styles.modifiedBadge}>~{modified} modified</span>}
196
+ {breaking > 0 && <span className={styles.removedBadge}>{breaking} breaking</span>}
197
+ </div>
198
+ <ChangeSection title="Public API" changes={publicChanges} />
199
+ <ChangeSection title="Internal (non-exported)" changes={internalChanges} />
200
+ </div>
201
+ );
202
+ }
@@ -8,24 +8,8 @@ import { APIRefSection } from '@teambit/api-reference.sections.api-reference-sec
8
8
  import type { Harmony, SlotRegistry } from '@teambit/harmony';
9
9
  import { Slot } from '@teambit/harmony';
10
10
  import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer';
11
- import { classRenderer } from '@teambit/api-reference.renderers.class';
12
- import { interfaceRenderer } from '@teambit/api-reference.renderers.interface';
13
- import { typeRenderer } from '@teambit/api-reference.renderers.type';
14
- import { functionRenderer } from '@teambit/api-reference.renderers.function';
15
- import { enumRenderer } from '@teambit/api-reference.renderers.enum';
16
- import { variableRenderer } from '@teambit/api-reference.renderers.variable';
17
- import { unresolvedRenderer } from '@teambit/api-reference.renderers.unresolved';
18
- import { typeRefRenderer } from '@teambit/api-reference.renderers.type-ref';
19
- import { typeUnionRenderer } from '@teambit/api-reference.renderers.type-union';
20
- import { typeIntersectionRenderer } from '@teambit/api-reference.renderers.type-intersection';
21
- import { typeLiteralRenderer } from '@teambit/api-reference.renderers.type-literal';
22
- import { parameterRenderer } from '@teambit/api-reference.renderers.parameter';
23
- import { inferenceTypeRenderer } from '@teambit/api-reference.renderers.inference-type';
24
- import { typeArrayRenderer } from '@teambit/api-reference.renderers.type-array';
25
- import { tupleTypeRenderer } from '@teambit/api-reference.renderers.tuple-type';
26
- import { thisRenderer } from '@teambit/api-reference.renderers.this';
11
+ import { defaultNodeRenderers } from '@teambit/api-reference.renderers.default-node-renderers';
27
12
  import { APIRefRenderersProvider } from '@teambit/api-reference.hooks.use-api-renderers';
28
- import { decoratorRenderer } from '@teambit/api-reference.renderers.decorator';
29
13
  import type { SchemaNodeConstructor } from '@teambit/semantics.entities.semantic-schema';
30
14
  import { SchemaRegistry, Schemas } from '@teambit/semantics.entities.semantic-schema';
31
15
  import type { CodeUI } from '@teambit/code';
@@ -33,8 +17,11 @@ import { CodeAspect } from '@teambit/code';
33
17
  import { TaggedExports } from '@teambit/tagged-exports';
34
18
  import type { WorkspaceUI } from '@teambit/workspace';
35
19
  import { WorkspaceAspect } from '@teambit/workspace';
20
+ import type { ComponentCompareUI } from '@teambit/component-compare';
21
+ import { ComponentCompareAspect } from '@teambit/component-compare';
36
22
 
37
23
  import { APIReferenceAspect } from './api-reference.aspect';
24
+ import { APICompareSection } from './api-compare.section';
38
25
 
39
26
  export type APINodeRendererSlot = SlotRegistry<APINodeRenderer[]>;
40
27
  export class APIReferenceUI {
@@ -45,7 +32,7 @@ export class APIReferenceUI {
45
32
  private workspace: WorkspaceUI
46
33
  ) {}
47
34
 
48
- static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect];
35
+ static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect, ComponentCompareAspect];
49
36
  static runtime = UIRuntime;
50
37
  static slots = [Slot.withType<APINodeRenderer[]>()];
51
38
 
@@ -82,28 +69,10 @@ export class APIReferenceUI {
82
69
  this.apiNodeRendererSlot.register(apiNodeRenderers);
83
70
  }
84
71
 
85
- apiNodeRenderers: APINodeRenderer[] = [
86
- classRenderer,
87
- typeRenderer,
88
- interfaceRenderer,
89
- variableRenderer,
90
- functionRenderer,
91
- enumRenderer,
92
- unresolvedRenderer,
93
- typeRefRenderer,
94
- typeIntersectionRenderer,
95
- typeUnionRenderer,
96
- parameterRenderer,
97
- typeLiteralRenderer,
98
- inferenceTypeRenderer,
99
- typeArrayRenderer,
100
- thisRenderer,
101
- decoratorRenderer,
102
- tupleTypeRenderer,
103
- ];
72
+ apiNodeRenderers: APINodeRenderer[] = defaultNodeRenderers;
104
73
 
105
74
  static async provider(
106
- [componentUI, codeUI, workspaceUI]: [ComponentUI, CodeUI, WorkspaceUI],
75
+ [componentUI, codeUI, workspaceUI, componentCompareUI]: [ComponentUI, CodeUI, WorkspaceUI, ComponentCompareUI],
107
76
  _,
108
77
  [apiNodeRendererSlot]: [APINodeRendererSlot],
109
78
  harmony: Harmony
@@ -118,6 +87,11 @@ export class APIReferenceUI {
118
87
  // register all default schema classes
119
88
  apiReferenceUI.registerSchemaClasses(() => Object.values(Schemas));
120
89
 
90
+ // register API compare tab in component compare
91
+ const apiCompareSection = new APICompareSection();
92
+ componentCompareUI.registerNavigation(apiCompareSection);
93
+ componentCompareUI.registerRoutes([apiCompareSection.route]);
94
+
121
95
  return apiReferenceUI;
122
96
  }
123
97
  }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { APIDiffResult, APIDiffChange, APIDiffDetail } from './api-compare.types';
3
+ export type { APIDiffResult, APIDiffChange, APIDiffDetail };
4
+ export declare function APICompare(): React.JSX.Element;